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