How do I create a Siesta transformer for an API endpoint that contains a JSON array? - ios

I'm building an app that needs to make a GET request to the API endpoint https://thecountedapi.com/api/counted using the Siesta framework. The endpoint returns a JSON array, just like an endpoint like https://api.github.com/users/ebelinski/repos, which is used in the Siesta example Github Browser. As a result, I'm trying to make my app use Siesta in the say way that one does. I create a service:
let API = Service(baseURL: "https://thecountedapi.com/api")
Then a transformer for my endpoint in application:didFinishLaunchingWithOptions:
API.configureTransformer("/counted") {
($0.content as JSON).arrayValue.map(Incident.init)
}
Where Incident is a struct with an initializer that takes in a JSON object.
Then in my view controller, I create a resource:
let resource = API.resource("/counted")
and in viewDidLoad:
resource.addObserver(self)
and in viewWillAppear:
resource.loadIfNeeded()
Then I have the following function in my VC to listen to changes:
func resourceChanged(resource: Resource, event: ResourceEvent) {
print(resource.jsonArray)
if let error = resource.latestError {
print(error.userMessage)
return
}
if let content: [Incident] = resource.typedContent() {
print("content exists")
incidents = content
}
print(incidents.count)
}
But when I run my app, I get mixed results. print(resource.jsonArray) just prints [], I have an error message Cannot parse server response, and if I set Siesta.enabledLogCategories = LogCategory.detailed, I can see the error mesage [Siesta:StateChanges] Siesta.Resource(https://thecountedapi.com/api/counted)[] received error: Error(userMessage: "Cannot parse server response", httpStatusCode: nil, entity: nil, cause: Optional(Siesta.Error.Cause.WrongTypeInTranformerPipeline(expectedType: "JSON", actualType: "__NSCFArray", transformer: Siesta.ResponseContentTransformer<SwiftyJSON.JSON, Swift.Array<TheCountedViewer.Incident….
If I comment out the whole transformer, I have some success in that print(resource.jsonArray) prints out the correct array from the endpoint. So my transformer must be wrong in some way, but I think I'm using basically the same transformer as in Github Browser:
service.configureTransformer("/users/*/repos") {
($0.content as JSON).arrayValue.map(Repository.init)
}
Am I missing something?

The key clue to your problem is buried in that (perhaps not ideally helfpul) log message:
Siesta.Error.Cause.WrongTypeInTranformerPipeline
expectedType: "JSON"
actualType: "__NSCFArray"
It’s saying that your transformer expected an input type of JSON, which makes sense — you said as much with ($0.content as JSON). However, it got the type __NSCFArray, which is the secret internal backing type for NSArray. In other words, it expected a SwiftyJSON value, but it got the raw output of NSJSONSerialization instead.
Why? The GithubBrowser project includes an NSDict/NSArray → SwiftyJSON transformer which it configures in the parsing stage. The model transformers in that project all depend on it.
To use SwiftyJSON in the same way in your project, you’ll need to include that transformer from the example project in yours:
private let SwiftyJSONTransformer =
ResponseContentTransformer
{ JSON($0.content as AnyObject) }
And then when setting up your service:
service.configure {
$0.config.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
}
(Note that you might want to create the ResponseContentTransformer with transformErrors: true if you are interested in errors.)
An alternative way to use SwiftyJSON, which is not quite as pretty but requires less setup, is to manually wrap things in JSON in each individual response transformer:
service.configureTransformer("/users/*/repos") {
JSON($0.content as AnyObject).arrayValue.map(Incident.init)
}

Related

Access session value in gatling checks

I use gatling to send data to an ActiveMQ. The payload is generated in a separate method. The response should also be validated. However, how can I access the session data within the checks
check(bodyString.is()) or simpleCheck(...)? I have also thought about storing the current payload in a separate global variable, but I don't know if this is the right approach. My code's setup looks like this at the moment:
val scn = scenario("Example ActiveMQ Scenario")
.exec(jms("Test").requestReply
.queue(...)
.textMessage{ session => val message = createPayload(); session.set("payload", payload); message}
.check(simpleCheck{message => customCheck(message, ?????? )})) //access stored payload value, alternative: check(bodystring.is(?????)
def customCheck(m: Message, string: String) = {
// check logic goes here
}
Disclaimer: providing example in Java as you don't seem to be a Scala developper, so Java would be a better fit for you (supported since Gatling 3.7).
The way you want to do things can't possibly work.
.textMessage(session -> {
String message = createPayload();
session.set("payload", payload);
return message;
}
)
As explained in the documentation, Session is immutable, so in a function that's supposed to return the payload, you can't also return a new Session.
What you would have to do it first store the payload in the session, then fetch it:
.exec(session -> session.set("payload", createPayload()))
...
.textMessage("#{payload}")
Regarding writing your check, simpleCheck doesn't have access to the Session. You have to use check(bodyString.is()) and pass a function to is, again as explained in the documentation.

Get Response from CREATE_STREAM

We upload a document from SAPUI5 to our SAP System using the CREATE_STREAM Method of the oData Service in ABAP. The creation of the document works fine.
What we would like to achieve is to get the response back to SAPUI5. Especially when there is an error during the creation of the document in the backend.
In Frontend we use the uploadSet Control.
...oUploadSet.uploadItem(oItem);
In the Backend we create a message with
...lo_message_container->add_message( iv_msg_type = /iwbep/cl_cos_logger=>error
iv_msg_number = '018'
iv_msg_id = lv_msg_id
iv_add_to_response_header = abap_true
)....
We can find the created message in the error protocol of our gateway server (/IWFND/ERROR_LOG). But how can this message be retrieved in SAPUI5 and used in the MessageManger Control?
We tried the onUploadCompleted Control but we can't find any response data there.
Can somebody explain how the response or a message header from the CREAT_STREAM method can be used in SAPUI5?
The "new" UploadSet control is kinda half-baked imo. The response will get lost in some internal method. This internal method will then trigger onUploadCompleted and you get nothing but useless information.
Lucky for us we can easily overwrite this internal stuff. UploadSet has an aggregation Uploader. We have to provide our own Uploader. Problem solved. Here is the line that needs to be modified.
sap.ui.define([
"sap/m/upload/Uploader",
...
], function (Uploader, ...) {
return Uploader.extend("my.custom.control.Uploader", {
uploadItem: function (oItem, aHeaders) {
// beginning of the method. take it from the official sources
oXhr.onreadystatechange = function () {
const oHandler = that._mRequestHandlers[oItem.getId()];
if (this.readyState === window.XMLHttpRequest.DONE && !oHandler.aborted) {
// we need to return the xhr object. it contains the response!
that.fireUploadCompleted({ item: oItem, xhr: oXhr });
}
};
// .. rest of the method
}
});
});
Use it like this
<mvc:View xmlns:custom="my.custom.control" ....>
<UploadSet items="....">
.....
<uploader>
<custom:Uploader uploadUrl="......"
uploadCompleted=".onUploadCompleted"
uploadStarted=".onUploadStarted" />
</uploader>
</UploadSet>
Edit: Your own uploader also means implementing your own event handlers (uploadAborted, uploadCompleted, uploadProgressed, uploadStarted). See the official documentation for more information about the events.

How to output any JSON sent to endpoint?

One of my Vapor app endpoints needs to be able to receive arbitrary JSON and output it to the logs. Once I have the logs, I can go back through and set up Codables structs and do the typical Vapor workflow.
In your route handler do print("\(req)") and you'll see the data
Doing this requires solving two problems that Vapor apps don't normally contend with:
Where to get the HTTP body (and how to decode it)?
How to return a future outside the context of the usual helpers?
The simplest solution I found gets the body from req.http.body.data, converts the data to JSON using JSONSerialization.jsonObject(with:options), and returns a Future using req.eventLoop.newSucceededFuture:
router.put("printanyjson") { req -> Future<HTTPStatus> in
if let data = req.http.body.data {
if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
print("\(json)")
}
}
return req.eventLoop.newSucceededFuture(result: .ok)
}
Note #1: This solution does not work if body is streaming. I'd like to see a solution that incorporates this idea.
Note #2: It's also possible to make recursive Codables that can decode any structure, allowing you to stay within the rails of typical Vapor usage. I'd like to see a solution that incorporates this idea.

