shape_validation.go 4.4 KB

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