Return JSON with an HTTP status other than 200 in Rocket - rest

I want my Rocket API to have a route like this:
#[post("create/thing", format = "application/json", data="<thing>")]
When the client sends { "name": "mything" }, everything should be alright and I know how to do that, but when it sends { "name": "foo" } it should respond with something like this:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"errors": [
{
"status": "422",
"title": "Invalid thing name",
"detail": "The name for a thing must be at least 4 characters long."
}
]
}
How do I respond with a result like a JSON object and a HTTP status code different than 200 in Rocket?
This is what I tried so far:
impl FromRequest for my Thing type. This lets me choose a status code as I can write my own from_request function, but I can't return anything else.
Registering an error catcher like in this example, but this way I only can react to one HTTP status code without context. I have too many failure modes to reserve one HTTP status code for each.

With #hellow's help, I figured it out. The solution is to implement the Responder trait for a new struct ApiResponse, which contains a status code as well the Json. This way I can do exactly what I wanted:
#[post("/create/thing", format = "application/json", data = "<thing>")]
fn put(thing: Json<Thing>) -> ApiResponse {
let thing: Thing = thing.into_inner();
match thing.name.len() {
0...3 => ApiResponse {
json: json!({"error": {"short": "Invalid Name", "long": "A thing must have a name that is at least 3 characters long"}}),
status: Status::UnprocessableEntity,
},
_ => ApiResponse {
json: json!({"status": "success"}),
status: Status::Ok,
},
}
}
Here is the full code:
#![feature(proc_macro_hygiene)]
#![feature(decl_macro)]
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_contrib;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response;
use rocket::response::{Responder, Response};
use rocket_contrib::json::{Json, JsonValue};
#[derive(Serialize, Deserialize, Debug)]
pub struct Thing {
pub name: String,
}
#[derive(Debug)]
struct ApiResponse {
json: JsonValue,
status: Status,
}
impl<'r> Responder<'r> for ApiResponse {
fn respond_to(self, req: &Request) -> response::Result<'r> {
Response::build_from(self.json.respond_to(&req).unwrap())
.status(self.status)
.header(ContentType::JSON)
.ok()
}
}
#[post("/create/thing", format = "application/json", data = "<thing>")]
fn put(thing: Json<Thing>) -> ApiResponse {
let thing: Thing = thing.into_inner();
match thing.name.len() {
0...3 => ApiResponse {
json: json!({"error": {"short": "Invalid Name", "long": "A thing must have a name that is at least 3 characters long"}}),
status: Status::UnprocessableEntity,
},
_ => ApiResponse {
json: json!({"status": "success"}),
status: Status::Ok,
},
}
}
fn main() {
rocket::ignite().mount("/", routes![put]).launch();
}

You need to build a response. Take a look at the ResponseBuilder. Your response might look something like this.
use std::io::Cursor;
use rocket::response::Response;
use rocket::http::{Status, ContentType};
let response = Response::build()
.status(Status::UnprocessableEntity)
.header(ContentType::Json)
.sized_body(Cursor::new("Your json body"))
.finalize();

Related

Accessing Google API data from within 3 async callbacks and a function in SwiftUI

