giovedì 12 novembre 2009

Grails validation, un incongruenza nella constraint size

Dopo mesi che non mi faccio più sentire (l'elezione a sindaco ha decisamente influito) ecco che mi ripropongo a voi.
Sembra difficile crederlo, ma siete davvero in molti quelli che seguite il blog... anche se poco aggiornato.
D'altronde in Italia non sono molti quelli che programmano in GWT e Grails, e in particolare con una combinazione di entrambi.

Oggi forse abbiamo trovato un incongruenza, o almeno qualcosa di poco intuitivo, nella validazione di grails.
Era un qualcosa che avevamo già intuito in passato, oggi abbiamo fatto alcuni test e possiamo essere sicuri di questo comportamento. Stiamo usando grails 1.2M4, ma credo il comportamento sia simili almeno per grails 1.0 in avanti.

In effetti la documentazione è poco chiara sulle constraint, in particolare proprio sull'interazione della constraint size con nullable e blank, verso i quali ha una frase piuttosto criptica:
Currently this constraint cannot be used in addition to blank or nullable, a custom validator may be added to perform this kind of constraints.

Il che apparentemente significherebbe, se il mio inglese non mi tradisce, che la constraint size non si può usare in aggiunta a nullable e blank... ma la cosa risulta falsa, infatti, prendiamo ad esempio questa classe

class Prova {
static constraints = {
prova1(size:0..16)
prova2(size:1..16)
}
String prova1
String prova2
}


e questo codice in un controller:

Prova provaA = new Prova()
provaA.validate()
println "Validazione ProvaA"
provaA.errors.each {println it}

Prova provaB = new Prova()
provaB.prova1 = ""
provaB.prova2 = ""
provaB.validate()
println "Validazione ProvaB"
provaB.errors.each {println it}


si ha questo output
Validazione ProvaA
org.springframework.validation.BeanPropertyBindingResult: 2 errors
Field error in object 'Prova' on field 'prova2': rejected value [null]; codes [Prova.prova2.nullable.error.Prova.prova2,Prova.prova2.nullable.error.prova2,Prova.prova2.nullable.error.java.lang.String,Prova.prova2.nullable.error,prova.prova2.nullable.error.Prova.prova2,prova.prova2.nullable.error.prova2,prova.prova2.nullable.error.java.lang.String,prova.prova2.nullable.error,Prova.prova2.nullable.Prova.prova2,Prova.prova2.nullable.prova2,Prova.prova2.nullable.java.lang.String,Prova.prova2.nullable,prova.prova2.nullable.Prova.prova2,prova.prova2.nullable.prova2,prova.prova2.nullable.java.lang.String,prova.prova2.nullable,nullable.Prova.prova2,nullable.prova2,nullable.java.lang.String,nullable]; arguments [prova2,class Prova]; default message [La proprietà [{0}] della classe [{1}] non può essere null]
Field error in object 'Prova' on field 'prova1': rejected value [null]; codes [Prova.prova1.nullable.error.Prova.prova1,Prova.prova1.nullable.error.prova1,Prova.prova1.nullable.error.java.lang.String,Prova.prova1.nullable.error,prova.prova1.nullable.error.Prova.prova1,prova.prova1.nullable.error.prova1,prova.prova1.nullable.error.java.lang.String,prova.prova1.nullable.error,Prova.prova1.nullable.Prova.prova1,Prova.prova1.nullable.prova1,Prova.prova1.nullable.java.lang.String,Prova.prova1.nullable,prova.prova1.nullable.Prova.prova1,prova.prova1.nullable.prova1,prova.prova1.nullable.java.lang.String,prova.prova1.nullable,nullable.Prova.prova1,nullable.prova1,nullable.java.lang.String,nullable]; arguments [prova1,class Prova]; default message [La proprietà [{0}] della classe [{1}] non può essere null]
Validazione ProvaB
org.springframework.validation.BeanPropertyBindingResult: 0 errors

Non avendo dichiarato nessun'altra contraint oltre a size, come per ogni proprietà della classe, si dovrebbe sottointendere "nullable: true"
E infatti sembrerebbe così, nel tentativo su ProvaA abbiamo i due errori sul valore nullo

Il size sulla stringa è inteso come un controllo sulla lenght() della stringa stessa. quindi può trarre in inganno il size(0..16) che potrebbe far pensare di aver concesso alla stringa un lenght 0... quindi come non avergli dato valore, quindi come aver sottinteso nullable:true
Ma non è così

Altra cosa strana è il size(1..0)... concedere ad una stringa lunghezza minima di 1, potrebbe far pensare di costringere la stringa ad avere un valore diverso dal valore vuoto, in parole povere di aver sottointeso "blank: false", ma non è così
come si vede dall'output precedente, un assegnazione prova2="" è accettata... anche se palesemente un lenght su una stringa = "" sarebbe 0.

Comportamento strano, non credete?