Constraining Cardinality of Action Parameters

4 views
Skip to first unread message

Ian Tegebo

unread,
Jun 5, 2015, 2:31:19 PM6/5/15
to gra...@googlegroups.com
Consider a command object that has a single integer property 'foo'.  In at least Grails 2.3.x, you can pass multiple 'foo' parameters to an action using such a command object.  Rather than a typeMismatch error, the first value will be used and the rest ignored.  I'd prefer the result to be a "cardinality error", that would cause '.hasErrors' to be true, for example.

@Validateable
class FooParams {
  Integer foo
}

class CardinalityController {
  def index(FooParams foo) { /* foo.hasErrors() should be true when passed multiple 'foo' query parameters */ }
}

So, I'd like a GET request to '/cardinality?foo=1&foo=2' should result in some kind of validation error, e.g. a typeMismatch error (Integer != List<Integer>).  Since custom validators in the 'constraints' static field only see a single value, I can't enforce it there.  I tried using '@BindUsing', but I can't figure out how to add a validation error in a way that's checkable from the calling controller/view.

I'm aware that there are other ways to enforce such a constraint.  For example, by checking params.<param>.list().size().  However, I want this "matching cardinality" behavior for all command objects, so I don't want to have to add interceptors or take some ad-hoc copy-paste approach.

I'm not very familiar with Spring, but it seems like one should be able to replace the default binding strategy to check for cases where there are multiple source parameter values whilst binding to a type that does not implement Collection (or some laundry list of sequence-like types).  If it's not possible to replace the default behavior, I see that I could implement a class that can be used to annotated command objects, i.e. '@BindUsing(<class implementing BindingHelper>).  However, I don't see how to add validation errors.  Here's what I tried when annotating just a field and not implementing a whole class:

@Validateable
class FooParams {
    @BindUsing({obj, src ->
        def value = src['foo']
        def size = value?.size()
        if (1 < size) {
            def errors = new ValidationErrors(obj)
            def error = new FieldError('fooParams', 'foo', 'too many values')
            errors.addError(error)
            obj.errors.addAllErrors(errors)
        }
        src['foo']
    })
    Integer foo
}

Ian Tegebo

unread,
Jun 5, 2015, 3:05:40 PM6/5/15
to gra...@googlegroups.com
So, I figured out that I can throw a ValidationException from a BindUsing closure.  This enabled me to write a BindHelper class that seems to do what I want:

class CardinalityChecker implements BindingHelper {
    @Override
    Object getPropertyValue(Object obj, String propertyName, DataBindingSource source) {
        def cardinality = source[propertyName].size() as Integer
        Class validatingClass = obj.getClass()
        def field = validatingClass.getDeclaredField(propertyName)
        if (1 < cardinality && !Collection.isAssignableFrom(field.getType())) {
            def fieldType = field.getType().name
            obj.errors.reject('too many values')
            throw new ValidationException("Property ${propertyName} is of type ${fieldType}", obj.errors as Errors)
        }
        return source[propertyName]
    }
}

The next step is to figure out how I might affect the default data binding to always include this on @Validateable classes (or some other marker that allows me to have command objects that are picky about assigning sequence-like things to types that aren't as well).

Ian Tegebo

unread,
Jun 5, 2015, 3:20:31 PM6/5/15
to gra...@googlegroups.com
Okay, while I've no clue as to how to replace the default behavior (in Spring or what have you), I think using an annotation may actually be a better approach:

@AnnotationCollector
@Validateable
@BindUsing(CardinalityChecker)
@interface ValidateableWithCardinality {}

That provides a clue to folks used to the default behavior while not incurring much overhead in getting the cardinality check in one's command object class.  Extra credit goes to making the validation error message more useful and in more carefully looking at the types we should accept/reject.

On Friday, June 5, 2015 at 11:31:19 AM UTC-7, Ian Tegebo wrote:
Reply all
Reply to author
Forward
0 new messages