I know this question is asked a lot, but I can't figure out how to apply any answers to my program. Sorry in advance this async stuff makes absolutely zero sense to me.
Basically, I have a button in SwiftUI that, when pressed, calls a function that makes two API calls to Google Sheets using Alamofire and GoogleSignIn.
Button("Search") {
if fullName != "" {
print(SheetsAPI.nameSearch(name: fullName, user: vm.getUser()) ?? "Error")
}
}
This function should return the values of some cells on success or nil on an error. However, it only ever prints out "Error". Here is the function code.
static func nameSearch<S: StringProtocol>(name: S, advisory: S = "", user: GIDGoogleUser?) -> [String]? {
let name = String(name)
let advisory = String(advisory)
let writeRange = "'App Control'!A2:C2"
let readRange = "'App Control'!A4:V4"
// This function can only ever run when user is logged in, ! should be fine?
let user = user!
let parameters: [String: Any] = [
"range": writeRange,
"values": [
[
name,
nil,
advisory
]
]
]
// What I want to be returned
var data: [String]?
// Google Identity said use this wrapper so that the OAuth tokens refresh
user.authentication.do { authentication, error in
guard error == nil else { return }
guard let authentication = authentication else { return }
// Get the access token to attach it to a REST or gRPC request.
let token = authentication.accessToken
let headers: HTTPHeaders = ["Authorization": "Bearer \(token)"]
AF.request("url", method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString { response in
switch response.result {
case .success:
// I assume there is a better way to make two API calls...
AF.request("anotherURL", headers: headers).responseDecodable(of: NameResponseModel.self) { response2 in
switch response2.result {
case .success:
guard let responseData = response2.value else { return }
data = responseData.values[0]
// print(responseData.values[0]) works fine
case .failure:
print(response2.error ?? "Unknown error.")
data = nil
}
}
case .failure:
print(response.error ?? "Unknown error.")
data = nil
}
}
}
// Always returns nil, "Unknown error." never printed
return data
}
The model struct for my second AF request:
struct NameResponseModel: Decodable { let values: [[String]] }
An example API response for the second AF request:
{
"range": "'App Control'!A4:V4",
"majorDimension": "ROWS",
"values": [
[
"Bob Jones",
"A1234",
"Cathy Jones",
"1234 N. Street St. City, State 12345"
]
]
}
I saw stuff about your own callback function as a function parameter (or something along those lines) to handle this, but I was completely lost. I also looked at Swift async/await, but I don't know how that works with callback functions. Xcode had the option to refactor user.authentication.do { authentication, error in to let authentication = try await user.authentication.do(), but it threw a missing parameter error (the closure it previously had).
EDIT: user.authentication.do also returns void--another reason the refactor didn't work (I think).
There is probably a much more elegant way to do all of this so excuse the possibly atrocious way I did it.
Here is the link to Google Identity Wrapper info.
Thanks in advance for your help.
Solved my own problem.
It appears (according to Apple's async/await intro video) that when you have an unsupported callback that you need to run asynchronously, you wrap it in something called a Continuation, which allows you to manually resume the function on the thread, whether throwing or returning.
So using that code allows you to run the Google Identity token refresh with async/await.
private static func auth(_ user: GIDGoogleUser) async throws -> GIDAuthentication? {
typealias AuthContinuation = CheckedContinuation<GIDAuthentication?, Error>
return try await withCheckedThrowingContinuation { (continuation: AuthContinuation) in
user.authentication.do { authentication, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: authentication)
}
}
}
}
static func search(user: GIDGoogleUser) async throws {
// some code
guard let authentication = try await auth(user) else { ... }
// some code
}
I then ran that before using Alamofire's built-in async/await functionality for each request (here's one).
let dataTask = AF.request(...).serializingDecodable(NameResponseModel.self)
let response = try await dataTask.value
return response.values[0]

Making a route both intermediate and final?

In building an app with PerfectHTTP I've come up against a problem with routing: Routes that are used as intermediate routes cannot also be used as final routes.
When set up as below, some URLs track correctly (/index.html in the example), while some URLs do not (/ in the example).
Is there some special sauce I'm missing here, or is what I'm trying to do simply impossible using Perfect?
import PerfectHTTP
import PerfectHTTPServer
// This does various housekeeping to set things common to all requests.
var routes = Routes() {
request, response in
Log.info(message: "\(request.method) \(request.uri)")
response
.setHeader(.contentType, value: "text/plain")
.next()
}
// For this handler, http://localhost:8181/index.html works,
// but http://localhost:8181/ does not
routes.add(method: .get, uris: ["/", "index.html"]) {
request, response in
response
.appendBody(string: "Hello, world!")
.completed()
}
HTTPServer.launch(.server(name: "localhost", port: 8181, routes: [routes]))
if you want set things common to all requests. use filter.
my routes is working
see more https://perfect.org/docs/filters.html
var routes = Routes()
routes.add(method: .get, uris: ["/", "index.html"]) {
request, response in
response
.appendBody(string: "Hello, world!")
.completed()
}
struct MyFilter: HTTPRequestFilter {
func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) {
print("request method: \(request.method)")
callback(.continue(request, response))
}
}
do {
let server = HTTPServer()
server.serverName = "testServer"
server.serverPort = 8181
server.addRoutes(routes)
server.setRequestFilters([(MyFilter(), HTTPFilterPriority.high)])
try server.start()
} catch {
fatalError("\(error)") // fatal error launching one of the servers
}

Decodable returning object

