blob: 72c81a9988a67a59a2e2b29ed7ff59ff9df10871 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package validate
16
17import (
18 "reflect"
19 "strings"
20
21 "github.com/go-openapi/errors"
22 "github.com/go-openapi/runtime"
23 "github.com/go-openapi/spec"
24 "github.com/go-openapi/strfmt"
25 "github.com/go-openapi/swag"
26)
27
28type typeValidator struct {
29 Type spec.StringOrArray
30 Format string
31 In string
32 Path string
33}
34
35func (t *typeValidator) schemaInfoForType(data interface{}) (string, string) {
36 // internal type to JSON type with swagger 2.0 format (with go-openapi/strfmt extensions),
37 // see https://github.com/go-openapi/strfmt/blob/master/README.md
38 // TODO: this switch really is some sort of reverse lookup for formats. It should be provided by strfmt.
39 switch data.(type) {
40 case []byte, strfmt.Base64, *strfmt.Base64:
41 return "string", "byte"
42 case strfmt.CreditCard, *strfmt.CreditCard:
43 return "string", "creditcard"
44 case strfmt.Date, *strfmt.Date:
45 return "string", "date"
46 case strfmt.DateTime, *strfmt.DateTime:
47 return "string", "date-time"
48 case strfmt.Duration, *strfmt.Duration:
49 return "string", "duration"
50 case runtime.File, *runtime.File:
51 return "file", ""
52 case strfmt.Email, *strfmt.Email:
53 return "string", "email"
54 case strfmt.HexColor, *strfmt.HexColor:
55 return "string", "hexcolor"
56 case strfmt.Hostname, *strfmt.Hostname:
57 return "string", "hostname"
58 case strfmt.IPv4, *strfmt.IPv4:
59 return "string", "ipv4"
60 case strfmt.IPv6, *strfmt.IPv6:
61 return "string", "ipv6"
62 case strfmt.ISBN, *strfmt.ISBN:
63 return "string", "isbn"
64 case strfmt.ISBN10, *strfmt.ISBN10:
65 return "string", "isbn10"
66 case strfmt.ISBN13, *strfmt.ISBN13:
67 return "string", "isbn13"
68 case strfmt.MAC, *strfmt.MAC:
69 return "string", "mac"
70 case strfmt.ObjectId, *strfmt.ObjectId:
71 return "string", "bsonobjectid"
72 case strfmt.Password, *strfmt.Password:
73 return "string", "password"
74 case strfmt.RGBColor, *strfmt.RGBColor:
75 return "string", "rgbcolor"
76 case strfmt.SSN, *strfmt.SSN:
77 return "string", "ssn"
78 case strfmt.URI, *strfmt.URI:
79 return "string", "uri"
80 case strfmt.UUID, *strfmt.UUID:
81 return "string", "uuid"
82 case strfmt.UUID3, *strfmt.UUID3:
83 return "string", "uuid3"
84 case strfmt.UUID4, *strfmt.UUID4:
85 return "string", "uuid4"
86 case strfmt.UUID5, *strfmt.UUID5:
87 return "string", "uuid5"
88 // TODO: missing binary (io.ReadCloser)
89 // TODO: missing json.Number
90 default:
91 val := reflect.ValueOf(data)
92 tpe := val.Type()
93 switch tpe.Kind() {
94 case reflect.Bool:
95 return "boolean", ""
96 case reflect.String:
97 return "string", ""
98 case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
99 // NOTE: that is the spec. With go-openapi, is that not uint32 for unsigned integers?
100 return "integer", "int32"
101 case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64:
102 return "integer", "int64"
103 case reflect.Float32:
104 // NOTE: is that not "float"?
105 return "number", "float32"
106 case reflect.Float64:
107 // NOTE: is that not "double"?
108 return "number", "float64"
109 // NOTE: go arrays (reflect.Array) are not supported (fixed length)
110 case reflect.Slice:
111 return "array", ""
112 case reflect.Map, reflect.Struct:
113 return "object", ""
114 case reflect.Interface:
115 // What to do here?
116 panic("dunno what to do here")
117 case reflect.Ptr:
118 return t.schemaInfoForType(reflect.Indirect(val).Interface())
119 }
120 }
121 return "", ""
122}
123
124func (t *typeValidator) SetPath(path string) {
125 t.Path = path
126}
127
128func (t *typeValidator) Applies(source interface{}, kind reflect.Kind) bool {
129 // typeValidator applies to Schema, Parameter and Header objects
130 stpe := reflect.TypeOf(source)
131 r := (len(t.Type) > 0 || t.Format != "") && (stpe == specSchemaType || stpe == specParameterType || stpe == specHeaderType)
132 debugLog("type validator for %q applies %t for %T (kind: %v)\n", t.Path, r, source, kind)
133 return r
134}
135
136func (t *typeValidator) Validate(data interface{}) *Result {
137 result := new(Result)
138 result.Inc()
139 if data == nil || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(data)), reflect.ValueOf(data)) {
140 // nil or zero value for the passed structure require Type: null
141 if len(t.Type) > 0 && !t.Type.Contains("null") { // TODO: if a property is not required it also passes this
142 return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), "null"))
143 }
144 return result
145 }
146
147 // check if the type matches, should be used in every validator chain as first item
148 val := reflect.Indirect(reflect.ValueOf(data))
149 kind := val.Kind()
150
151 // infer schema type (JSON) and format from passed data type
152 schType, format := t.schemaInfoForType(data)
153
154 debugLog("path: %s, schType: %s, format: %s, expType: %s, expFmt: %s, kind: %s", t.Path, schType, format, t.Type, t.Format, val.Kind().String())
155
156 // check numerical types
157 // TODO: check unsigned ints
158 // TODO: check json.Number (see schema.go)
159 isLowerInt := t.Format == "int64" && format == "int32"
160 isLowerFloat := t.Format == "float64" && format == "float32"
161 isFloatInt := schType == "number" && swag.IsFloat64AJSONInteger(val.Float()) && t.Type.Contains("integer")
162 isIntFloat := schType == "integer" && t.Type.Contains("number")
163
164 if kind != reflect.String && kind != reflect.Slice && t.Format != "" && !(t.Type.Contains(schType) || format == t.Format || isFloatInt || isIntFloat || isLowerInt || isLowerFloat) {
165 // TODO: test case
166 return errorHelp.sErr(errors.InvalidType(t.Path, t.In, t.Format, format))
167 }
168
169 if !(t.Type.Contains("number") || t.Type.Contains("integer")) && t.Format != "" && (kind == reflect.String || kind == reflect.Slice) {
170 return result
171 }
172
173 if !(t.Type.Contains(schType) || isFloatInt || isIntFloat) {
174 return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), schType))
175 }
176 return result
177}