blob: 8afccbd99791bf90842f3e9e949fdb84acd13493 [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 strfmt
16
17import (
18 "encoding"
19 "reflect"
20 "strings"
21 "sync"
22 "time"
23
24 "github.com/go-openapi/errors"
25 "github.com/mitchellh/mapstructure"
26)
27
28// Default is the default formats registry
29var Default = NewSeededFormats(nil, nil)
30
31// Validator represents a validator for a string format.
32type Validator func(string) bool
33
34// Format represents a string format.
35//
36// All implementations of Format provide a string representation and text
37// marshaling/unmarshaling interface to be used by encoders (e.g. encoding/json).
38type Format interface {
39 String() string
40 encoding.TextMarshaler
41 encoding.TextUnmarshaler
42}
43
44// Registry is a registry of string formats, with a validation method.
45type Registry interface {
46 Add(string, Format, Validator) bool
47 DelByName(string) bool
48 GetType(string) (reflect.Type, bool)
49 ContainsName(string) bool
50 Validates(string, string) bool
51 Parse(string, string) (interface{}, error)
52 MapStructureHookFunc() mapstructure.DecodeHookFunc
53}
54
55type knownFormat struct {
56 Name string
57 OrigName string
58 Type reflect.Type
59 Validator Validator
60}
61
62// NameNormalizer is a function that normalizes a format name.
63type NameNormalizer func(string) string
64
65// DefaultNameNormalizer removes all dashes
66func DefaultNameNormalizer(name string) string {
67 return strings.Replace(name, "-", "", -1)
68}
69
70type defaultFormats struct {
71 sync.Mutex
72 data []knownFormat
73 normalizeName NameNormalizer
74}
75
76// NewFormats creates a new formats registry seeded with the values from the default
77func NewFormats() Registry {
78 return NewSeededFormats(Default.(*defaultFormats).data, nil)
79}
80
81// NewSeededFormats creates a new formats registry
82func NewSeededFormats(seeds []knownFormat, normalizer NameNormalizer) Registry {
83 if normalizer == nil {
84 normalizer = DefaultNameNormalizer
85 }
86 // copy here, don't modify original
87 d := append([]knownFormat(nil), seeds...)
88 return &defaultFormats{
89 data: d,
90 normalizeName: normalizer,
91 }
92}
93
94// MapStructureHookFunc is a decode hook function for mapstructure
95func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc {
96 return func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
97 if from.Kind() != reflect.String {
98 return data, nil
99 }
100 for _, v := range f.data {
101 tpe, _ := f.GetType(v.Name)
102 if to == tpe {
103 switch v.Name {
104 case "date":
105 d, err := time.Parse(RFC3339FullDate, data.(string))
106 if err != nil {
107 return nil, err
108 }
109 return Date(d), nil
110 case "datetime":
111 return ParseDateTime(data.(string))
112 case "duration":
113 dur, err := ParseDuration(data.(string))
114 if err != nil {
115 return nil, err
116 }
117 return Duration(dur), nil
118 case "uri":
119 return URI(data.(string)), nil
120 case "email":
121 return Email(data.(string)), nil
122 case "uuid":
123 return UUID(data.(string)), nil
124 case "uuid3":
125 return UUID3(data.(string)), nil
126 case "uuid4":
127 return UUID4(data.(string)), nil
128 case "uuid5":
129 return UUID5(data.(string)), nil
130 case "hostname":
131 return Hostname(data.(string)), nil
132 case "ipv4":
133 return IPv4(data.(string)), nil
134 case "ipv6":
135 return IPv6(data.(string)), nil
136 case "mac":
137 return MAC(data.(string)), nil
138 case "isbn":
139 return ISBN(data.(string)), nil
140 case "isbn10":
141 return ISBN10(data.(string)), nil
142 case "isbn13":
143 return ISBN13(data.(string)), nil
144 case "creditcard":
145 return CreditCard(data.(string)), nil
146 case "ssn":
147 return SSN(data.(string)), nil
148 case "hexcolor":
149 return HexColor(data.(string)), nil
150 case "rgbcolor":
151 return RGBColor(data.(string)), nil
152 case "byte":
153 return Base64(data.(string)), nil
154 case "password":
155 return Password(data.(string)), nil
156 default:
157 return nil, errors.InvalidTypeName(v.Name)
158 }
159 }
160 }
161 return data, nil
162 }
163}
164
165// Add adds a new format, return true if this was a new item instead of a replacement
166func (f *defaultFormats) Add(name string, strfmt Format, validator Validator) bool {
167 f.Lock()
168 defer f.Unlock()
169
170 nme := f.normalizeName(name)
171
172 tpe := reflect.TypeOf(strfmt)
173 if tpe.Kind() == reflect.Ptr {
174 tpe = tpe.Elem()
175 }
176
177 for i := range f.data {
178 v := &f.data[i]
179 if v.Name == nme {
180 v.Type = tpe
181 v.Validator = validator
182 return false
183 }
184 }
185
186 // turns out it's new after all
187 f.data = append(f.data, knownFormat{Name: nme, OrigName: name, Type: tpe, Validator: validator})
188 return true
189}
190
191// GetType gets the type for the specified name
192func (f *defaultFormats) GetType(name string) (reflect.Type, bool) {
193 f.Lock()
194 defer f.Unlock()
195 nme := f.normalizeName(name)
196 for _, v := range f.data {
197 if v.Name == nme {
198 return v.Type, true
199 }
200 }
201 return nil, false
202}
203
204// DelByName removes the format by the specified name, returns true when an item was actually removed
205func (f *defaultFormats) DelByName(name string) bool {
206 f.Lock()
207 defer f.Unlock()
208
209 nme := f.normalizeName(name)
210
211 for i, v := range f.data {
212 if v.Name == nme {
213 f.data[i] = knownFormat{} // release
214 f.data = append(f.data[:i], f.data[i+1:]...)
215 return true
216 }
217 }
218 return false
219}
220
221// DelByType removes the specified format, returns true when an item was actually removed
222func (f *defaultFormats) DelByFormat(strfmt Format) bool {
223 f.Lock()
224 defer f.Unlock()
225
226 tpe := reflect.TypeOf(strfmt)
227 if tpe.Kind() == reflect.Ptr {
228 tpe = tpe.Elem()
229 }
230
231 for i, v := range f.data {
232 if v.Type == tpe {
233 f.data[i] = knownFormat{} // release
234 f.data = append(f.data[:i], f.data[i+1:]...)
235 return true
236 }
237 }
238 return false
239}
240
241// ContainsName returns true if this registry contains the specified name
242func (f *defaultFormats) ContainsName(name string) bool {
243 f.Lock()
244 defer f.Unlock()
245 nme := f.normalizeName(name)
246 for _, v := range f.data {
247 if v.Name == nme {
248 return true
249 }
250 }
251 return false
252}
253
254// ContainsFormat returns true if this registry contains the specified format
255func (f *defaultFormats) ContainsFormat(strfmt Format) bool {
256 f.Lock()
257 defer f.Unlock()
258 tpe := reflect.TypeOf(strfmt)
259 if tpe.Kind() == reflect.Ptr {
260 tpe = tpe.Elem()
261 }
262
263 for _, v := range f.data {
264 if v.Type == tpe {
265 return true
266 }
267 }
268 return false
269}
270
271// Validates passed data against format.
272//
273// Note that the format name is automatically normalized, e.g. one may
274// use "date-time" to use the "datetime" format validator.
275func (f *defaultFormats) Validates(name, data string) bool {
276 f.Lock()
277 defer f.Unlock()
278 nme := f.normalizeName(name)
279 for _, v := range f.data {
280 if v.Name == nme {
281 return v.Validator(data)
282 }
283 }
284 return false
285}
286
287// Parse a string into the appropriate format representation type.
288//
289// E.g. parsing a string a "date" will return a Date type.
290func (f *defaultFormats) Parse(name, data string) (interface{}, error) {
291 f.Lock()
292 defer f.Unlock()
293 nme := f.normalizeName(name)
294 for _, v := range f.data {
295 if v.Name == nme {
296 nw := reflect.New(v.Type).Interface()
297 if dec, ok := nw.(encoding.TextUnmarshaler); ok {
298 if err := dec.UnmarshalText([]byte(data)); err != nil {
299 return nil, err
300 }
301 return nw, nil
302 }
303 return nil, errors.InvalidTypeName(name)
304 }
305 }
306 return nil, errors.InvalidTypeName(name)
307}