shape_validation.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // +build codegen
  2. package api
  3. import (
  4. "bytes"
  5. "fmt"
  6. "text/template"
  7. )
  8. // A ShapeValidationType is the type of validation that a shape needs
  9. type ShapeValidationType int
  10. const (
  11. // ShapeValidationRequired states the shape must be set
  12. ShapeValidationRequired = iota
  13. // ShapeValidationMinVal states the shape must have at least a number of
  14. // elements, or for numbers a minimum value
  15. ShapeValidationMinVal
  16. // ShapeValidationNested states the shape has nested values that need
  17. // to be validated
  18. ShapeValidationNested
  19. )
  20. // A ShapeValidation contains information about a shape and the type of validation
  21. // that is needed
  22. type ShapeValidation struct {
  23. // Name of the shape to be validated
  24. Name string
  25. // Reference to the shape within the context the shape is referenced
  26. Ref *ShapeRef
  27. // Type of validation needed
  28. Type ShapeValidationType
  29. }
  30. var validationGoCodeTmpls = template.Must(
  31. template.New("validationGoCodeTmpls").
  32. Funcs(template.FuncMap{
  33. "getMin": func(ref *ShapeRef) float64 {
  34. if !ref.CanBeEmpty() && ref.Shape.Min <= 0 {
  35. return 1
  36. }
  37. return ref.Shape.Min
  38. },
  39. }).
  40. Parse(`
  41. {{ define "requiredValue" -}}
  42. if s.{{ .Name }} == nil {
  43. invalidParams.Add(request.NewErrParamRequired("{{ .Name }}"))
  44. }
  45. {{- end }}
  46. {{ define "minLen" -}}
  47. {{- $min := getMin .Ref -}}
  48. if s.{{ .Name }} != nil && len(s.{{ .Name }}) < {{ $min }} {
  49. invalidParams.Add(request.NewErrParamMinLen("{{ .Name }}", {{ $min }}))
  50. }
  51. {{- end }}
  52. {{ define "minLenString" -}}
  53. {{- $min := getMin .Ref -}}
  54. if s.{{ .Name }} != nil && len(*s.{{ .Name }}) < {{ $min }} {
  55. invalidParams.Add(request.NewErrParamMinLen("{{ .Name }}", {{ $min }}))
  56. }
  57. {{- end }}
  58. {{ define "minVal" -}}
  59. {{- $min := getMin .Ref -}}
  60. if s.{{ .Name }} != nil && *s.{{ .Name }} < {{ $min }} {
  61. invalidParams.Add(request.NewErrParamMinValue("{{ .Name }}", {{ $min }}))
  62. }
  63. {{- end }}
  64. {{ define "nestedMapList" -}}
  65. if s.{{ .Name }} != nil {
  66. for i, v := range s.{{ .Name }} {
  67. if v == nil { continue }
  68. if err := v.Validate(); err != nil {
  69. invalidParams.AddNested(fmt.Sprintf("%s[%v]", "{{ .Name }}", i), err.(request.ErrInvalidParams))
  70. }
  71. }
  72. }
  73. {{- end }}
  74. {{ define "nestedStruct" -}}
  75. if s.{{ .Name }} != nil {
  76. if err := s.{{ .Name }}.Validate(); err != nil {
  77. invalidParams.AddNested("{{ .Name }}", err.(request.ErrInvalidParams))
  78. }
  79. }
  80. {{- end }}
  81. `))
  82. // GoCode returns the generated Go code for the Shape with its validation type.
  83. func (sv ShapeValidation) GoCode() string {
  84. var err error
  85. w := &bytes.Buffer{}
  86. switch sv.Type {
  87. case ShapeValidationRequired:
  88. err = validationGoCodeTmpls.ExecuteTemplate(w, "requiredValue", sv)
  89. case ShapeValidationMinVal:
  90. switch sv.Ref.Shape.Type {
  91. case "list", "map", "blob":
  92. err = validationGoCodeTmpls.ExecuteTemplate(w, "minLen", sv)
  93. case "string":
  94. err = validationGoCodeTmpls.ExecuteTemplate(w, "minLenString", sv)
  95. case "integer", "long", "float", "double":
  96. err = validationGoCodeTmpls.ExecuteTemplate(w, "minVal", sv)
  97. default:
  98. panic(fmt.Sprintf("ShapeValidation.GoCode, %s's type %s, no min value handling",
  99. sv.Name, sv.Ref.Shape.Type))
  100. }
  101. case ShapeValidationNested:
  102. switch sv.Ref.Shape.Type {
  103. case "map", "list":
  104. err = validationGoCodeTmpls.ExecuteTemplate(w, "nestedMapList", sv)
  105. default:
  106. err = validationGoCodeTmpls.ExecuteTemplate(w, "nestedStruct", sv)
  107. }
  108. default:
  109. panic(fmt.Sprintf("ShapeValidation.GoCode, %s's type %d, unknown validation type",
  110. sv.Name, sv.Type))
  111. }
  112. if err != nil {
  113. panic(fmt.Sprintf("ShapeValidation.GoCode failed, err: %v", err))
  114. }
  115. return w.String()
  116. }
  117. // A ShapeValidations is a collection of shape validations needed nested within
  118. // a parent shape
  119. type ShapeValidations []ShapeValidation
  120. var validateShapeTmpl = template.Must(template.New("ValidateShape").Parse(`
  121. // Validate inspects the fields of the type to determine if they are valid.
  122. func (s *{{ .Shape.ShapeName }}) Validate() error {
  123. invalidParams := request.ErrInvalidParams{Context: "{{ .Shape.ShapeName }}"}
  124. {{ range $_, $v := .Validations -}}
  125. {{ $v.GoCode }}
  126. {{ end }}
  127. if invalidParams.Len() > 0 {
  128. return invalidParams
  129. }
  130. return nil
  131. }
  132. `))
  133. // GoCode generates the Go code needed to perform validations for the
  134. // shape and its nested fields.
  135. func (vs ShapeValidations) GoCode(shape *Shape) string {
  136. buf := &bytes.Buffer{}
  137. validateShapeTmpl.Execute(buf, map[string]interface{}{
  138. "Shape": shape,
  139. "Validations": vs,
  140. })
  141. return buf.String()
  142. }
  143. // Has returns true or false if the ShapeValidations already contains the
  144. // the reference and validation type.
  145. func (vs ShapeValidations) Has(ref *ShapeRef, typ ShapeValidationType) bool {
  146. for _, v := range vs {
  147. if v.Ref == ref && v.Type == typ {
  148. return true
  149. }
  150. }
  151. return false
  152. }