/dsl/validation.go

https://github.com/goadesign/goa · Go · 385 lines · 207 code · 31 blank · 147 comment · 99 complexity · 4512242e4808db9cd7429c4f454e2985 MD5 · raw file

  1. package dsl
  2. import (
  3. "reflect"
  4. "regexp"
  5. "strconv"
  6. "goa.design/goa/v3/eval"
  7. "goa.design/goa/v3/expr"
  8. )
  9. const (
  10. // FormatDate describes RFC3339 date values.
  11. FormatDate = expr.FormatDate
  12. // FormatDateTime describes RFC3339 date time values.
  13. FormatDateTime = expr.FormatDateTime
  14. // FormatUUID describes RFC4122 UUID values.
  15. FormatUUID = expr.FormatUUID
  16. // FormatEmail describes RFC5322 email addresses.
  17. FormatEmail = expr.FormatEmail
  18. // FormatHostname describes RFC1035 Internet hostnames.
  19. FormatHostname = expr.FormatHostname
  20. // FormatIPv4 describes RFC2373 IPv4 address values.
  21. FormatIPv4 = expr.FormatIPv4
  22. // FormatIPv6 describes RFC2373 IPv6 address values.
  23. FormatIPv6 = expr.FormatIPv6
  24. // FormatIP describes RFC2373 IPv4 or IPv6 address values.
  25. FormatIP = expr.FormatIP
  26. // FormatURI describes RFC3986 URI values.
  27. FormatURI = expr.FormatURI
  28. // FormatMAC describes IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address values.
  29. FormatMAC = expr.FormatMAC
  30. // FormatCIDR describes RFC4632 and RFC4291 CIDR notation IP address values.
  31. FormatCIDR = expr.FormatCIDR
  32. // FormatRegexp describes regular expression syntax accepted by RE2.
  33. FormatRegexp = expr.FormatRegexp
  34. // FormatJSON describes JSON text.
  35. FormatJSON = expr.FormatJSON
  36. // FormatRFC1123 describes RFC1123 date time values.
  37. FormatRFC1123 = expr.FormatRFC1123
  38. )
  39. // Enum adds a "enum" validation to the attribute.
  40. // See http://json-schema.org/latest/json-schema-validation.html#anchor76.
  41. //
  42. // Example:
  43. //
  44. // Attribute("string", String, func() {
  45. // Enum("this", "that", "and this")
  46. // })
  47. //
  48. // Attribute("array", ArrayOf(Int), func() {
  49. // Elem(func() {
  50. // Enum(1, 2, 3, 4, 5) // Sets possible values for array elements
  51. // })
  52. // })
  53. //
  54. func Enum(vals ...interface{}) {
  55. if a, ok := eval.Current().(*expr.AttributeExpr); ok {
  56. for i, v := range vals {
  57. // When can a.Type be nil? glad you asked
  58. // There are two ways to write an Attribute declaration with the DSL that
  59. // don't set the type: with one argument - just the name - in which case the type
  60. // is set to String or with two arguments - the name and DSL. In this latter form
  61. // the type can end up being either String - if the DSL does not define any
  62. // attribute - or object if it does.
  63. // Why allowing this? because it's not always possible to specify the type of an
  64. // object - an object may just be declared inline to represent a substructure.
  65. // OK then why not assuming object and not allowing for string? because the DSL
  66. // where there's only one argument and the type is string implicitly is very
  67. // useful and common, for example to list attributes that refer to other attributes
  68. // such as responses that refer to responses defined at the API level or links that
  69. // refer to the result type attributes. So if the form that takes a DSL always ended
  70. // up defining an object we'd have a weird situation where one arg is string and
  71. // two args is object. Breaks the least surprise principle. Soooo long story
  72. // short the lesser evil seems to be to allow the ambiguity. Also tests like the
  73. // one below are really a convenience to the user and not a fundamental feature
  74. // - not checking in the case the type is not known yet is OK.
  75. if a.Type != nil && !a.Type.IsCompatible(v) {
  76. eval.ReportError("value %#v at index %d is incompatible with attribute of type %s",
  77. v, i, a.Type.Name())
  78. ok = false
  79. }
  80. }
  81. if ok {
  82. if a.Validation == nil {
  83. a.Validation = &expr.ValidationExpr{}
  84. }
  85. a.Validation.Values = make([]interface{}, len(vals))
  86. for i, v := range vals {
  87. switch actual := v.(type) {
  88. case expr.MapVal:
  89. a.Validation.Values[i] = actual.ToMap()
  90. case expr.ArrayVal:
  91. a.Validation.Values[i] = actual.ToSlice()
  92. default:
  93. a.Validation.Values[i] = actual
  94. }
  95. }
  96. }
  97. }
  98. }
  99. // Format adds a "format" validation to the attribute.
  100. // See http://json-schema.org/latest/json-schema-validation.html#anchor104.
  101. // The formats supported by goa are:
  102. //
  103. // FormatDate: RFC3339 date
  104. //
  105. // FormatDateTime: RFC3339 date time
  106. //
  107. // FormatUUID: RFC4122 uuid
  108. //
  109. // FormatEmail: RFC5322 email address
  110. //
  111. // FormatHostname: RFC1035 internet host name
  112. //
  113. // FormatIPv4, FormatIPv6, FormatIP: RFC2373 IPv4, IPv6 address or either
  114. //
  115. // FormatURI: RFC3986 URI
  116. //
  117. // FormatMAC: IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address
  118. //
  119. // FormatCIDR: RFC4632 or RFC4291 CIDR notation IP address
  120. //
  121. // FormatRegexp: RE2 regular expression
  122. //
  123. // FormatJSON: JSON text
  124. //
  125. // FormatRFC1123: RFC1123 date time
  126. //
  127. // Example:
  128. //
  129. // Attribute("created_at", String, func() {
  130. // Format(FormatDateTime)
  131. // })
  132. func Format(f expr.ValidationFormat) {
  133. if a, ok := eval.Current().(*expr.AttributeExpr); ok {
  134. if !a.IsSupportedValidationFormat(f) {
  135. eval.ReportError("invalid validation format %q", f)
  136. }
  137. if a.Type != nil && a.Type.Kind() != expr.StringKind {
  138. incompatibleAttributeType("format", a.Type.Name(), "a string")
  139. } else {
  140. if a.Validation == nil {
  141. a.Validation = &expr.ValidationExpr{}
  142. }
  143. a.Validation.Format = expr.ValidationFormat(f)
  144. }
  145. }
  146. }
  147. // Pattern adds a "pattern" validation to the attribute.
  148. // See http://json-schema.org/latest/json-schema-validation.html#anchor33.
  149. //
  150. // Example:
  151. //
  152. // Attribute("pattern", String, func() {
  153. // Pattern("^[A-Z].*[0-9]$")
  154. // })
  155. //
  156. func Pattern(p string) {
  157. if a, ok := eval.Current().(*expr.AttributeExpr); ok {
  158. if a.Type != nil && a.Type.Kind() != expr.StringKind {
  159. incompatibleAttributeType("pattern", a.Type.Name(), "a string")
  160. } else {
  161. _, err := regexp.Compile(p)
  162. if err != nil {
  163. eval.ReportError("invalid pattern %#v, %s", p, err)
  164. } else {
  165. if a.Validation == nil {
  166. a.Validation = &expr.ValidationExpr{}
  167. }
  168. a.Validation.Pattern = p
  169. }
  170. }
  171. }
  172. }
  173. // Minimum adds a "minimum" validation to the attribute.
  174. // See http://json-schema.org/latest/json-schema-validation.html#anchor21.
  175. //
  176. // Example:
  177. //
  178. // Attribute("integer", Int, func() {
  179. // Minimum(100)
  180. // })
  181. //
  182. func Minimum(val interface{}) {
  183. if a, ok := eval.Current().(*expr.AttributeExpr); ok {
  184. if a.Type != nil &&
  185. a.Type.Kind() != expr.IntKind && a.Type.Kind() != expr.UIntKind &&
  186. a.Type.Kind() != expr.Int32Kind && a.Type.Kind() != expr.UInt32Kind &&
  187. a.Type.Kind() != expr.Int64Kind && a.Type.Kind() != expr.UInt64Kind &&
  188. a.Type.Kind() != expr.Float32Kind && a.Type.Kind() != expr.Float64Kind {
  189. incompatibleAttributeType("minimum", a.Type.Name(), "an integer or a number")
  190. } else {
  191. var f float64
  192. switch v := val.(type) {
  193. case float32, float64, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64:
  194. f = reflect.ValueOf(v).Convert(reflect.TypeOf(float64(0.0))).Float()
  195. case string:
  196. var err error
  197. f, err = strconv.ParseFloat(v, 64)
  198. if err != nil {
  199. eval.ReportError("invalid number value %#v", v)
  200. return
  201. }
  202. default:
  203. eval.ReportError("invalid number value %#v", v)
  204. return
  205. }
  206. if a.Validation == nil {
  207. a.Validation = &expr.ValidationExpr{}
  208. }
  209. a.Validation.Minimum = &f
  210. }
  211. }
  212. }
  213. // Maximum adds a "maximum" validation to the attribute.
  214. // See http://json-schema.org/latest/json-schema-validation.html#anchor17.
  215. //
  216. // Example:
  217. //
  218. // Attribute("integer", Int, func() {
  219. // Maximum(100)
  220. // })
  221. //
  222. func Maximum(val interface{}) {
  223. if a, ok := eval.Current().(*expr.AttributeExpr); ok {
  224. if a.Type != nil &&
  225. a.Type.Kind() != expr.IntKind && a.Type.Kind() != expr.UIntKind &&
  226. a.Type.Kind() != expr.Int32Kind && a.Type.Kind() != expr.UInt32Kind &&
  227. a.Type.Kind() != expr.Int64Kind && a.Type.Kind() != expr.UInt64Kind &&
  228. a.Type.Kind() != expr.Float32Kind && a.Type.Kind() != expr.Float64Kind {
  229. incompatibleAttributeType("maximum", a.Type.Name(), "an integer or a number")
  230. } else {
  231. var f float64
  232. switch v := val.(type) {
  233. case float32, float64, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64:
  234. f = reflect.ValueOf(v).Convert(reflect.TypeOf(float64(0.0))).Float()
  235. case string:
  236. var err error
  237. f, err = strconv.ParseFloat(v, 64)
  238. if err != nil {
  239. eval.ReportError("invalid number value %#v", v)
  240. return
  241. }
  242. default:
  243. eval.ReportError("invalid number value %#v", v)
  244. return
  245. }
  246. if a.Validation == nil {
  247. a.Validation = &expr.ValidationExpr{}
  248. }
  249. a.Validation.Maximum = &f
  250. }
  251. }
  252. }
  253. // MinLength adds a "minItems" validation to the attribute.
  254. // See http://json-schema.org/latest/json-schema-validation.html#anchor45.
  255. //
  256. // Example:
  257. //
  258. // Attribute("map", MapOf(String, String), func() {
  259. // MinLength(10) // min key-values in map
  260. // Key(func() {
  261. // MinLength(1) // min length of map key
  262. // })
  263. // Elem(func() {
  264. // MinLength(5) // min length of map elements
  265. // })
  266. // })
  267. //
  268. func MinLength(val int) {
  269. if a, ok := eval.Current().(*expr.AttributeExpr); ok {
  270. if a.Type != nil {
  271. kind := a.Type.Kind()
  272. if kind != expr.BytesKind &&
  273. kind != expr.StringKind &&
  274. kind != expr.ArrayKind &&
  275. kind != expr.MapKind {
  276. incompatibleAttributeType("minimum length", a.Type.Name(), "a string or an array")
  277. return
  278. }
  279. }
  280. if a.Validation == nil {
  281. a.Validation = &expr.ValidationExpr{}
  282. }
  283. a.Validation.MinLength = &val
  284. }
  285. }
  286. // MaxLength adds a "maxItems" validation to the attribute.
  287. // See http://json-schema.org/latest/json-schema-validation.html#anchor42.
  288. //
  289. // Example:
  290. //
  291. // Attribute("array", ArrayOf(String), func() {
  292. // MaxLength(200) // max array length
  293. // Elem(func() {
  294. // MaxLength(5) // max length of each array element
  295. // })
  296. // })
  297. //
  298. func MaxLength(val int) {
  299. if a, ok := eval.Current().(*expr.AttributeExpr); ok {
  300. if a.Type != nil {
  301. kind := a.Type.Kind()
  302. if kind != expr.BytesKind &&
  303. kind != expr.StringKind &&
  304. kind != expr.ArrayKind &&
  305. kind != expr.MapKind {
  306. incompatibleAttributeType("maximum length", a.Type.Name(), "a string or an array")
  307. return
  308. }
  309. }
  310. if a.Validation == nil {
  311. a.Validation = &expr.ValidationExpr{}
  312. }
  313. a.Validation.MaxLength = &val
  314. }
  315. }
  316. // Required adds a "required" validation to the attribute.
  317. // See http://json-schema.org/latest/json-schema-validation.html#anchor61.
  318. //
  319. // Example:
  320. //
  321. // var _ = Type("MyType", func() {
  322. // Attribute("string", String)
  323. // Attribute("int", Integer)
  324. // Required("string", "int")
  325. // })
  326. //
  327. func Required(names ...string) {
  328. var at *expr.AttributeExpr
  329. switch def := eval.Current().(type) {
  330. case *expr.AttributeExpr:
  331. at = def
  332. case *expr.ResultTypeExpr:
  333. at = def.AttributeExpr
  334. case *expr.MappedAttributeExpr:
  335. at = def.AttributeExpr
  336. default:
  337. eval.IncompatibleDSL()
  338. return
  339. }
  340. if at.Type != nil && !expr.IsObject(at.Type) {
  341. incompatibleAttributeType("required", at.Type.Name(), "an object")
  342. } else {
  343. if at.Validation == nil {
  344. at.Validation = &expr.ValidationExpr{}
  345. }
  346. at.Validation.AddRequired(names...)
  347. }
  348. }
  349. // incompatibleAttributeType reports an error for validations defined on
  350. // incompatible attributes (e.g. max value on string).
  351. func incompatibleAttributeType(validation, actual, expected string) {
  352. eval.ReportError("invalid %s validation definition: attribute must be %s (but type is %s)",
  353. validation, expected, actual)
  354. }