-
Notifications
You must be signed in to change notification settings - Fork 1
/
Framework.scala
192 lines (160 loc) · 7.22 KB
/
Framework.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package com.github.tminglei.bind
import FrameworkUtils._
import org.slf4j.LoggerFactory
/**
* A mapping, w/ constraints/processors/options, was used to validate/convert input data
*/
trait Mapping[T] extends Metable[MappingMeta] {
/**
* get the mapping's options
*/
def options: Options = Options.apply()
/**
* set the mapping's options
*/
def options(setting: Options => Options) = this
/**
* associate a label the mapping, which will be used when generating error message
*/
def label(label: String) = options(_.copy(_label = Option(label)))
/**
* prepend some [[PreProcessor]]s to the mapping, which will be used to pre-process inputting data strings
*/
def >-:(newProcessors: PreProcessor*) = options(_.copy(_processors = newProcessors ++: options._processors))
/**
* prepend some [[Constraint]]s to the mapping, which will be used to validate data string before it was converted
*/
def >+:(newConstraints: Constraint*) = options(_.copy(_constraints = newConstraints ++: options._constraints))
/**
* append some [[ExtraConstraint]]s to the mapping, which will be used to validate data after it was converted
*/
def verifying(validates: ExtraConstraint[T]*) = options(_.copy(_extraConstraints = options._extraConstraints ++ validates))
///---
/**
* do the converting and return the result object if successful
* (p.s. to prevent failure, [[validate]] should be called before this)
*
* @param name the full name of the data node
* @param data the data map
* @return result object
*/
def convert(name: String, data: Map[String, String]): T
/**
* do the validating and return the error message list
* (p.s. if successful, the returned list is empty)
*
* @param name the full name of the data node
* @param data the data map
* @param messages the messages object
* @param parentOptions the parent mapping's options
* @return error message list
*/
def validate(name: String, data: Map[String, String], messages: Messages, parentOptions: Options): Seq[(String, String)]
/**
* used to map/transform result object to another
*
* @param transform the transform function
* @tparam R target result type
* @return a new wrapper mapping
*/
def map[R](transform: T => R): Mapping[R] = new TransformMapping[T, R](this, transform)
}
///////////////////////////////////////// core mapping implementations /////////////////////////////////
/**
* A wrapper mapping, used to transform converted value to another
*/
private
case class TransformMapping[T, R](base: Mapping[T], transform: T => R,
extraConstraints: List[ExtraConstraint[R]] = Nil) extends Mapping[R] {
private val logger = LoggerFactory.getLogger(TransformMapping.getClass)
override def _meta = base._meta
override def options = base.options
override def options(setting: Options => Options) = copy(base = base.options(setting))
override def verifying(validates: ExtraConstraint[R]*) = copy(extraConstraints = extraConstraints ++ validates)
def convert(name: String, data: Map[String, String]): R = {
logger.debug(s"transforming $name")
transform(base.convert(name, data))
}
def validate(name: String, data: Map[String, String], messages: Messages, parentOptions: Options): Seq[(String, String)] = {
val errors = base.validate(name, data, messages, parentOptions)
if (errors.isEmpty)
Option(convert(name, data)).map { v =>
extraValidateRec(name, v, messages, base.options.merge(parentOptions), extraConstraints)
}.getOrElse(Nil)
else errors
}
override def toString = _meta.name
}
/**
* A field mapping is an atomic mapping, which doesn't contain other mappings
*/
case class FieldMapping[T](inputMode: InputMode = SoloInput, doConvert: (String, Map[String, String]) => T,
moreValidate: Constraint = PassValidating, meta: MappingMeta,
override val options: Options = Options.apply()) extends Mapping[T] {
private val logger = LoggerFactory.getLogger(FieldMapping.getClass)
override val _meta = meta
override def options(setting: Options => Options) = copy(options = setting(options))
def convert(name: String, data: Map[String, String]): T = {
logger.debug(s"converting $name")
val newData = processDataRec(name, data, options.copy(_inputMode = inputMode), options._processors)
doConvert(name, newData)
}
def validate(name: String, data: Map[String, String], messages: Messages, parentOptions: Options): Seq[(String, String)] = {
logger.debug(s"validating $name")
val theOptions = options.merge(parentOptions).copy(_inputMode = inputMode)
val newData = processDataRec(name, data, theOptions, theOptions._processors)
if (isUntouchedEmpty(name, newData, theOptions)) Nil
else {
val validates = (if (theOptions._ignoreConstraints) Nil else theOptions._constraints) :+
moreValidate
val errors = validateRec(name, newData, messages, theOptions, validates)
if (errors.isEmpty) {
Option(doConvert(name, newData)).map { v =>
extraValidateRec(name, v, messages, theOptions, theOptions.$extraConstraints)
}.getOrElse(Nil)
} else errors
}
}
override def toString = _meta.name
}
/**
* A group mapping is a compound mapping, and is used to construct a complex/nested mapping
*/
case class GroupMapping[T](fields: Seq[(String, Mapping[_])], doConvert: (String, Map[String, String]) => T,
override val options: Options = Options.apply(_inputMode = BulkInput)) extends Mapping[T] {
private val logger = LoggerFactory.getLogger(GroupMapping.getClass)
override val _meta = MappingMeta("object", reflect.classTag[Product], Nil)
override def options(setting: Options => Options) = copy(options = setting(options))
def convert(name: String, data: Map[String, String]): T = {
logger.debug(s"converting $name")
val newData = processDataRec(name, data, options, options._processors)
if (isEmptyInput(name, newData, options._inputMode)) null.asInstanceOf[T]
else doConvert(name, newData)
}
def validate(name: String, data: Map[String, String], messages: Messages, parentOptions: Options): Seq[(String, String)] = {
logger.debug(s"validating $name")
val theOptions = options.merge(parentOptions)
val newData = processDataRec(name, data, theOptions, theOptions._processors)
if (isUntouchedEmpty(name, newData, theOptions)) Nil
else {
val validates = theOptions._constraints :+
{ (name: String, data: Map[String, String], messages: Messages, options: Options) =>
if (isEmptyInput(name, data, options._inputMode)) Nil
else {
fields.flatMap { case (fieldName, binding) =>
val fullName = if (name.isEmpty) fieldName else name + "." + fieldName
binding.validate(fullName, data, messages, options)
}
}
}
val errors = validateRec(name, newData, messages, theOptions, validates)
if (errors.isEmpty) {
if (isEmptyInput(name, newData, options._inputMode)) Nil
else {
extraValidateRec(name, doConvert(name, newData), messages, theOptions, theOptions.$extraConstraints)
}
} else errors
}
}
override def toString = _meta.name
}