I've just finished up with my weekend project - object field validation in Nim. I couldn't find a library that looked quite like this. It's inspired by things like Hibernate validators in that you annotate fields with pragmas. It doesn't use RTTI, relying instead on macros to generate validation functions. The validations can be performed on nested objects as well. Take a look:
https://github.com/captainbland/nim-validation
Here's a quick demonstration of how it looks in a test:
type TestObject = object
something {.lessThan(50).}: int # Here we use a field pragma to assert that this field should be less than 50
stringyfield {.matchesPattern("hello|bye").}: string
# To generate the validation methods, you must call generateValidators on your type:
generateValidators(TestObject)
# Then to validate the object you simply call validate() on it
let validation = TestObject(something: 100, stringyfield: "hello").validate()
# errorCount and hasErrors give you some information about the objects
doAssert(validation.errorCount == 1)
doAssert(validation.hasErrors == true)
# But you can also get a seq of the errors which you can use to extract messages to do with the errors
for error in validation.validationErrors:
echo error.message
Also just a shout out to say this was a fun little project to work on. Nim is great. :D
Excellent addition to Nim library. I am working with very complicated objects that are hard construct correctly, should help on this front.
I was thinking if it is possible to make Nim inject validate calls automatically at object construction, so you can't forget to add validate calls. Maybe pattern templates can do it.
P.S. Does library supports cross field checks? Say, I have to 2 date fields and want check if one is always greater than the other.
Thanks for your feedback.
Good suggestion with regards to injecting the validation calls. It's definitely something I want to do although maybe it makes sense for the consumer of this library to implement. I will play around with this soon to see if I can at least provide a recommendation.
It doesn't support field cross referencing yet unfortunately - at least not explicitly. If it's a nested object you wanted to validate, you could write a custom validator pragma that compares the two fields from the context of the parent object.
I'm still thinking about ways to do that nicely. Maybe something that looks like {.dateIsLaterThan(self.fieldname.} and the library would substitute in that field in this case.
Hi CaptainBland,
I think you are in right direction, inject into scope a variable like it, self or parent and it will make it possible to use in expression if validator is a template
Untested idea:
template validate_field(body: untyped)
let parent {.inject.}
let it {.onject.}
if body: ok
else: error
type TestObject = object
something {.it <= 50).}: int
stringyfield {.it.matchPattern("hello|bye").}: string
date2: Date
date1: {.it >= parent.date2.}: Date
Yeah that'd be a pretty powerful way to do it if it works the way I imagine it does. I'll have to give that a try. What does 'it' mean in this context in your sample there?
Also I'm not sure how syntactically flexible field pragma expressions are at the moment - at least I had trouble using the valid pragma (which takes no arguments as it just marks a nested object for checking) without parens, could be a bug but I'm not sure whose right now! :)
Oh, you are right! Pragma can't be just an expression, it has to be call.
template checkIt(body: untyped)
let parent {.inject.}
let it {.onject.}
if body: ok
else: error
type TestObject = object
something {.checkIt(it <= 50).}: int
stringyfield {.checkIt(it.matchPattern("hello|bye")).}: string
date2: Date
date1: {.checkIt(it >= parent.date2).}: Date
I've now updated this a bit. I fixed a problem where it would try to run any field pragma as a validation, which would usually result in a compile error. This library should now play nicely with other, similar libraries.
More exciting is it now supports cross-field validation - you can access other fields in the object using the identifier 'this', and then you can also access the fields of the referenced objects for comparison. In the below example, the equals validation is used for this.
type
WrapperObject = ref object of RootObj
child* {.valid().}: TestObject
b* {.matchesPattern("hello|bye").}: string
a* {.lessThan(50), equals(this.child.something).}: int
generateValidators(WrapperObject)
let validationNestedNegative = WrapperObject(a: 75, b: "slkjdf", child: TestObject(something: 70, stringyfield: "bye", shouldMatch: "hu")).validate()
echo "ValidatinNested: ", validationNestedNegative.errorCount, "msgs: ", validationNestedNegative
doAssert(validationNestedNegative.errorCount == 4)
doAssert(validationNestedNegative.hasErrors == true)