I have a decodable class:
struct AuthenticationResponse : Decodable {
var status: String
var error: Error
var access_token: String? = ""
var expires_in: Double? = 0
var token_type: String? = ""
var scope: String? = ""
var refresh_token: String? = "
}
struct Error : Decodable {
var desc: String
var code: String
}
In the Error class I have:
And to decode to this class, I have:
URLSession.shared.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in
if let jsonData = data{
let decoder = JSONDecoder()
print("hey")
print("response: \(String(data:jsonData, encoding:.utf8))")
completion(try! decoder.decode(AuthenticationResponse.self, from: jsonData))
}
}.resume()
As some of the responses I receive are (Success response):
{
“status”: “SUCCESS” “error”: null, "access_token":
"MWVmOWQxMDYwMjQyNDQ4NzQyNTdkZjQ3NmI4YmVjMGZjZGM5N2IyZmNkOTA1 N2M0NDUzODEwYjM5ZWQyNGNkZg",
"expires_in": 3600, "token_type": "bearer", "scope": null,
"refresh_token":
"ZGEwOGZiOWZhMzhhYjBmMzAyOGRmZTA5NjJhMjY2MTk3YzMyMmE1ZDlkNWI2N mJjYmIxMjNkMjE1NWFhNWY0Mg"
}
And then a failed response just contains an error object with desc and code in it.
What i am trying to achieve is a decodable class suitable for both scenarios (When a response is successful and failed) however im not sure how to achieve this. I'm aware i can make 2 separate decodable classes but this would make things messier as i'd have to determine if the response is an error and populate to return different classes.
Does anyone know how i should acheive this>
I will give it a try, but first we need to sort out what I consider a somewhat shoddy question. Since Error is the name of a (famous and widely used) protocol it should be renamed and since you want to be able to leave it empty in your AuthenticationResponse it must obviously be an optional there (bearing the question why it is in the Response at all, but I will leave this aside). This leaves us with the following:
struct AuthError : Decodable {
var desc: String
var code: String
}
struct AuthenticationResponse : Decodable {
var status: String
var error: AuthError?
var access_token: String? = ""
var expires_in: Double? = 0
var token_type: String? = ""
var scope: String? = ""
var refresh_token: String? = ""
}
Then we need some example data for the two relevant cases in question, I used:
let okData = """
{
"status": "SUCCESS",
"error": null,
"access_token":
"MWVmOWQxMDYwMjQyNDQ4NzQyNTdkZjQ3NmI4YmVjMGZjZGM5N2IyZmNkOTA1N2M0NDUzODEwYjM5ZWQyNGNkZg",
"expires_in": 3600,
"token_type": "bearer",
"scope": null,
"refresh_token":
"ZGEwOGZiOWZhMzhhYjBmMzAyOGRmZTA5NjJhMjY2MTk3YzMyMmE1ZDlkNWI2NmJjYmIxMjNkMjE1NWFhNWY0Mg"
}
""".data(using: .utf8)!
let errData = """
{
"desc": "username or password incorrect",
"code": "404"
}
""".data(using: .utf8)!
Now we can define a single enum return type which allows for all our cases:
enum AuthResult {
case ok(response: AuthenticationResponse)
case authError(error: AuthError)
case parseError(description: String)
case fatal
}
which finally allows us to write our parse function for the received authentication data:
func parse(_ jsonData:Data) -> AuthResult {
let decoder = JSONDecoder()
do {
let authRes = try decoder.decode(AuthenticationResponse.self, from: jsonData)
return .ok(response: authRes)
} catch {
do {
let errRes = try decoder.decode(AuthError.self, from: jsonData)
return .authError(error: errRes)
} catch let errDecode {
return .parseError(description: errDecode.localizedDescription)
}
}
}
All this in a Playground will permit usage as in
switch parse(okData) {
case let .ok(response):
print(response)
case let .authError(error):
print(error)
case let .parseError(description):
print("You threw some garbage at me and I was only able to \(description)")
default:
print("don't know what to do here")
}
That is still elegant compared to the mess you would make in most other languages, but the call is still out on wether it would not make more sense to just define AuthenticationResponse as the (regular) return type of the parse function and provide the rest by throwing some enum (conforming to Error) and some suitable payload.
Coming (mainly) from Java I still shun from using exceptions as "somewhat" regular control flow (as in a "regular" login failure), but given Swifts much more reasonable approach to exceptions this might have to be reconsidered.
Anyways, this leaves you with a function to parse either case of your services replies and a decent way to handle them in a "uniform" manner. As you might not be able to modify the behaviour of the service handling your request this might be the only viable option. However, if you are able to modify the service you should strive for a "uniform" reply that would be parseable by a single call to JSONDecoder.decode. You would still have to interpret the optionals (as you should in the above example, since they are still a pain to work with, even given Swifts brilliant compiler support forcing you to "do the right thing"), but it would make your parsing less error prone.

