-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support overriding constraint messages (i18n support) #21
Comments
Indeed :-) I'm still trying to decide how to best approach this -- one possibility is to change the architecture so that constraint messages are not necessarily strings, and can be overridden. Question is, what would the ideal syntax look like? A few ideas:
Where One concern is that I'd like to keep the constraint processing logic type-safe, which would mean having some notion of a constraint "base enumeration type" known to the validator in advance. Either that, or the constraint can simply be If you have a real use-case in mind, this would really help designing a proper API for this feature, and I'm very much open to ideas :-) |
Yep, the first idea is the best and like you said, it's better to keep the constraint processing logic type-safe. We must be able to format the messages too in order to specify the wrong value in the message for example. |
|
IMO, I think you can do it simply by putting the message key and args instead of message case class RuleViolation( value: Any, constraint: String, description: Option[ String ] ) to something like case class RuleViolation( value: Any, constraintKey: String, contraintArgs: Seq[Any], description: Option[ String ] ) Doing this way we can use the existing i18n that most MVC frameworks provide. RuleViolation(7,"validation.size.between",Seq("firstName", 7, 1, 5), Some(firstName)) instead of this RuleViolation(7,"has size 7, expected between 1 and 5",Some(firstName)) Would be nice to have field name as the first item in Seq because developer can customize it validation.size.between={0} must be between {2} and {3} but {1} |
I'm not very fond of this idea, because:
One way of possibly doing this is to provide a cake -- the message shape is determined by the desired backend (so you can import com.wix.accord.simple._ or com.wix.accord.someframework._ etc.), but that has complexity and performance ramifications I'm not very keen on. |
Thank you for replying I'm from Java and Spring messageSource is one of my favorite in Spring. This may explain |
I am interested in this one enhancement. I would like to be able to build my own Failure since I need some different information from a failed validation. Jono |
I'm exploring two possible approaches to generalizing constraint types. In both cases the idea is for the existing, string-based constraints to be moved to
As it stands, both of these approaches appear to be tremendously complicated from an API standpoint; I'll give it a few more hours, but if either approach doesn't appear to pan out, I'll instead go with a radically simplified model: each validator will define (and include in the resulting violation) its own specific constraint type, deriving from a common root. This will probably necessitate adding a type parameter to the violation model as well as validators themselves, which isn't awful but is certainly something to take into account. |
Exactly one month later, and I have something to show for it. The free constraints branch has reached a stable milestone, where all tests pass and things are largely working. In a nutshell, the idea is that the whole Accord cake is now accessible via "domains". A domain defines the type of constraints, and provides constraint generators for core combinators. For example, the legacy, string-based model is called "simple" (have a look at SimpleConstraints to get an idea what implementing a domain is like). The included example project (before and after migration) reflects most of the required changes when migrating to this new approach. These are:
|
is this feature available on the 0.5 snapshot to play with? |
Sorry for the delay -- no, it's experimental enough that I placed it in its own branch, I'm not sure it makes sense to publish this on any repo before I get some feedback. You can very easily build it yourself (clone, switch to |
+1 for this. We really like Accord but this feature has become a necessity for us. BTW, thank you for the great library! |
Some way of replacing default messages with the others is really needed |
This, I believe, will be the major focus for either the next milestone release (0.7) or, if it progresses nicely enough, round 1.0 off. I have some new notions on how to do this, and will go into more detail later. |
Time to seriously tackle this issue. If anyone has additional feedback, now's a good time :-) |
Quick brain-dump of current possible directions: Runtime dispatchIf we give up type safety, the problem becomes very easy -- each combinator determines its own constraint type and uses that. Accord can then provide a very simple rendering framework and it's up to the user to handle the dispatch (or register renderers, a la Jackson modules). This is the fallback method. Accord's main claim to fame is type safety, and I'd hate to have to give that up. CakeSee discussion above. I believe this is far too complex to use in practice, requiring users to define their own cake and import it all over the place, and composing cakes becomes an exercise in annoyance. This doesn't jive with Accord's stated design goal of dead-simple APIs. Type encoding
This gets us to the point where all possible constraint types are encoded in the result; breaking them down into discrete components requires (I believe) yet another macro. A generic trait ConstraintRenderer[C, R] extends (C => R)
trait ResultModel[T] {
type Context
type Constraint
type Result = T
def newContext: Context
def addConstraint(context: Context, constraint: Constraint): Unit // Hand-wavy. Needs to take hierarchy into account
def finalizeContext(context: Context): T
}
def render[C](result: Result[C])(implicit model: ResultModel[_]): model.Result = macro renderImpl
def renderImpl(...) = {
// A macro which:
// 1. Break down C into separate components C1, C2...,CEn
// 2. Generate an implicit requirement for each C as follows:
val mappers = Map(
classOf[C1] -> implicitly[ConstraintRenderer[C1, model.Constraint]],
classOf[C2] -> implicitly[ConstraintRenderer[C2, model.Constraint]], // ...
classOf[Cn] -> implicitly[ConstraintRenderer[Cn, model.Constraint]]
)
// 3. Generates code to map constraints to target result model
val context = model.newContext
result.flatten foreach { violation =>
val mapper = mappers.get(violation.constraint.getClass).getOrElse(throw new Exception("Safety net: there's a bug in Accord"))
model.addConstraint(context, mapper(violation.constraint))
}
model.finalizeContext(context)
} The usage site would then be: case class Person(name: String, age: Int)
case object Person {
implicit val personValidator = validator[Person] { p =>
p.name is notEmpty // Resolves to type C1 = Not[Empty.type]
p.age must be >= 18 // Resolves to type C2 = BinaryOp[Int]
}
// Type of personValidator is Validator[Person, Not[Empty.type] with BinaryOp[Int]]
}
val result = validate(Person("", -6)) // Resolves to Result[Not[Empty.type] with BinaryOp[Int]]
import com.wix.accord.rendering.Text._
// Text would include the following implicits:
// implicit val ResultModel[String]
// implicit def renderNot[C](implicit r: ConstraintRenderer[C, String]]): ConstraintRenderer[Not[C], String]
// implicit def renderBinaryOp[T]: ConstraintRenderer[BinaryOp[T]](implicit r: ConstraintRenderer[T, String]])
// ...
val rendered: String = render(result) The generated implicit requirement guarantees that renderers for all requisite constraint types are available. I'm rather leery about the whitebox macros, but this is the most promising avenue of research I have. |
Just wanted to give +1 here too, it would be really great to have this feature. Or is there any way at all to customize a constraint message (not necessarily compatible with i18n frameworks etc)? Edit: Nevermind, just reimplemented the |
I really like the feature as well, any news on when it would be released? Thanks |
Pinging @noam-almog, a fresh pair of eyes would be invaluable here |
It will be nice to have i18n support or at least a way to customize constraints messages.
The text was updated successfully, but these errors were encountered: