How to pass hash object in gRPC ruby client - ruby

I want to make a Ruby client.
My proto file looks like:
syntax = "proto3";
import "google/protobuf/struct.proto";
import "google/protobuf/duration.proto";
import "discovery/protobuf/shared/v1beta1/metadata.proto";
option java_multiple_files = true;
option ruby_package = "v1beta1";
message Request {
...
google.protobuf.Struct test = 12;
}
In my service_pb.rb file I have:
add_message 'request' do
...
optional :test, :message, 12, 'google.protobuf.Struct'
end
Now I am trying to pass the request params in my client.rb:
params = {xyz: "abc", test: { bar: "296" }}
stub = Message::Stub.new('localhost:9999', :this_channel_is_insecure)
msg = Request.new(params)
while running this I am getting:
ArgumentError: Unknown field name 'bar' in initialization map entry.
I need to pass a Hash object in request params.

One solution is to use Google::Protobuf::Struct.from_hash
Example code:
require 'google/protobuf/well_known_types'
Google::Protobuf::Struct.from_hash({'k' => 123})
=> <Google::Protobuf::Struct: fields: {"k"=><Google::Protobuf::Value: null_value: :NULL_VALUE, number_value: 123.0, string_value: "", bool_value: false, struct_value: nil, list_value: nil>}>

Related

Jenkins read json file with multiple list value jsonsurper or readjson

