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