So I have an API where one of the entry points looks something like:
module SomeModule
module SomeOtherModule
class Users < SomeModule::SomeOtherModule::Base
helpers do
params :user do
requires :user, type: Hash do
requires :device_id, type: String, desc: "Device ID"
end
end
end
desc "Some description"
params do
use :user
end
put "/", route_name: :v1_put_user_path do
...
end
end
end
end
SomeModule::SomeOtherModule::Base inherits from Grape::API
My client is sending the params in the body - which look like:
{"user[device_id]":"xyz"}
Since this come like a string the I fail to recognise them.Therefore return:
{"error":"user is missing"}
This means that on my server I'm getting:
{"user[device_id]"=>"00999877",
"route_info"=>
...
When I'm expecting to see:
{"user"=>{"device_id"=>"00999877"},
"route_info"=>
...
Any ideas on how should I define my params?...or maybe someone has stumble upon this issue and can provide some solution
You have to define your params block like this:
module SomeModule
module SomeOtherModule
class Users < SomeModule::SomeOtherModule::Base
params do
group :user, type: Hash do
requires :device_id, type: String, desc: "Device ID"
end
end
put "/", route_name: :v1_put_user_path do
...
end
end
end
end
UPDATE
Explaining the solution...
If your client is sending a JSON like the code below, then you have to define a group block and define the type parameter as Hash. The same rule applies when your client sends a "form-field" like user[device_id].
{ user: { device_id: 1 } }
UPDATE 2
I've just noticed that your client is sending an incorrect JSON to your service. This JSON shouldn't be...
{"user[device_id]":"xyz"}
...because it's not what your API is expecting. Instead, it should send
{ user: { device_id: "xyz" } }.
Related
I have a GET call (/getTag) that has a variable 'name'.
One of my users created on with a & sign. And unfortunately the GET call is now failing because it looks like this.
/getTag?name=IS&me-1234
Unfortunately my server interprets it like this because of the & sign:
{ id: 'IS', 'me-1234': '' }
Anyone experienced this before and have a way to solve it?
You should use encodeURIComponent on a variable name before passing it to axios.
You can use params key in axios
axios.get('/getTag', {params: {name: 'Is&me123'}})
You should request like:
axios.get('https://your-site.com/getTag', { params: { name: 'IS&me-1234' } });
instead of:
axios.get('https://your-site.com/getTag?name=IS&me-1234');
I have a minimal (example) REST end-point test/people.cfc:
component
restpath = "test/people/"
rest = true
{
remote void function create(
required string first_name restargsource = "Form",
required string last_name restargsource = "Form"
)
httpmethod = "POST"
restpath = ""
produces = "application/json"
{
// Simulate adding person to database.
ArrayAppend(
Application.people,
{ "first_name" = first_name, "last_name" = last_name }
);
// Simulate getting people from database.
var people = Application.people;
restSetResponse( {
"status" = 201,
"content" = SerializeJSON( people )
} );
}
}
As noted here and in the ColdFusion documentation:
Note: ColdFusion ignores the function's return value and uses the response set using the RestSetResponse() function.
So the void return type for the function appears to be correct for the REST function.
Now, I know I can call it from a CFM page using:
httpService = new http(method = "POST", url = "https://localhost/rest/test/people");
httpService.addParam( name = "first_name", type = "formfield", value = "Alice" );
httpService.addParam( name = "last_name", type = "formfield", value = "Adams" );
result = httpService.send().getPrefix();
However, I would like to call the function without making a HTTP request.
Firstly, the REST CFCs do not appear to be accessible from within the REST directory. This can be solved simply by creating a mapping in the ColdFusion admin panel to the root path of the REST service.
I can then do:
<cfscript>
Application.people = [];
people = new restmapping.test.People();
people.create( "Alice", "Adams" );
WriteDump( application.people );
</cfscript>
This calls the function directly and the output shows it has added the person. However, the response from the REST function has disappeared into the aether. Does anyone know if it is possible to retrieve the response's HTTP status code and content (as a minimum - preferably all the HTTP headers)?
Update - Integration Testing Scenario:
This is one use-case (of several) where calling the REST end-point via a HTTP request has knock-on effects that can be mitigated by invoking the end-point directly as a method of a component.
<cfscript>
// Create an instance of the REST end-point component without
// calling it via HTTP request.
endPoint = new restfiles.test.TestRESTEndPoint();
transaction {
try {
// Call a method on the end-point without making a HTTP request.
endPoint.addValueToDatabase( 1, 'abcd' );
assert( getRESTStatusCode(), 201 );
assert( getRESTResponseText(), '{"id":1,"value":"abcd"}' );
// Call another method on the end-point without making a HTTP request.
endPoint.updateValueInDatabase( 1, 'dcba' );
assert( getRESTStatusCode(), 200 );
assert( getRESTResponseText(), '{"id":1,"value":"dcba"}' );
// Call a third method on the end-point without making a HTTP request.
endPoint.deleteValueInDatabase( 1 );
assert( getRESTStatusCode(), 204 );
assert( getRESTResponseText(), '' );
}
catch ( any e )
{
WriteDump( e );
}
finally
{
transaction action="rollback";
}
}
</cfscript>
Calling each REST function via a HTTP request will commit the data to the database after each request - cleaning up between tests where the data has been committed can get very complicated and often results in needing to flashback the database to a previous state (resulting in integration tests being unable to be run in parallel with any other tests and periods of unavailability during flashbacks). Being able to call the REST end-points without making lots of atomic HTTP requests and instead bundle them into a single transaction which can be rolled back means the testing can be performed in a single user's session.
So, how can I get the HTTP status code and response text which have been set by RestSetResponse() when I create an instance of the REST component and invoke the function representing the REST path directly (without using a HTTP request)?
#MT0,
The solution will* involve a few steps:
Change remote void function create to remote struct function create
Add var result = {"status" = 201, "content" = SerializeJSON( people )}
Change your restSetResponse(..) call to restSetResponse(result)
Add return result;
* The solution will not currently work, b/c ColdFusion ticket CF-3546046 was not fixed completely. I've asked Adobe to re-open it and also filed CF-4198298 to get this issue fixed, just in case CF-3546046 isn't re-opened. Please see my most recent comment on CF-3546046, and feel free to vote for either ticket. Once either is fixed completely, then the above-listed changes to your code will allow it to set the correct HTTP response when called via REST and to return the function's return variable when invoked directly. Note: you could also specify a headers struct w/in the result struct in step 2, if you also want to return headers when the function is invoked directly.
Thanks!,
-Aaron Neff
is there a way to return a value inside Action controller.
I have a method in my User model which returns the number of friends of a given user.
def nrOfFriends(current_user: Long): Int = {
DB.withConnection{ implicit connection =>
var nr: Int = SQL("select count(*) from friend where user_id_1=" + current_user + " or user_id_2=" + current_user).as(scalar[Int].single)
nr
}
}
In my controller, I just want to return the value from the model
def freunde() = IsAuthenticated { username => _ =>
User.findByUsername(username).map { user =>
var nr: Int = Friend.nrOfFriends(user.id.get)
Ok(""+nr)
}.getOrElse(Forbidden)
}
But in the way that is written, it will print "empty string " concatenated with the number
If I replace Ok(""+nr) with Ok(nr) I receive the following error:
"Cannot write an instance of Int to HTTP response. Try to define a Writeable[Int]"
I need for my action to return a value so that I can pass the value from the action to header.views.html inside the navbar something like that
#Freund.freunde Friends
if you want your response to just be the value of nr you can simply call nr.toString:
def freunde() = IsAuthenticated { username => _ =>
User.findByUsername(username).map { user =>
var nr: Int = Friend.nrOfFriends(user.id.get)
Ok(nr.toString)
}.getOrElse(Forbidden)
}
The error you're getting makes reference to the fact that Int doesn't have an implicit Writeable[Int] in scope. So play doesn't know how display an Int in an http response.
You can add make Int writeable by putting this in scope:
implicit val intWriteable = play.api.http.Writeable[Int](_.toString.getBytes, None)
Then you would be able to just say:
Ok(nr)
without error.
However, it sounds like you just want the result of nrOfFriends inside an unrelated template. If that's the case, you should be using an Action at all. Instead just call your model function inside the template where you need the data.
#User.nrOfFriends(user.id) Friends
Of course you would need to pass in the user to the template as well.
You didn't post a full sample of all the code involved in what you are trying to accomplish so I think this is the best I can do for now. Perhaps try posting the base template that your <a> is in.
An important point is that Actions are for production an HTTP response, and not just plain data internally to the application.
An action of a controller handles a request and generates a result to be sent to the client. In other words, an action returns a play.api.mvc.Result value, representing the HTTP response to send to the web client. In your example Ok constructs a 200 OK response. The body of the response must be one of the predefined types, including text/plain, json, and html. The number of a friends is an integer and is NOT an acceptable type of the body. Therefore, a simple way to address this problem is to convert it into a text/plain using .toString().
On the other hand, you can define a writer for Int that lets Play know how to convert an integer into a json format.
For more details, please take a look at this https://www.playframework.com/documentation/2.2.x/ScalaActions.
My code sample was
package require rest
set yweather(forecast) {
url http://weather.yahooapis.com/forecastrss
req_args { p: }
opt_args { u: }
}
rest::create_interface yweather
Output
% set res [yweather::forecast -p 94089]
channel {title {content {Yahoo! Weather - Sunnyvale, CA}} .........
But i am trying to view Response header, like Status codes, set-cookie information. I don't know how to view, some one please help to resolve this issue.
Thanks
Typically with handling REST, I'd just use the standard http package directly (or wrapped into a little class). That would let you use http::meta to get at the gory response details, and also let you control much more precisely what message gets sent in the first place (usually rather important!)
However, that's me (as I'm pretty au fait with REST and the http package). Let's dig into the rest package more carefully and get it to do what we want.
By close reading of the documentation, I see that the interface descriptor dictionary allows the keys pre_transform and post_transform, and that the http token is available in the calling context. Let's try with the post_transform…
package require rest
set yweather(forecast) {
url http://weather.yahooapis.com/forecastrss
req_args { p: }
opt_args { u: }
post_transform extract_metadata
}
rest::create_interface yweather
proc extract_metadata {response} {
upvar 1 token token
lappend response [http::meta $token]
return $response
}
Now, if you do:
set res [yweather::forecast -p 94089]
You should get the extra information you want (and far more probably!) in the meta field at the end.
We are looking at using the API Blueprint. There are cases where we would like one request to return a proper response and another request to return an 'error' response such as a 400 bad request so that other developers can work against the mock API on apiary.io with both types of responses and handle it in their applications.
I've created a completely arbitrary example below,
## Thing [/thing/{id}]
Gets a thing but the thing id must be a prime number!
+ Parameters
+ id (string) ... ID of the thing, a prime number!
+ Model (application/json)
The thing itself.
+ Body
{
"description": "It is green"
}
### Retrieve a Single Gist [GET]
+ Response 200
[Gist][]
Now somehow I would like to add in a response for /thing/40
+ Response 400
{ "error" : "Invalid request" }
But I am not sure how I would do this with the API Blueprint. This was achievable under the 'old' style on apiary.io but we'd like to move on to the new syntax
To document multiple responses simply add it after the Response 200 like so:
## Thing [/thing/{id}]
Gets a thing but the thing id must be a prime number!
+ Parameters
+ id (string) ... ID of the thing, a prime number!
+ Model (application/json)
The thing itself.
+ Body
{
"description": "It is green"
}
### Retrieve a Single Gist [GET]
+ Response 200
[Thing][]
+ Response 400 (application/json)
{ "error" : "Invalid request" }
Note there is currently no dedicated syntax to discuss the conditions (when this response i s returned). You can discuss it anyway you like it for example:
+ Response 400 (application/json)
This response is returned when no `Thing` for given `id` exists.
+ Body
If you are using Apiary mock, keep in mind that the first response listed is returned by default, unless you say otherwise using the prefer HTTP header.
You can use the templates and specify a specific use case after a generic response for your resource, see example:
Reservation [/reservation/{reservation_key}]
A Reservation object has the following attributes:
room_number
reserved_at - An ISO8601 date when the question was published.
booker_details - A Booker Object.
Parameters
reservation_key (required, text, 1) ... Reservation Key ash
View a Reservation Detail [GET]
Response 200 (application/json)
{
"room_number": 55,
"reserved_at": "2014-11-11T08:40:51.620Z",
"booker_details":
{
"name": "Jon",
"last_name": "Doe",
}
}
Reservation [/reservation/notarealreservation123]
Using a non existing reservation (pls use notarealreservation123 in the fake) returns not found