How do we customize grails RestfulController error response? For example one of my restful controller returning following response by default on error while trying to save object.
{
"errors":
[
{
"object": "com.test.Task",
"field": "description",
"rejected-value": null,
"message": "Property [description] of class [class com.test.Task] cannot be null"
}
]
}
I would like to customize the response as bellow.
{
"errors" :
{
"message": "Property [description] of class [class com.test.Task] cannot be"
},
{
"message": "This is going to be any 2nd message"
},
.....
}
I found the solution
All you need is Register Custom Objects Marshallers on org.grails.datastore.mapping.validation.ValidationErrors class
def messageSource //inject messageSource
JSON.registerObjectMarshaller(ValidationErrors) { validationErrors ->
def errors = [] //add all errors into this list
validationErrors.target.errors.allErrors.each { error ->
errors.add(messageSource.getMessage(error, null)) //get messages from properties file.
}
//return map with errors list
return ["errors":errors]
}
Response will be:
{
"errors": [
"Property [description] of class [class com.test.Task] cannot be",
"This is going to be any 2nd message"
]
}
Use the Internationalization feature described here. Add the following to your resource bundle messages.properties.
task.description.nullable = your message
or
com.test.Task.description.nullable = your message
Related
When providing the incorrect format of a field for a request to my application if the type throws an error then the error message returned by micronaut is vague.
E.G two scenarios
public class fakeClass {
#NotNull
private String fakeName;
}
if my request is {"fakeName": ""}
then the response, correctly, would be something like
{
"violations": [
{
"field": "create.fakeClass.fakeName",
"message": "must not be blank"
}
],
"type": "https://zalando.github.io/problem/constraint-violation",
"title": "Constraint Violation",
"status": 400 }
But lets say my class looks like this:
public class fakeClass {
#Format("yyyy-MM-dd")
private LocalDate exampeDate;
}
With an invalid date or incorrect format of {"exampleDate": 202222--01-01} or {"exampleDate": 2022/01/01}
Then the error message is
{
"type": "about:blank",
"parameters": {
"path": "/project"
},
"status": 400,
"detail": "Required argument [fakeClass fakeClass] not specified"
}
Is there a simple way to provide more information to the error message to make it clear why the request failed for an invalid format or type like #NotNull or #NotBlank?
The problem here is not Micronaut but your payloads. The examples you mentioned are invalid JSON documents.
For example this on here is invalid, since the value is not a number nor a string.
{
"exampleDate": 202222--01-01
}
this would be the a valid variant
{
"exampleDate": "202222--01-01"
}
Make sure you send the date as a String. In your case this is expected to be valid.
{
"exampleDate": "2022-11-01"
}
In general it is recommended to send date using the ISO-8601 format, which you did (yyyy-MM-dd). Furthermore I recommend to apply a global configuration rather than using on each POJO a #Format("yyyy-MM-dd") annotation.
jackson:
dateFormat: yyyyMMdd
timeZone: UTC
serializationInclusion: NON_NULL
serialization:
writeDatesAsTimestamps: false
#Format("yyyy-MM-dd") is a formatter not a Constraint.
You can use #Pattern(<regex>). There is also date specific ones like #Past, #PastOrPresent, #Futureand #FutureOrPresent.
I'm successfully using a pipeline resolver to persist a parent/child relationship, except when the list of child items is empty and I #return early.
I'm guessing the issue is around my response mappers and use of $ctx.prev vs $ctx.result but I can't figure it out.
The pipeline looks like this:
BEFORE template: {}
Function 1:
request = PutItem the parent
response = $utils.toJson($ctx.result)
Function 2:
request = TransactWriteItems (foreach UpdateItem) the children
response = $utils.toJson($ctx.prev.result)
AFTER template: $utils.toJson($ctx.prev.result)
When I call the mutation with
{"parentAttribute":"foo", "children": [{"childAttribute": "bar"}]}
I get a good response like:
{
"data": {
"createFoo": {
"parentAttribute": "foo",
"children": [
{
"childAttribute": "bar"
}
]
}
}
}
If no children, Function 2 request mapper does #return to avoid "TransactWriteItems must have at least one operation" error.
In this scenario I am hoping for the above response to the mutation, just with children: []
Instead, I get:
{
"data": {
"createFoo": null
}
}
The data has been written correctly; if I query it I get back the parent with empty list of children.
How do I get this pipeline to execute so that it returns the combined parent+child data whether the child array is populated or not?
Detail
The schema is something like:
type Foo {
id: String!
attr1: String
bars: [Bar]
}
type Bar {
id: String!
attr2: String
}
type Mutation {
createFoo(foo: Foo): Foo
}
And a dynamodb representation like this:
pk
sk
attr1
attr2
FOO#1
METADATA#FOO#1
Lorem
FOO#1
BAR#1
Ipsum
While the pipeline looks like:
before.vtl
{}
createParent-request.vtl
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"pk" : $util.dynamodb.toDynamoDBJson(...),
"sk" : $util.dynamodb.toDynamoDBJson(...)
},
"attributeValues" : {
"data" : $util.dynamodb.toDynamoDBJson(...)
}
}
createParent-response.vtl
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($ctx.result)
createChildren-request.vtl
#if($ctx.args.fooInput.children.size() > 0)
{
"version": "2018-05-29",
"operation": "TransactWriteItems",
"transactItems": [
#foreach( $child in $ctx.args.fooInput.children )
{
"table": "${table}",
"operation": "UpdateItem",
"key": {
"pk" : $util.dynamodb.toDynamoDBJson(...),
"sk" : $util.dynamodb.toDynamoDBJson(...)
},
"update": {
"expression": "SET #data = :data",
"expressionNames": {
"#data": "data"
},
"expressionValues": {
":data":
$util.dynamodb.toDynamoDBJson(...)
}
}
}
#if( $foreach.hasNext ),#end
#end
]
}
#else
#return
#end
createChildren-response.vtl
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($ctx.prev.result)
after.vtl
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($ctx.prev.result)
I figured it out. For the expected behaviour, one needs the 'after' mapper to return the necessary JSON to populate the overall mutation response. In my example above, after.vtl needs to return a parent and nothing else matters (in particular, the result of the individual function response mappers).
I ended up putting the output of the 'create parent' operation into ctx.stash then returning ctx.stash in after.vtl, setting the other resolvers to {}.
Note that, if your response has subtypes (with their own resolvers) and you return it sparse, AppSync will call the resolver. In the context of my example, it's enough to return the parent without any children and then the normal query resolver for "get children of a parent" will execute to populate the final response.
I'm using postman in order to call SuiteCRM REST API.
I tried to call this endpoint
PATCH http://{{suitecrm-url}}/Api/V8/module
and i've added this payload to the body (Content-Type: Application/Json):
{
"data": {
"type": "Accounts",
"id": "3a3ae651-d509-2508-7dc4-5be2e51cc96b",
"attributes": {
"name": "name with space"
}
}
}
When the request is executed SuiteCRM gives this response:
{
"errors": {
"status": 400,
"title": null,
"detail": "The option \"attributes\" with value array is invalid."
}
}
I found out that the problem was the whitespace in the value: when i tried to use the value "namewithspace", it worked.
Anyone has any idea how to solve this problem ?
Thanks in advance
I found out this issue on github that resolved my problem:
https://github.com/salesagility/SuiteCRM/issues/6452
In short to make it work i had to modify the file in
/Api/V8/Params/Options/Fields.php
and replace this line
const REGEX_FIELD_PATTERN = '/[^\w-,]/';
with
const REGEX_FIELD_PATTERN = '/[^\w-,\s\]/';
The person mentioned in github:
this is just for temporary fix and not upgrade safe
// The code below works but I'm not happy with it. Without the shown fix it throws a validation error.
exports.update = function(req,res) {
List.model.findById(req.params.id).exec(function(err, item) {
if (err) return res.apiError('database error', err);
if (!item) return res.apiError('not found');
//Here i have tried PATCH and POST
var data = (req.method == 'PATCH') ? req.body : req.query;
//I added this line to poorly fix the bug.
if (!data.email) {
data.email = item.email
}
// getUpdateHandler is throwing the error
item.getUpdateHandler(req).process(data, function(err) {
//This is my postman request
/api/item/update/58814243317de2ce8d8090fd
//This is my error when removing the fix above
{
"error": "create error",
"detail": {
"message": "Validation failed",
"name": "ValidatorError",
"errors": {
"email": {
"name": "ValidatorError",
"path": "email",
"message": "Email is required",
"type": "required"
}
}
}
}
// Update. My issue: I was not passing the required values to the form when setting up the layout on the get req. So values that were not being updated were being posted as null. If it was a required field it raised an error. Fixed
You can give the update handler a list of fields to process by passing it options.fields (see here). So perhaps you could determine which fields have been sent through in the request, and tell the update handler to only process those fields?
We have created a complextype field "carriers" which is an array of Carrier objects. See below metadata
"dataProperties": [
{
"name": "carriers",
"complexTypeName":"Carrier#Test",
"isScalar":false
}]
The Carrier entity is defined as below:
{
"shortName": "Carrier",
"namespace": "Test",
"isComplexType": true,
"dataProperties": [
{
"name": "Testing",
"isScalar":true,
"dataType": "String"
}
]
}
We are trying to return an array of complextype in breeze from a REST service call. We get an error in breeze.debug.js in the method proto._updateTargetFromRaw. The error is because the datatype is null.
Any idea how to fix this issue?
I'm guessing the problem is in your "complexTypeName". You wrote "Carrier#Test" when I think you meant to write "Carrier:#Test". The ":#" combination separates the "short name" from the namespace; you omitted the colon.
Hope that's the explanation.