How to send a speech to text request using google_speech1 in Rust?

I am trying to use google_speech1 for Rust, but the documentation provides incomplete examples, which makes it very hard for me, being both new at Rust and at using Google Speech Api, to figure out how to do send a speech to text request.
More specifically, I would like to be able to send a local audio file, indicate the source language and retrieve the transcription.
Here is the closest I could find in the official documentation(https://docs.rs/google-speech1/1.0.8+20181005/google_speech1/struct.SpeechRecognizeCall.html):
use speech1::RecognizeRequest;
// As the method needs a request, you would usually fill it with the desired information
// into the respective structure. Some of the parts shown here might not be applicable !
// Values shown here are possibly random and not representative !
let mut req = RecognizeRequest::default();
// You can configure optional parameters by calling the respective setters at will, and
// execute the final call using `doit()`.
// Values shown here are possibly random and not representative !
let result = hub.speech().recognize(req)
.doit();
UPDATE
Taking a step back, even simple examples provided on the website don't seem to run properly. Here is some sample very basic code:
pub mod speech_api_demo {
extern crate google_speech1 as speech1;
extern crate hyper;
extern crate hyper_rustls;
extern crate yup_oauth2 as oauth2;
use oauth2::{ApplicationSecret, Authenticator, DefaultAuthenticatorDelegate, MemoryStorage};
use speech1::Speech;
use speech1::{Error, Result};
use std::fs::File;
use std::io::Read;
#[derive(Deserialize, Serialize, Default)]
pub struct ConsoleApplicationSecret {
pub web: Option<ApplicationSecret>,
pub installed: Option<ApplicationSecret>,
}
pub fn speech_sample_demo() {
/*
Custom code to generate application secret
*/
let mut file =
File::open("C:\\Users\\YOURNAME\\.google-service-cli\\speech1-secret.json").unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
use serde_json as json;
let my_console_secret = json::from_str::<ConsoleApplicationSecret>(&data);
assert!(my_console_secret.is_ok());
let unwrappedConsoleSecret = my_console_secret.unwrap();
assert!(unwrappedConsoleSecret.installed.is_some() && unwrappedConsoleSecret.web.is_none());
let secret: ApplicationSecret = unwrappedConsoleSecret.installed.unwrap();
/*
Custom code to generate application secret - END
*/
// Instantiate the authenticator. It will choose a suitable authentication flow for you,
// unless you replace `None` with the desired Flow.
// Provide your own `AuthenticatorDelegate` to adjust the way it operates and get feedback about
// what's going on. You probably want to bring in your own `TokenStorage` to persist tokens and
// retrieve them from storage.
let auth = Authenticator::new(
&secret,
DefaultAuthenticatorDelegate,
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
<MemoryStorage as Default>::default(),
None,
);
let mut hub = Speech::new(
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
auth,
);
let result = hub.operations().get("name").doit();
match result {
Err(e) => match e {
// The Error enum provides details about what exactly happened.
// You can also just use its `Debug`, `Display` or `Error` traits
Error::HttpError(_)
| Error::MissingAPIKey
| Error::MissingToken(_)
| Error::Cancelled
| Error::UploadSizeLimitExceeded(_, _)
| Error::Failure(_)
| Error::BadRequest(_)
| Error::FieldClash(_)
| Error::JsonDecodeError(_, _) => (println!("{}", e)),
},
Ok(res) => println!("Success: {:?}", res),
}
}
}
Running this code (calling speech_sample_demo) gives the following error:
Token retrieval failed with error: Invalid Scope: 'no description
provided'
I also tried some very ugly code to force the scope into the request, but it did not make any difference. I am having a hard time understanding what this error means. Am I missing something in my request or is it something else getting in the way at the other end? Or maybe that api code library is just broken?
Please also note that client id and client secret provided by default don't work anymore, when I was using those it would say that account is deleted.
I then set up an OAuth 2.0 client and generated the json file which I copied over to default location and then started getting the error above. Maybe it is just me not setting Google Api account properly, but in any case would be great if someone else could try it out to see if I am the only one having those issues.
Once I get over running such a simple request, I have some more code ready to be tested that sends over an audio file, but for now it fails very early on in the process.
The error you get originates from here and means that the OAuth scope you used when generating your credentials file doesn't allow you to access the Google speech API. So the problem is not in your Rust code, but instead in the script you used to generate your OAuth access tokens.
Basically, this means that when you generated your OAuth json file, you requested access to the Google API in a general way, but you didn't say which specific APIs you meant to use. According to this document, you need to request access to the https://www.googleapis.com/auth/cloud-platform scope.
You are missing the flow param to Authenticator. This is how you get the access token. You create an Enum using FlowType.
example:
use oauth2::{ApplicationSecret, Authenticator, DefaultAuthenticatorDelegate, MemoryStorage,FlowType};
let Flo = FlowType::InstalledInteractive;
let auth = Authenticator::new(
&secret,
DefaultAuthenticatorDelegate,
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
<MemoryStorage as Default>::default(),
None,)
See here: https://docs.rs/yup-oauth2/1.0.3/yup_oauth2/enum.FlowType.html
Not exactly easy to figure out.
I made this work via service accounts by doing this
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_only()
.enable_http1()
.build();
let service_account_key: oauth2::ServiceAccountKey = oauth2::read_service_account_key(
&"PATH_TO_SERVICE_ACCOUNT.json".to_string(),
)
.await
.unwrap();
let auth = oauth2::ServiceAccountAuthenticator::builder(service_account_key)
.build()
.await
.unwrap();
let hub = Speech::new(hyper::Client::builder().build(https), auth);

Fetching data from a microservice to rest in grails

I have a rest and a microservice.In microservice i have a table and i want that table data to be fetched to rest and i have written the below way in a rest demoController.
def result = restBuilder().post("http://localhost:2222/api/microservice/fetchData"){
header 'authorization', 'fdgtertddfgfdgfffffff'
accept("application/json")
contentType("application/json")
json "{'empId':1,'ename':'test1'}"
}
But it throws an error "No signature of method: demoController.restBuilder() is applicable for argument types: () values: []".How should i fetch data from a microservice to rest?
You are calling a method named restBuilder() and that method does not exist. If you want that to work, you will need to implement that method and have it return something that can deal with a call to post(String, Closure).
You probably are intending to use the RestBuilder class. The particulars will depend on which version of Grails you are using but you probably want is something like this...
RestBuilder restBuilder = new RestBuilder()
restBuilder.post('http://localhost:2222/api/microservice/fetchData'){
header 'authorization', 'fdgtertddfgfdgfffffff'
accept 'application/json'
json {
empId = 1
name = 'test1'
}
}
You may need to add a dependency on grails-datastore-rest-client in your build.gradle.
compile "org.grails:grails-datastore-rest-client"
I hope that helps.

Resources