shape_validation.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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(template.New("validationGoCodeTmpls").Parse(`
  31. {{ define "requiredValue" -}}
  32. if s.{{ .Name }} == nil {
  33. invalidParams.Add(request.NewErrParamRequired("{{ .Name }}"))
  34. }
  35. {{- end }}
  36. {{ define "minLen" -}}
  37. if s.{{ .Name }} != nil && len(s.{{ .Name }}) < {{ .Ref.Shape.Min }} {
  38. invalidParams.Add(request.NewErrParamMinLen("{{ .Name }}", {{ .Ref.Shape.Min }}))
  39. }
  40. {{- end }}
  41. {{ define "minLenString" -}}
  42. if s.{{ .Name }} != nil && len(*s.{{ .Name }}) < {{ .Ref.Shape.Min }} {
  43. invalidParams.Add(request.NewErrParamMinLen("{{ .Name }}", {{ .Ref.Shape.Min }}))
  44. }
  45. {{- end }}
  46. {{ define "minVal" -}}
  47. if s.{{ .Name }} != nil && *s.{{ .Name }} < {{ .Ref.Shape.Min }} {
  48. invalidParams.Add(request.NewErrParamMinValue("{{ .Name }}", {{ .Ref.Shape.Min }}))
  49. }
  50. {{- end }}
  51. {{ define "nestedMapList" -}}
  52. if s.{{ .Name }} != nil {
  53. for i, v := range s.{{ .Name }} {
  54. if v == nil { continue }
  55. if err := v.Validate(); err != nil {
  56. invalidParams.AddNested(fmt.Sprintf("%s[%v]", "{{ .Name }}", i), err.(request.ErrInvalidParams))
  57. }
  58. }
  59. }
  60. {{- end }}
  61. {{ define "nestedStruct" -}}
  62. if s.{{ .Name }} != nil {
  63. if err := s.{{ .Name }}.Validate(); err != nil {
  64. invalidParams.AddNested("{{ .Name }}", err.(request.ErrInvalidParams))
  65. }
  66. }
  67. {{- end }}
  68. `))
  69. // GoCode returns the generated Go code for the Shape with its validation type.
  70. func (sv ShapeValidation) GoCode() string {
  71. var err error
  72. w := &bytes.Buffer{}
  73. switch sv.Type {
  74. case ShapeValidationRequired:
  75. err = validationGoCodeTmpls.ExecuteTemplate(w, "requiredValue", sv)
  76. case ShapeValidationMinVal:
  77. switch sv.Ref.Shape.Type {
  78. case "list", "map", "blob":
  79. err = validationGoCodeTmpls.ExecuteTemplate(w, "minLen", sv)
  80. case "string":
  81. err = validationGoCodeTmpls.ExecuteTemplate(w, "minLenString", sv)
  82. case "integer", "long", "float", "double":
  83. err = validationGoCodeTmpls.ExecuteTemplate(w, "minVal", sv)
  84. default:
  85. panic(fmt.Sprintf("ShapeValidation.GoCode, %s's type %s, no min value handling",
  86. sv.Name, sv.Ref.Shape.Type))
  87. }
  88. case ShapeValidationNested:
  89. switch sv.Ref.Shape.Type {
  90. case "map", "list":
  91. err = validationGoCodeTmpls.ExecuteTemplate(w, "nestedMapList", sv)
  92. default:
  93. err = validationGoCodeTmpls.ExecuteTemplate(w, "nestedStruct", sv)
  94. }
  95. default:
  96. panic(fmt.Sprintf("ShapeValidation.GoCode, %s's type %d, unknown validation type",
  97. sv.Name, sv.Type))
  98. }
  99. if err != nil {
  100. panic(fmt.Sprintf("ShapeValidation.GoCode failed, err: %v", err))
  101. }
  102. return w.String()
  103. }
  104. // A ShapeValidations is a collection of shape validations needed nested within
  105. // a parent shape
  106. type ShapeValidations []ShapeValidation
  107. var validateShapeTmpl = template.Must(template.New("ValidateShape").Parse(`
  108. // Validate inspects the fields of the type to determine if they are valid.
  109. func (s *{{ .Shape.ShapeName }}) Validate() error {
  110. invalidParams := request.ErrInvalidParams{Context: "{{ .Shape.ShapeName }}"}
  111. {{ range $_, $v := .Validations -}}
  112. {{ $v.GoCode }}
  113. {{ end }}
  114. if invalidParams.Len() > 0 {
  115. return invalidParams
  116. }
  117. return nil
  118. }
  119. `))
  120. // GoCode generates the Go code needed to perform validations for the
  121. // shape and its nested fields.
  122. func (vs ShapeValidations) GoCode(shape *Shape) string {
  123. buf := &bytes.Buffer{}
  124. validateShapeTmpl.Execute(buf, map[string]interface{}{
  125. "Shape": shape,
  126. "Validations": vs,
  127. })
  128. return buf.String()
  129. }
  130. // Has returns true or false if the ShapeValidations already contains the
  131. // the reference and validation type.
  132. func (vs ShapeValidations) Has(ref *ShapeRef, typ ShapeValidationType) bool {
  133. for _, v := range vs {
  134. if v.Ref == ref && v.Type == typ {
  135. return true
  136. }
  137. }
  138. return false
  139. }