Best way to handle errors in Grails domain class validation - grails

We have an API command like "student/create" to create a new student object. The code looks like this:
def student = new Student(firstName: firstName, lastName: lastName, email: email)
if (! student.validate()) {
response.error = "UNKNOWN_ERROR" // in case we can't find anything better
student.errors.allErrors.each { error ->
// set response.error to an appropriate value
println error
}
} else {
student.save()
}
Our goal is to give a reasonable error message like "EMAIL_DUPLICATE" or "FIRSTNAME_LENGTH" on a validation fail, so we want to test the errors we get against a set of expected errors so we can respond like that.
Here's what we get from that println:
Field error in object 'com.example.Student' on field 'email': rejected value [student#example.com]; codes [com.example.Student.email.unique.error.com.example.Student.email,com.example.Student.email.unique.error.email,com.example.Student.email.unique.error.java.lang.String,com.example.Student.email.unique.error,student.email.unique.error.com.example.Student.email,student.email.unique.error.email,student.email.unique.error.java.lang.String,student.email.unique.error,com.example.Student.email.unique.com.example.Student.email,com.example.Student.email.unique.email,com.example.Student.email.unique.java.lang.String,com.example.Student.email.unique,student.email.unique.com.example.Student.email,student.email.unique.email,student.email.unique.java.lang.String,student.email.unique,unique.com.example.Student.email,unique.email,unique.java.lang.String,unique]; arguments [email,class com.example.Student,student#example.com.org]; default message [Property [{0}] of class [{1}] with value [{2}] must be unique]
How can I figure out that this means the email is already used in the database so that I can tell the API user that?
(to be clear, I want to give a computer-readable message like "EMAIL_DUPLICATE" instead of something like "Property email of class Student with value student#example.com must be unique")

Not sure it would work in more situations than just this one, but does:
println "${error.objectName}_${error.codes[-1]}".toUpperCase()
Get you anywhere near?

Related

Extracting hash key that may or may not be an array

I'm making an API call that returns XML (JSON also available) and the response body will show errors if any. There may be only one error or multiple errors. When the XML (or JSON) is parsed into a hash, the key that holds the errors will be an array when multiple errors are present but will be just a standard key when only one error is present. This makes parsing difficult as I can't seem to come up with one line of code that would fit both cases
The call to the API returns this when one error
<?xml version=\"1.0\" encoding=\"utf-8\"?><response><version>1.0</version><code>6</code><message>Data validation failed</message><errors><error><parameter>rptFilterValue1</parameter><message>Parameter is too small</message></error></errors></response>
And this when multiple errors
<?xml version=\"1.0\" encoding=\"utf-8\"?><response><version>1.0</version><code>6</code><message>Data validation failed</message><errors><error><parameter>rptFilterValue1</parameter><message>Parameter is too small</message></error><error><parameter>rptFilterValue2</parameter><message>Missing required parameter</message></error></errors></response>
I use the following to convert the XML to a Hash
Hash.from_xml(response.body).deep_symbolize_keys
This returns the following hash.
When there is only one error, the hash looks like this
{:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}}}}
When there are 2 errors, the hash looks like this
{:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>[{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}, {:parameter=>"rptFilterValue2", :message=>"Missing required parameter"}]}}}
When I first tested the API response, I had multiple errors so the way I went about getting the error message was like this
data = Hash.from_xml(response.body).deep_symbolize_keys
if data[:response].has_key?(:errors)
errors = data[:response][:errors][:error].map{|x| "#{x.values[0]} #{x.values[1]}"}
However when there is only one error, the code errors out with undefined method 'values' for parameter
The only actual workaround I found was to test the class of the error key. When Array I use one method for extracting and when Hash I use another method.
if data[:response][:errors][:error].class == Array
errors = data[:response][:errors][:error].map{|x| "#{x.values[0]} #{x.values[1]}"}
else
errors = data[:response][:errors][:error].map{|x| "#{x[1]}"}
end
But I just hate hate hate it. There has to be a way to extract xml/json data from a key that may or may not be an array. The solution may be in the conversion from xml to hash rather than when parsing the actual hash. I couldn't find anything online.
I'll appreciate any help or tip.
If you're using Rails, Array#wrap is available if you can do your .dig first:
single = {:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}}}}
Array.wrap(single.dig(:response, :errors, :error))
This returns an Array of size 1:
[
{
:message => "Parameter is too small",
:parameter => "rptFilterValue1"
}
]
For multiples:
multiple = {:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>[{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}, {:parameter=>"rptFilterValue2", :message=>"Missing required parameter"}]}}}
Array.wrap(multiple.dig(:response, :errors, :error))
This returns an Array of size 2:
[
{
:message => "Parameter is too small",
:parameter => "rptFilterValue1"
},
{
:message => "Missing required parameter",
:parameter => "rptFilterValue2"
}
]
You can parse XML with Nokogiri and xpath, which returns array even if selector points out single element
errors = Nokogiri::XML(xml_response).xpath('//error')
errors.map { |e| e.children.each_with_object({}) { |x, h| h[x.name] = x.content } }
Your API response with single error gives
=> [{"parameter"=>"rptFilterValue1", "message"=>"Parameter is too small"}]
and API result with multiple errors
=> [{"parameter"=>"rptFilterValue1", "message"=>"Parameter is too small"}, {"parameter"=>"rptFilterValue2", "message"=>"Missing required parameter"}]
If there's no error elements you'll get an empty array.

