Finding a specific ID using Rest in Grails - rest

I'm exploring the RESTful domain capabilities in Grails 3.1.4 and am getting odd results. For example, I have this domain object (and no corresponding controller):
package resttest
import grails.rest.*
#Resource(readOnly = false, formats = ['json', 'xml'])
class Book {
String name
}
Querying all objects seems to work OK:
$ curl http://localhost:8080/book
[{"id":1,"name":"The Time Machine"},{"id":2,"name":"Moby Dick"}]
But getting a specific object fails:
$ curl http://localhost:8080/book/1
{"message":"Not Found","error":404}
I must be doing something wrong, but simple I can't see it.

A valid uri needs to be provided in #Resource after which the endpoint can be accessed. I would use as below for a Book resource (note plural books instead of book).
import grails.rest.Resource
#Resource(uri = "/books", readOnly = false, formats = ['json', 'xml'])
class Book {
String name
}
Result:
$ curl http://localhost:8080/books
[{"id":1,"name":"The Time Machine"},{"id":2,"name":"Moby Dick"}]
$ curl http://localhost:8080/books/1
{"id":1,"name":"The Time Machine"}

Related

Trouble using case class for multiple fields in POST body Finatra

I am working on a simple Finatra API example, but having trouble using a case class for the POST request when using more than one field in the request body.
Here is an example of my case class and controller:
class SaleResource extends Controller {
post("/sales") { sale: Sale =>
s"$sale"
}
}
case class Sale(
user: Option[String],
streetAddress: Option[String]
)
I send a post request to that route with the following request body JSON:
{
"user": "Foo Barrington",
"streetAddress":"Baz Street"
}
The response:
Sale(Some(Foo Barrington),None)
The response shows that the user is being properly deserialized, but for some reason I cannot get the streetAddress field to populate.
Also, I noticed when I set either of these fields to String instead of Option[String] I only get unsuccessful 500 responses.
Things I have tried:
case statements matching Some(streetAddress) to that fields string value or "none found" when it is None. In these cases it still is saying streetAddress is None when it is not.
Making the request with both curl and Postman.
I can always access the user field from the Sales object, but never the streetAddress (or any other field from the request body for that matter if I add test elements to the case class.
I would expect both fields to be recognized since they are both provided in the request. I am newer to Scala/Finatra in general, so it is possible I am just using the Finatra library or Case classes incorrectly.
EDIT:
It seems as if changing the field names to not be mixed/camelcase fixes all issues, but this seems like odd behavior.
Finatra uses Jackson library behind the scenes. The default configuration uses PropertyNamingStrategy.SNAKE_CASE which seems like:
{
"user": "Foo Barrington",
"street_address":"Baz Street"
}
You need to change it to PropertyNamingStrategy.LOWER_CAMEL_CASE to parse that JSON.
In order to do that, you need to define a custom FinatraJacksonModule and tell the app to
use it.
object CustomFinatraJacksonModule extends FinatraJacksonModule {
override val propertyNamingStrategy = PropertyNamingStrategy.LOWER_CAMEL_CASE
}
class MyFinatraHttpServer extends HttpServer {
override protected def jacksonModule: Module = CustomFinatraJacksonModule
}
Jackson Integration provides more information about the topic.

How to get Json Api rendering to work with json views in Grails v3.3.3

I have a simple problem and documentation is not helping me resolve it.
I have created a Grails v3.3.3 demo project - and created a simple domain class called JsonApiBook, with 'name' attribute like this
package ttrestapi
import grails.rest.*
#Resource (uri='/jsonApiBook', formats=['json','xml'])
class JsonApiBook {
static constraints = {
}
String name
}
and marked up the URI as the documentation says the JSON API rendering only works with domain classes (and not a controller class).
In my bootstrap I have saved a instance of book to the tables - and can view that generally.
In my views directory I have a created jsonApiBook folder and created two gson files.
A '_jsonApIBook' template like this
import ttrestapi.JsonApiBook
model {
JsonApiBook book
}
json jsonapi.render(book)
which invokes the jsonapi helper object to render the instance.
I have in the same directory created an index.json like this:
import ttrestapi.Book
model {
List<Book> bookList
}
// We can use template namespace
// method with a Collection.
json tmpl.book(bookList)
When I run the app and use postman or browser to render then I get a result but its Json api compliant (I think it's ignored the template).
So localhost:8080/jsonApiBook just returns (looks default layout):
[
{
"id": 1,
"name": "json api book3"
}
]
and localhost:8080/jsonApiBook/1 just returns 'null' which can't be right.
How should I be setting up the json views for rendering JSON API compliant output? As this doesn't appear to work correctly.
build.gradle
buildscript {
....
dependencies {
........
classpath "org.grails.plugins:views-gradle:1.2.7"
}
}
--
apply plugin: "org.grails.grails-web"
apply plugin: "org.grails.plugins.views-json"
dependencies {
. . .
compile "org.grails.plugins:views-json:1.2.7"
. . .
}
Domain JsonApiBook.groovy
import grails.rest.Resource
#Resource (uri='/jsonApiBook', formats=['json','xml'])
class JsonApiBook {
String name
static constraints = {
}
}
Bootstrap.groovy
class BootStrap {
def init = { servletContext ->
new JsonApiBook(name: 'first').save(flush:true)
new JsonApiBook(name: 'second').save(flush:true)
new JsonApiBook(name: 'third').save(flush:true)
new JsonApiBook(name: 'fourth').save(flush:true)
new JsonApiBook(name: 'fifth').save(flush:true)
}
def destroy = {
}
}
Created folder under view called jsonApiBook
Created template named _jsonApiBook.gson in jsonApiBook folder
model {
JsonApiBook jsonApiBook
}
json {
name jsonApiBook.name
}
created show.gson under same folder
model {
JsonApiBook jsonApiBook
}
json g.render(template:"jsonApiBook", model:[jsonApiBook:jsonApiBook])
When i run http://localhost:8080/jsonApiBook i get bellow:
When i run http://localhost:8080/jsonApiBook/1 i get bellow:
Note: I used grails 3.3.3 with h2 memory DB
Reference
Hope this helps you
ok - got to similar place today on the train. Essentially the convention over configuration is core to whats happening here.
First the #Resource annotation generates a default RestfulController for you. In this approach the default base template _resourceClassName.gson expects the model variable to have the same name as the resource type so my original example instead of 'book'
import ttrestapi.JsonApiBook
model {
JsonApiBook book
}
json jsonapi.render(book)
it should really read as (following convention)
import ttrestapi.JsonApiBook
// variable should be same name as the Class name starting with lowercase
// as default (it can be different but the caller has to change how the
// the template parameter is invoked
model {
JsonApiBook jsonApiBook
}
json jsonapi.render(jsonApiBook)
Then the index.gson should have read as modified below
import ttrestapi.JsonBookApi
//note although not obvious in the written docs which use the show command, the
// default expected model variable is <resourceClass>List
model {
List<JsonBookApi> jsonBookApiList
}
// We can use template namespace
// method with a Collection.
json tmpl.jsonBookApi (jsonBookApiList )
If you want to use another variable name then in the base template you'd have to declare that name as map when calling the base template, from the index.gson . e.g. say the variable name in the base template was
model {
JsonBookApi myBook...
then when calling this template from my index.gson you would put something like this
...
model {
List<JsonBookApi> jsonBookApiList
}
json tmpl.jsonBookApi ("myBook", jsonBookApiList )
this invokes the correct template _jsonBookApi, but takes the model variable default in the index.gson and forces it to bind the value of jsonBookApiList to the myBook variable in the base template (_jsonBookApi.gson).
With the default generation of a controller, using #Resource annotation, the model variable will always be 'resourceClassName'List
I think the only way to change that is not to use the #Resource annotation on your domain class, but to use the URL mappings configuration to map your uri to a controller, and then you have to create a controller yourself by hand and ensure you extend from RestfulController. doing this you can override the default model variable name by implementing an overidden 'index()' method and ensuring you explicitly name the model variable you want, and ensure that the index.gson model variable is exactly the same as that set in your controller.
however the key point was I was not following the core convention defaults so the code as originally built couldn't work and returned null.
when you start out the documentation isn't absolutely clear what bits are part of the convention, and in the examples (which use show.gson) don't tell you what the model variable default name will be for the index.gson (add List to end) so its quite easy to get lost

grails 3 rest-api profile GET

I am using grails 3 rest-api profile. I created generated simple domain class
#Resource(readOnly = false, formats = ['json', 'xml'])
class User {
String name
String address
Date dateOfBirth
}
while http://localhost:8080/user/ will give me back json list of users, if I try http://localhost:8080/user/1 if gives back :
{"message":"Not Found","error":404}
what gives ?
actually I since found out that the url exposed is /user/show/1
since RestResource automatically exposes a controller based on grails.rest.RestfulController you can look in that class for the full list of methods
seems to be a bug. If you specify a uri it works:
#Resource(uri='/user/', readOnly = false, formats = ['json', 'xml'])
class User {
String name
String address
Date dateOfBirth
}

Swagger-Springmvc: Getting unknownFields in JSON

I have a REST API which I have configured as follows
#Api(value="rest", description="Sweet blah!!!")
#Controller
public class abc{...}
A method in abc is annotated as follows
#ApiOperation(value="Create Account",
notes="Sweet Blah",
response=Account.class,
nickname="AccountCreation2",
produces= "application/json,application/xml",
consumes="application/json, application/xml")
#ApiImplicitParams(value=
{ #ApiImplicitParam(name="body",value="Sweet Blah.",
required=true, paramType="body", dataType="com.trrr.Account"),
#ApiImplicitParam(name="accountId", value="provides account Id for the new
account",required=true, paramType="path", dataType="Integer")
})
#RequestMapping(value = "/accounts/{accountId}", method = RequestMethod.PUT)
public ResponseEntity<?> createAccount(#PathVariable("accountId") Integer accountId,
#RequestBody Account acct){ ... }
My generated documentation using Swagger UI shows everything find however is unable to generated Model json for Account which is my model class.
Account is composed of few variables, in addition to an array of User Defined class 'Sharing'.
It is composed of another User defined class User.
Account class is annotated as follows:
#XStreamAlias("Account")
#XmlRootElement(name = "Account")
public class Account {... }
The generated documentation displays for Model Response and Request
**{
"unknownFields": {}
}**
Kindly guide as to what may be going wrong here. How to have a json version of Account object displayed. Thank you.
Well, the same thing worked on a next day. Not sure why it was not working on day one. Its appears idiotic but only purpose of me writing this is to ensure that nobody else doing the right way and gets confused with my post.

Grails with MongoDB, Object id, and scaffold

I have data writing to a mongoDB database with issues using integration tests and the Grails scaffolding. When trying to select a domain instance from the 'list' type page, I get the error "[domain name] not found with id null".
I am sure it is because of the Grails url [controller]/[action]/[id]. This id is a string and needs to be converted to an ObjectId for Grails queries.
Is there a way to do this so that it affects a specified domain or even better yet, all of the domains at once?
I guess as I'm writing my app, I can convert it to an ObjectId from within the action method, but I'd like to have the scaffolding work or provide a global solution.
I believe this is happening because the show() method (that the Grails scaffolding functionality generates as an action) accepts an id parameter of type Long ie.
def show(Long id) {
def suiteInstance = Suite.get(id)
if (!suiteInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'suite.label', default: 'MyDomainClass'), id])
redirect(action: "list")
return
}
[suiteInstance: suiteInstance]
}
which binds the id parameter to the argument. Because the ObjectId can't be converted to a Long, it ends up being null, hence the call to MyDomainClass.get(id) fails with the error message.
You can get around this by overriding the show() action in your scaffolded controller so that it expects an ObjectId or String, but I would say the proper fix for this is to update the Grails scaffolding plugin so it is a little more liberal in the types of IDs it accepts.
I had this problem as well. You can keep the domain object id as an ObjectId and update the controller as follows:
domain Object:
import org.bson.types.ObjectId;
class DomainObject {
ObjectId id
// Add other member variables...
}
Controller:
def show(String id) {
def domainObjectInstance = domainObject.get(new ObjectId(id))
if (!domainObjectInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'domainObject.label', default: 'DomainObject'), id])
redirect(action: "list")
return
}
[domainObjectInstance: domainObjectInstance]
}
You would also need to update your other controller methods that use id as well such as edit, update etc.
Additionally, if you want the grails default controller generation to work like this for all your domain objects you can update the template as coderLMN suggests.
The get(params.id) call in show() method will NOT convert params.id String to an ObjectId object, so the domain instance will be null, then the following code takes you to list action with an error message:
if (!exampleInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'example.label', default: 'Example'), params.id])
redirect(action: "list")
return
}
Possible solutions:
you can run "grails install-template" command, so that the scaffolding templates in src/templates/scaffolding/ directory can be modified. Then you have new scaffold ready to generate customized controllers, views, tests for all your Domain classes.
A simpler solution is to define the id property as String instead of ObjectId. A String id will be equal to objectId.toString(), in this case your scaffold will work.
In domain classes keep you id type as ObjectId and keep scaffold = true for all respective controllers.
In Domain class :
ObjectId id
In respective controller :
static scaffold = true
Clear all existing collections from Mongo
I guess that's sufficient to have Grails-Mongo app up & running, considering you have correctly configured mongo-plugin