Basic Validation Framework
From Lift
- ! It's a suggestion !
- It's a work in progress (a brain storming, paper POC)
- I don't know the current Liftweb architecture
Contents |
Requirements/Features
- usable outside web context, in example in test context
- simple to write validators, convertors
- manage (add/update/remove) message to clients (feebabck in Wicket, error_list in TG, FacesMessages in JSF,...)
- a validation stack/tree must be readable by third party consumers :
- HtmlBuilder : to add some tags, attributes (eg: maxlength in input tag), content (eg : * to required field's label)
- CustomClientSide : to mimic configuration into a javascript framework
- a validation stack/tree must be modifiable by third party providers :
- ORM : to mirror DB contrains for binded field
- programmer : to add what he wich
- DSL : to translate "user friendly" DSL into
- Widgets : to add conversion (ex: date field splitted into 3 inputs) and controls
- use to process input request : conversion from string and validation
- use to process output response : conversion into strings
- special traitements on first display (avoid some validations, like required)
Proposal
Pseudo types
/**
* Logger where to put messages for Client/Browser.
* (similar to Feebback in wicket, or FacesMessages in JSF)
*/
abstract class ClientLogger {
def info(fieldName: String, message:String, params: Any*)
def warn(fieldName: String, message:String, params: Any*)
def error(fieldName: String, message:String, params: Any*)
def hasWarnOrError() : boolean
def hasWarnOrErrorOn(fieldName:String)
}
/**
* A DataPreparator, is a basic
abstract class DataPreparator {
/**
* Convert and/or Validate the data from Web (simple String, group of string) into other object (Date, Number, phone number,...).
* Input strings value could be removed.
* The convertion could be applied to 0-n entries.
* @param data in/out, set of data to transform
* @param logger logger where to put message for Client/Browser
*/
abstract def fromWeb(data: Map[String, Any], logger: ClientLogger);
/**
* Convert the data into String (or group of string) for a web displaying.
* Input non-strings value should be removed.
* The convertion could be applied to 0-n entries.
* @param data in/out, set of data to transform
* @param logger logger where to put message for Client/Browser
*/
abstract def toWeb(data: Map[String, Any], logger: ClientLogger);
}
abstract class SingleDataPreparator(fieldname: String)[T] extends DataPrepartor {
override def fromWeb(data: Map[String, Any], logger: ClientLogger) {
val value = data.get(fieldname)
if (!StringUtils.isBlank(value) && !logger.hasWarnOrErrors(fieldname)) {
fromWeb(data.get(fieldname), logger)
}
}
override def toWeb(data: Map[String, Any], logger: ClientLogger) {
val value = data.get(fieldname)
if (!StringUtils.isBlank(value) && !logger.hasWarnOrErrors(fieldname)) {
toWeb(data.get(fieldname).asInstanceOf[T] logger)
}
}
abstract def fromWeb(value: String, logger: ClientLogger);
abstract def toWeb(value: T, logger: ClientLogger);
}
case RequiredValidator(fieldname:String) extends DataPreparator{
override def fromWeb(data: Map[String, Any], logger: ClientLogger) = {
if (StringUtils.isBlank(data.get(fieldname)) {
logger.warn(fieldname, "required")
}
}
}
case StringLengthValidator(fieldname:String, max: Int, min: Int) extends SingleDataPreparator(fieldname) {
override def fromWeb(value: String, logger: ClientLogger) = {
if ((max > 0) && (value.length > max)) {
logger.warn(fieldname, "maximum length is %d", max)
}
if ((min > 0) && (value.length < min)) {
logger.warn(fieldname, "minimum length is %d", min)
}
}
}
...
Validation phases
var stringValidators : List(DataPreparator) = _;
def converters : List(DataPreparator) = _;
def anyValidators : List(DataPreparator) = _;
def processRequest(params: Map[String,String], logger : Clientlogger) : Map[String, Any] = {
var data = new Map[String, Any](params)
stringValidators.forEach(fromWeb(data, clogger))
converters.forEach(fromWeb(data, clogger))
anyValidators.forEach(fromWeb(data, clogger))
data
}
def processReponse(data: Map[String,Any], logger : Clientlogger) : Map[String, String] = {
var params = new Map[String, String](data)
anyValidators.forEach(toWeb(data, clogger))
converters.forEach(toWeb(data, clogger))
stringValidators.forEach(toWeb(data, clogger))
params
}