I want to be able ro read json format based on parameter value selected in choice. e.g If dev is selected, it should select (dev1,dev2,dev3) and loop through each selected in json through the node. what is important now is to get the value in json to a file and then I call call it from file into the node
error:
groovy.lang.MissingMethodException: No signature of method: net.sf.json.JSONObject.$() is applicable for argument types: (org.jenkinsci.plugins.workflow.cps.CpsClosure2) values: [org.jenkinsci.plugins.workflow.cps.CpsClosure2#7e1eb88f]
Possible solutions: is(java.lang.Object), any(), get(java.lang.Object), get(java.lang.String), has(java.lang.String), opt(java.lang.String)
script in pipeline
#!/usr/bin/env groovy
node{
properties([
parameters([
choice(
name: 'environment',
choices: ['','Dev', 'Stage', 'devdb','PreProd','Prod' ],
description: 'environment to choose'
),
])
])
node () {
def myJson = '''{
"Dev": [
"Dev1",
"Dev2",
"Dev3"
],
"Stage": [
"Stage1",
"Stage2"
],
"PreProd": [
"Preprod1"
],
"Prod": [
"Prod1",
"Prod2"
]
}''';
def myObject = readJSON text: myJson;
echo myObject.${params.environment};
// put the list of the node in a file or in a list to loop
}
Using Pipeline Utility Steps, it can be easier:
Reading from string:
def obj = readJSON text: myjson
Or reading from file:
def obj = readJSON file: 'myjsonfile.json'
Now you can get the element and iterate the list:
def list = obj[params.environment]
list.each { elem ->
echo "Item: ${elem}"
}
Reference: https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#readjson-read-json-from-files-in-the-workspace
Let me make it simple.
To read the json file you need to download it from git or wherever you stored it. Let's assume git in this case. Once the json file is downloaded then you want to access the content of json file in your code. Which can be done by this code.
import groovy.json.JsonSlurperClassic
def downloadConfigFile(gitProjectURL, jsonnFileBranch) {
// Variables
def defaultBranch = jsonnFileBranch
def gitlabAdminCredentials = 'admin'
def poll = false
def jenkinsFilePath = 'jenkins.json'
// Git checkout
git branch: defaultBranch, credentialsId: gitlabAdminCredentials, poll: poll, url: gitProjectURL
// Check if file existed or not
def jenkinsFile = fileExists(jenkinsFilePath)
if (jenkinsFile) {
def jsonStream = readFile(jenkinsFilePath)
JsonSlurperClassic slurper = new JsonSlurperClassic()
def parsedJson = slurper.parseText(jsonStream)
return parsedJson
} else {
return [:]
}
}
Now we have the entire json file parsed using above function.
Now you can call the function and read the value in a global variable.
stage('Download Config') {
jsonConfigData = downloadConfigFile(gitProjectURL, jsonnFileBranch)
if (jsonConfigData.isEmpty()) {
error err_jenkins_file_does_not_exists
} else {
println("jsonConfigData : ${jsonConfigData}")
}
Now you can access the value of json file or say variable like this.
def projectName = jsonConfigData.containsKey('project_name') == true ? jsonConfigData['project_name'] : ''
You can access any thing if its child node in similar way. I hope it helps you.

How to get image classification prediction from GCP AIPlatform in ruby?

I'm new with ruby and I want to use GCP AIPlatform but I'm struggeling with the payload.
So far, I have :
client = ::Google::Cloud::AIPlatform::V1::PredictionService::Client.new do |config|
config.endpoint = "#{location}-aiplatform.googleapis.com"
end
img = File.open(imgPath, 'rb') do |img|
'data:image/png;base64,' + Base64.strict_encode64(img.read)
end
instance = Instance.new(:content => img)
request = Google::Cloud::AIPlatform::V1::PredictRequest.new(
endpoint: "projects/#{project}/locations/#{location}/endpoints/#{endpoint}",
instances: [instance]
)
result = client.predict request
p result
Here is my proto
message Instance {
required bytes content = 1;
};
But I have the following error : Invalid type Instance to assign to submessage field 'instances'
I read the documentation but for ruby SDK it's a bit light.
The parameters are OK, the JS example here : https://github.com/googleapis/nodejs-ai-platform/blob/main/samples/predict-image-object-detection.js is working with those parameters
What am I doing wrong ?
I managed it
client = Google::Cloud::AIPlatform::V1::PredictionService::Client.new do |config|
config.endpoint = "#{location}-aiplatform.googleapis.com"
end
img = File.open(imgPath, 'rb') do |img|
Base64.strict_encode64(img.read)
end
instance = Google::Protobuf::Value.new(:struct_value => {:fields => {
:content => {:string_value => img}
}})
endpoint = "projects/#{project}/locations/#{location}/endpoints/#{endpoint}"
request = Google::Cloud::AIPlatform::V1::PredictRequest.new(
endpoint: endpoint,
instances: [instance]
)
result = client.predict request
p result
The use of the Google::Protobuf::Value looks ugly to me but it works

Ruby Strong Params: NoMethodError undefined method permit for Integer

My controller:
class V1::SendContractController < V1::BaseController
def create
byebug
bride_membership = Wedding.find(send_params[:weddingId]).bride_memberships[0]
SendBrideContractJob.perform_now(bride_membership, send_params[:contractId])
render json: { enqueuedDelivery: true }, status: :ok
end
private
def send_params
params
.require(:weddingId)
.permit(:contractId)
end
end
My params
Parameters: {"weddingId"=>4, "contractId"=>20, "send_contract"=>{"weddingId"=>4, "contractId"=>20}}
The error
NoMethodError (undefined method `permit' for 4:Integer):
But then when I byebug it I get what I want!
(byebug) params
<ActionController::Parameters {"weddingId"=>4, "contractId"=>20, "controller"=>"v1/send_contract", "action"=>"create", "send_contract"=>{"weddingId"=>4, "contractId"=>20}} permitted: false>
(byebug) params[:weddingId]
4
And I'm using axios with an interceptor to take care of formatting issues:
axios.interceptors.request.use((config) => {
if(config.url !== "/authentications") {
config.paramsSerializer = params => {
// Qs is already included in the Axios package
return qs.stringify(params, {
arrayFormat: "brackets",
encode: false
});
};
axios.defaults.headers.common['Authorization'] = `Bearer ${store.state.token.token}`
config.headers.common['Authorization']= `Bearer ${store.state.token.token}`
axios.defaults.headers.common['Accept'] = 'application/vnd.bella.v1+json'
config.headers.common['Accept'] = 'application/vnd.bella.v1+json'
return config
}
return config
})
I believe that require gives you the object at the key you provide to do further permit and / or require calls.
Perhaps you could try (not tested):
params.require(:weddingId)
params.permit(:weddingId, :contractId)
Edit: there's this too: Multiple require & permit strong parameters rails 4
Refer to this documentation and question.The require ensures that a parameter is present. If it's present, returns the parameter at the given key, otherwise raises an ActionController::ParameterMissing error.
p = { "weddingId"=>4, "contractId"=>20 }
ActionController::Parameters.new(p).require(:weddingId)
# 4
p = { "weddingId"=>nil, "contractId"=>20 }
ActionController::Parameters.new(p).require(:weddingId)
# ActionController::ParameterMissing: param is missing or the value is empty: weddingId
If you want to make sure :weddingId is present:
def contract_params
params.require(:weddingId)
params.permit(:contractId, :weddingId)
end
BTW, SendContractController is better called ContractsController.

PayPal express ActiveMerchant gateway not working

According to this, the ActiveMerchant PayPal Express Gateway is initialized like this:
paypal_options = {
login: "API_USERNAME_HERE",
password: "API_PASSWORD_HERE",
signature: "API_SIGNATURE_HERE"
}
::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options)
I'm definitely supplying a signature, yet I'm getting this error:
An API Certificate or API Signature is required to make requests to PayPal
The PayPal initializer looks like this (found here):
def initialize(options = {})
requires!(options, :login, :password)
headers = {'X-PP-AUTHORIZATION' => options.delete(:auth_signature), 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} if options[:auth_signature]
options = {
:pem => pem_file,
:signature => signature,
:headers => headers || {}
}.update(options)
if options[:pem].blank? && options[:signature].blank?
raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal"
end
super(options)
end
I don't understand what this initializer is doing with the signature and why it's not accepting it as per the example.
Here are the options I'm passing, which I've put to STDOUT:
{
"password" =>"***************",
"signature" =>"AVtrAKGQXoUNJFduUU0pn1dewq80AK9KYWenyFwYcduz8elS85B8T0Wc",
"allow_guest_checkout" =>true,
"login" =>"********************",
"test" =>true
}
Can someone help me with this please?
Note that I'm using this in JRuby, but I don't think that makes any difference in this case.
EDIT after #PiersC's comments:
I hardcoded this instead of taking them as params from Java and it worked:
options = {
login: "*************",
password: "*****************",
signature: "AVtrAKGQXoUNJFduUU0pn1dewq80AK9KYWenyFwYcduz8elS85B8T0Wc"
}
However this has led to another question. I've been converting the Java maps to Ruby hashes like this:
def self.convert_hash(map)
hsh = {}
map.each {|key, value| hsh[key] = value}
hsh.with_indifferent_access
end
And this has worked on all other gateways. How do I convert the Java map correctly to the options hash in Ruby?
Your option keys are strings but should be symbols, eg. { password: '***', ... } ActiveSupport::HashWithInvalidAccess hides (obscures?) the difference between symbol keys and string keys, but if you are using a regular Hash then { 'signature' => signature } is not the same as { signature: signature }.

Acumatica: How to create new customer in ruby?

I need to create Customer using SOAP API in ruby (we want to consume Acumatica api from Ruby on Rails project).
Currently my code using Savon gem looks like this:
client = Savon.client(wsdl: 'wsdl.wsdl') # sample wsdl path
response = client.call :login, message: { name: '', password: '' }
auth_cookies = response.http.cookies
class ServiceRequest
def to_s
builder = Builder::XmlMarkup.new
builder.instruct!(:xml, encoding: 'UTF-8')
# ... problem is here, I don't know how XML request should look like
builder
end
end
p client.call :submit, message: ServiceRequest.new, cookies: auth_cookies
Problem is that, I don't know how XML request should look like.
C# requests looks like this (just piece of sample from docs):
PO302000result = context.PO302000Submit(
new Command[]
{ new Value { Value = "PORE000079", LinkedCommand =
PO302000.DocumentSummary.ReceiptNbr},
new Value { Value = "OK", LinkedCommand =
PO302000.AddPurchaseOrderLine.ServiceCommands.DialogAnswer,
PO302000.Actions.AddPOOrderLine, new Key { Value = "='PORG000084'", FieldName = Commit = true },
PO302000.AddPurchaseOrderLine.OrderNbr.FieldName, ObjectName =
PO302000.AddPurchaseOrderLine.OrderNbr.ObjectName },
new Key { Value = "='CPU00004'", FieldName =
PO302000.AddPurchaseOrderLine.InventoryID.FieldName, ObjectName =
PO302000.AddPurchaseOrderLine.InventoryID.ObjectName },
new Value{ Value = "True", LinkedCommand =
PO302000.AddPurchaseOrderLine.Selected, Commit = true },
PO302000.Actions.AddPOOrderLine2
new Key{ Value = "='CPU00004'", FieldName =
PO302000.DocumentDetails_.InventoryID.FieldName, ObjectName =
PO302000.DocumentDetails_.InventoryID.ObjectName},
new Value{ Value = "1.00", LinkedCommand =
PO302000.DocumentDetails_.ReceiptQty, Commit = true},
// the next part of code is needed if you use Serial items
PO302000.BinLotSerialNumbers.ServiceCommands.NewRow,
new Value { Value = "R01", LinkedCommand =
PO302000.BinLotSerialNumbers.Location },
PO302000.Actions.Save
} );
But I don't know what kind of XML this code produce. It looks like we have Commands array with Values and then action name. But what XML does this kind of code renders? Maybe some C# or Java folks can copy me xml requests samples that are rendered by that kind of code?
Thank you a lot.
Basically it's a bad idea to generate XML SOAP package manually, you should have some wrapper on your side, which have to simplify your code.
Anyway, the C# code below + XML SOAP request
Content[] result = context.Submit(
new Command[]
{
new Value { Value = "PORE000079", LinkedCommand = PO302000.DocumentSummary.ReceiptNbr}
,new Value { Value = "OK", LinkedCommand = PO302000.AddPurchaseOrderLine.ServiceCommands.DialogAnswer, Commit = true }
,PO302000.Actions.AddPOOrderLine
,new Key { Value = "='PORG000077'", FieldName = PO302000.AddPurchaseOrderLine.OrderNbr.FieldName, ObjectName = PO302000.AddPurchaseOrderLine.OrderNbr.ObjectName }
,new Key { Value = "='CPU00004'", FieldName = PO302000.AddPurchaseOrderLine.InventoryID.FieldName, ObjectName = PO302000.AddPurchaseOrderLine.InventoryID.ObjectName }
,new Value{ Value = "True", LinkedCommand = PO302000.AddPurchaseOrderLine.Selected, Commit = true }
,PO302000.Actions.AddPOOrderLine2
,new Key{ Value = "='CPU00004'", FieldName = PO302000.DocumentDetails.InventoryID.FieldName, ObjectName = PO302000.DocumentDetails.InventoryID.ObjectName}
,new Value{ Value = "1.00", LinkedCommand = PO302000.DocumentDetails.ReceiptQty, Commit = true}
// the next part of code is needed if you use Serial items
,PO302000.BinLotSerialNumbers.ServiceCommands.NewRow
,new Value { Value = "R01", LinkedCommand = PO302000.BinLotSerialNumbers.Location }
,new Value { Value = "1.00", LinkedCommand = PO302000.BinLotSerialNumbers.Quantity, Commit = true }
,new Value { Value = "25.00", LinkedCommand = PO302000.DocumentDetails.UnitCost, Commit = true }
,new Key { Value = "='CPU00004'", FieldName = PO302000.DocumentDetails.InventoryID.FieldName, ObjectName = PO302000.DocumentDetails.InventoryID.ObjectName }
,new Value { Value = "0.00", LinkedCommand = PO302000.DocumentDetails.ReceiptQty, Commit = true }
,PO302000.Actions.Save
}
);
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><Submit xmlns="http://www.acumatica.com/typed/"><commands><Command xsi:type="Value"><Value>PORE000079</Value><LinkedCommand xsi:type="Field"><FieldName>ReceiptNbr</FieldName><ObjectName>Document</ObjectName><Value>ReceiptNbr</Value><Commit>true</Commit><LinkedCommand xsi:type="Action"><FieldName>cancel</FieldName><ObjectName>Document</ObjectName><LinkedCommand xsi:type="Key"><FieldName>ReceiptNbr</FieldName><ObjectName>Document</ObjectName><Value>=[Document.ReceiptNbr]</Value><LinkedCommand xsi:type="Key"><FieldName>ReceiptType</FieldName><ObjectName>Document</ObjectName><Value>=[Document.ReceiptType]</Value></LinkedCommand></LinkedCommand></LinkedCommand></LinkedCommand></Command><Command xsi:type="Value"><Value>OK</Value><Commit>true</Commit><LinkedCommand xsi:type="Answer"><ObjectName>poLinesSelection</ObjectName><Value>='Yes'</Value></LinkedCommand></Command><Command xsi:type="Action"><FieldName>AddPOOrderLine</FieldName><ObjectName>Document</ObjectName><Commit>true</Commit></Command><Command xsi:type="Key"><FieldName>OrderNbr</FieldName><ObjectName>poLinesSelection</ObjectName><Value>='PORG000077'</Value></Command><Command xsi:type="Key"><FieldName>InventoryID</FieldName><ObjectName>poLinesSelection</ObjectName><Value>='CPU00004'</Value></Command><Command xsi:type="Value"><Value>True</Value><Commit>true</Commit><LinkedCommand xsi:type="Field"><FieldName>Selected</FieldName><ObjectName>poLinesSelection</ObjectName><Value>Selected</Value><Commit>true</Commit></LinkedCommand></Command><Command xsi:type="Action"><FieldName>AddPOOrderLine2</FieldName><ObjectName>Document</ObjectName><Commit>true</Commit></Command><Command xsi:type="Key"><FieldName>InventoryID</FieldName><ObjectName>transactions</ObjectName><Value>='CPU00004'</Value></Command><Command xsi:type="Value"><Value>1.00</Value><Commit>true</Commit><LinkedCommand xsi:type="Field"><FieldName>ReceiptQty</FieldName><ObjectName>transactions</ObjectName><Value>ReceiptQty</Value><Commit>true</Commit></LinkedCommand></Command><Command xsi:type="NewRow"><ObjectName>splits</ObjectName></Command><Command xsi:type="Value"><Value>R01</Value><LinkedCommand xsi:type="Field"><FieldName>LocationID</FieldName><ObjectName>splits</ObjectName><Value>Location</Value></LinkedCommand></Command><Command xsi:type="Value"><Value>1.00</Value><Commit>true</Commit><LinkedCommand xsi:type="Field"><FieldName>Qty</FieldName><ObjectName>splits</ObjectName><Value>Quantity</Value></LinkedCommand></Command><Command xsi:type="Value"><Value>25.00</Value><Commit>true</Commit><LinkedCommand xsi:type="Field"><FieldName>CuryUnitCost</FieldName><ObjectName>transactions</ObjectName><Value>UnitCost</Value></LinkedCommand></Command><Command xsi:type="Key"><FieldName>InventoryID</FieldName><ObjectName>transactions</ObjectName><Value>='CPU00004'</Value></Command><Command xsi:type="Value"><Value>0.00</Value><Commit>true</Commit><LinkedCommand xsi:type="Field"><FieldName>ReceiptQty</FieldName><ObjectName>transactions</ObjectName><Value>ReceiptQty</Value><Commit>true</Commit></LinkedCommand></Command><Command xsi:type="Action"><FieldName>Save</FieldName><ObjectName>Document</ObjectName></Command></commands></Submit></soap:Body></soap:Envelope>
So in the end what I did:
gem install 'mumboe-soap4r' # not soap4r,
# because soap4r is old and bugged with newer rubies
Then I ran
wsdl2ruby.rb --wsdl customer.wsdl --type client
Where wsdl2ruby.rb is installed together with mumboe-soap4r gem. Replace customer.wsdl with path to your wsdl, could be URL or file system path.
After running this command next files were created:
default.rb
defaultMappingRegistry.rb
defaultDriver.rb
ScreenClient.rb
Using those files you can write code similar to C# code or php code to interact with acumatica API:
require_relative 'defaultDriver'
require 'soap/wsdlDriver'
# this is my helper method to make life easier
def prepare_value(value, command, need_commit = false, ignore = false)
value_command = Value.new
value_command.value = value
value_command.linkedCommand = command
value_command.ignoreError = ignore unless ignore.nil?
value_command.commit = need_commit unless need_commit.nil?
value_command
end
soap_client = SOAP::WSDLDriverFactory.new('customer.wsdl').create_rpc_driver
soap_client.login(name: '', password: '').loginResult
screen = soap_client.getSchema(nil)
soap_client.clear(nil)
content = screen.getSchemaResult
# p schema
p customer = content.customerSummary.customerID
p customer_name = content.customerSummary.customerName
country = content.generalInfoMainAddress.country
customer_class = content.generalInfoFinancialSettings.customerClass
commands = ArrayOfCommand.new
commands << prepare_value('ABBA', customer_name)
commands << prepare_value('US', country)
commands << prepare_value('MERCHANT', customer_class)
commands << content.actions.insert
commands << customer.clone # to return
p commands
p soap_client.submit(commands)
Hope it will help someone.
Actually 'mumboe-soap4r' or 'soap2r' or 'soap4r' doesn't work with Acumatica soap API. They are too old and buggy.
In the end I am using Savon gem (version 2). I am creating message using XmlMarkup class. But how I know what XML should I create? In order to know this I am creating soap request in .net , then I see what right XML request looks like and only then I am creating soap request using Savon gem. Too much work, but I don't know better way for now. It works.
In order for Savon to work with Acumatica API I set next options:
client = Savon.client do
wsdl 'http://path/Soap/AR303000.asmx?wsdl'
log true
namespaces 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'
env_namespace 'soap'
namespace_identifier nil
element_form_default ''
end
Don't forget to pass auth cookies
response = client.call(:login, message: { name: '', password: '' })
auth_cookies = response.http.cookies
Build your customer creating xml, then do submit
m = build_create_submit(customer_name)
response = client.call(:submit, message: m, cookies: auth_cookies)
# get customer id of newly created customer
customer_id = response.try(:hash).try(:[], :envelope).
try(:[], :body).
try(:[], :submit_response).
try(:[], :submit_result).
try(:[], :content).
try(:[], :customer_summary).
try(:[], :customer_id).
try(:[], :value)

Resources