How to read results using Apollo Client for iOS

I'm trying to use GraphQL in iOS with Apollo Client. I have the following mutation:
login(user: String!, password: String!): UserType
and the UserType looks like this:
id: ID
user: String!
password: String!
name: String
lastname: String
email: Email!
groups: [GroupType]
In iOS, I have configured aopllo client as the doc says and is working perfectly, but I don't know how to get access to every field in the response. When the login success I want to read the json I receive as response with the UserType fields, so, I'm doing this:
apolloClient.perform(mutation: loginMutation) {
resultsGQL, error in
...
}
My question is, how can I read every field from resultGQL which belongs to the UserType data defined in my grapql schema?
Regards
The question is not 100% clear, since it is missing some code your mutation: A GraphQL mutation has to return at least one value, which you have to define. Since i'm not sure about your method
login(user: String!, password: String!): UserType
i am giving you a simple example for updating an existing userProfile with a GraphQL mutation and then returning every field being defined in your schema for userType.
Let us assume you have a userType (and therefore know the corresponding userId) and you want to modify the email:
mutation updateUserProfile($id: ID!, $newEmail: String) {
updateUser(id: $id, email: $newEmail) {
id
user
password
name
lastName
email
}
}
As you can see, after executing
updateUser(id: $id, email: $newEmail)
all return values are defined inside the following {...} parentheses and are therefore accessible in your callback variable
resultsGQL
That means:
apolloClient.perform(mutation: loginMutation) { resultsGQL, error in
if let results = resultsGQL?.data {
// "results" can now access all data from userType
}
}
Since you defined all entities of your userType schema to be returned from the mutation, you can access them now in your callback variable.

How to add Error Code to validation attributes in MVC?

I'd like to be able to specify an error code to be returned from my API along with the error message in the event that validation fails. The aim is to specify a validation attribute on a property such as:
[Range(1, int.MaxValue, ErrorMessage = "Page must be 1 or greater", ErrorCode = 1234)]
And, in the event that someone requests page 0, return a 400 Bad Request with a JSON error object in the body like this:
{
"errorCode": 1234,
"errorMessage": "Page : Page must be 1 or greater"
}
I already have a custom ModelValidationFilter which returns an array of error messages from the ModelStateDictionary, so that's all working, but I can't see a straightforward way of getting the error code included in there too without overriding all of the MVC model validation classes.
This seems like a problem which should already have been solved by someone at some point, but I can't find anything to support that theory.

grails: how to check field value's validity specifically

getFieldError("user_name").rejectedValue
If I have a field as above where I've set constraints as follows:
user_name(blank: false, unique: true, email: true)
how can I check for violations specifically in each case so as to customize the error message in each case? Currently I have the following but that only works in the first case if I try to submit the form with an empty field:
def errorVal = createdUser.errors.getFieldError("user_name").rejectedValue
if (errorVal == null)
render(contentType: 'text/json') {["message": 'Username cannot be blank!']}
else if (errorVal == invalid)
render(contentType: 'text/json') {["message": 'Username must be an e-mail address!']}
else if (errorVal == unique)
render(contentType: 'text/json') {["message": 'Username already exists for picked institution!']}
If I try to test the second case by putting something in the field which is a non-email address, I get this error in the console:
No such property: invalid for class: com.twc.fatcaone.AdminController
Like the great answers others have posted, I would recommend following built in framework guidelines, however, if you want you can use the EmailValidator class to check for valid e-mail addresses. Uniqueness is already check for you but if you have additional custom criteria then you can search the database using AND on your different criteria and if the result is positive you know its not unique. Again, this may not always be the best solution.

Providing meaningful error messages to Unity3d WWW.error from Rails

I am learning how to post data to a web server from Unity3d as a first step towards creating a leaderboard for my game. Right now I have a very simple script in Unity that posts a name and score to my server. The PostScoreURL maps to a create action in Rails (when sent as a POST). It is very simple but works on a basic level.
Unity has a WWW class that I am using to POST to the server. This class has an error property but it is always coming up as 500 Internal Server Error; it has this error if the user is successfully created or if the user is not created due to a validation problem. I would like to add to my Rails app so that I can send more meaningful error messages back to Unity so that the user will know what is going on. For example, if they try to create a user that already exists, they should be notified that they need to use a different name.
Here is my code:
The Unity script:
string PostScoreURL = "http://localhost:3000/users";
string name = "Melanie";
int score = 8732;
WWW www;
bool requestSent = false;
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 100),"Post Score"))
{
WWWForm form = new WWWForm();
form.AddField("user[name]", name);
form.AddField("user[score]", score);
www = new WWW(PostScoreURL,form);
requestSent = true;
}
if(requestSent && www.isDone)
{
print (www.error);
requestSent = false;
}
}
From the Rails app:
user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :score
validates_presence_of :name, :score
validates_uniqueness_of :name
end
Here is my create action:
def create
#user = User.new(params[:user])
#user.save
end
I would like to send back relevant error messages to Unity. For example: Missing Username, Missing Score, Username taken.
You shouldn't consider your business rule errors as connection errors, that is, those errors should be treated in the server side, returning to the client a "successful" response with an internal error code, defined by you.
I don't know RoR, it probably let you define error codes in the response, but in my case (that don't use RoR) we created a response class with error codes that is serialized and returned to the client. After that, WWW class calls a parser that will verify the error codes. Also, WWW watches for connection errors on WWW.error.

Resources