Post JSON array with IONIC 2/3

I'm trying to send a JSON array as parameter using HTTP native with IONIC 3. This is the request:
HTTPResponse = await this.http.post(url,
body, headers);
This is the complete code. I'm using HTTP from from '#ionic-native/http'. This is the complete code
import { HTTP, HTTPResponse } from '#ionic-native/http';
[...]
let body = '[{"userId": 1, "timestamp":"2018-10-12T18:00:00.000+02",
"audit":"MENUTEST"},{"userId": 1, "timestamp":"2018-10-
12T18:00:00.000+02", "audit":"MENUTEST"},{"userId": 1,
"timestamp":"2018-10-12T18:00:00.000+02", "audit":"MENUTEST"}]';
//Auth header
let headers = { Authorization: `Bearer ${token}`};
let httpResponse: HTTPResponse = await
this.http.post(URL_data, JSON.parse(body), headers);
I'm using similar code with other request and all is going fine, the only difference is that the request that is going have a simple parameters in JSON body. This is the request that is going fine:
import { HTTP, HTTPResponse } from '#ionic-native/http';
[...]
let body = '{ "username": "usuario.prueba1", "password": "' +
Md5.hashStr('prueba') + '", "customerCode": "1234DEV" }';
let httpResponse: HTTPResponse = await
this.http.post(URL_login, JSON.parse(body), headers);
Updated answer:
If you have a look at the source-file of the android implementation of the post action you will see that a JsonObject is expected as parameter, this is why passing an array will result in a JSON error. You have to wrap the array in an object like that to make this work:
let body = {
myArray: [
{
userId: 1,
timestamp: '2018-10-12T18:00:00.000+02',
audit: 'MENUTEST',
},
{
userId: 1,
timestamp: '2018-10-12T18:00:00.000+02',
audit: 'MENUTEST',
},
{
userId: 1,
timestamp: '2018-10-12T18:00:00.000+02',
audit: 'MENUTEST',
},
],
};
let headers = { Authorization: `Bearer ${token}` };
let httpResponse: HTTPResponse = await this.http.post(
URL_data,
body,
headers,
);
Old answer:
I guess your Content-Type is application/x-www-form-urlencoded so you need to add a name for you parameter which is separated from the value by an equal sign (=):
let payload = [{ "userId": 3, "timestamp": "2018-10-12T18:00:00.000+02", "audit": "Mensaje 3" }, { "userId": 4, "timestamp": "2018-10-13T18:00:00.000+02", "audit": "Mensaje 4" }];
let body = `MyArray=${payload}`;
The relevant part of the specification can be found here. Point 2. says:
The control names/values are listed in the order they appear in the document. The name is separated from the value by `=' and name/value pairs are separated from each other by `&'.
To support deep structure, you should change serializer.
Try set http.setDataSerializer("json"); And send data as usual: http.post(url, body, {})
Check this answer:
https://stackoverflow.com/a/49589124/6097876

Getting JSON array with Alamofire + SwiftyJSON

I'm really new to Swift, sorry if this is a dumb question... there seem to be many questions about this but none of them use the latest version of Alamofire
Alamofire.request(.GET, url)
.responseJSON { response in
let json = JSON(response.data!)
debugPrint(json)
self.delegate?.didReceiveAPIResults(json)
}
And the delegate's didReceiveAPIResults method
func didReceiveAPIResults(results: JSON) {
dispatch_async(dispatch_get_main_queue(), {
self.tableData = results["items"].arrayObject!
self.appsTableView!.reloadData()
})
}
Here's the JSON response:
{
"items": [
{
"id": 1,
"name": "Sample 1"
},
{
"id": 2,
"name": "Sample 2"
}
]
}
I expect the debugPrint to print something similar to that JSON, but instead it just prints unknown
If I debugPrint response.data by itself, it appears to be encoded...
Optional(<7b226461 7461223a 5b7b2269 64223a36 2c226e61 6d6522......
Then my results["items"].arrayObject! line has this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Rather than grabbing response.data, I'd suggest grabbing response.result.value. When you do responseJSON, Alamofire does the JSON parsing for you, and you should feel free to just grab this parsed object.
Alamofire.request(.GET, url)
.responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
self.delegate?.didReceiveAPIResults(json)
}
}