327 lines
7.9 KiB
Go
327 lines
7.9 KiB
Go
// Copyright 2015 go-swagger maintainers
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package strfmt
|
|
|
|
import (
|
|
"encoding"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-openapi/errors"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// Default is the default formats registry
|
|
var Default = NewSeededFormats(nil, nil)
|
|
|
|
// Validator represents a validator for a string format.
|
|
type Validator func(string) bool
|
|
|
|
// Format represents a string format.
|
|
//
|
|
// All implementations of Format provide a string representation and text
|
|
// marshaling/unmarshaling interface to be used by encoders (e.g. encoding/json).
|
|
type Format interface {
|
|
String() string
|
|
encoding.TextMarshaler
|
|
encoding.TextUnmarshaler
|
|
}
|
|
|
|
// Registry is a registry of string formats, with a validation method.
|
|
type Registry interface {
|
|
Add(string, Format, Validator) bool
|
|
DelByName(string) bool
|
|
GetType(string) (reflect.Type, bool)
|
|
ContainsName(string) bool
|
|
Validates(string, string) bool
|
|
Parse(string, string) (interface{}, error)
|
|
MapStructureHookFunc() mapstructure.DecodeHookFunc
|
|
}
|
|
|
|
type knownFormat struct {
|
|
Name string
|
|
OrigName string
|
|
Type reflect.Type
|
|
Validator Validator
|
|
}
|
|
|
|
// NameNormalizer is a function that normalizes a format name.
|
|
type NameNormalizer func(string) string
|
|
|
|
// DefaultNameNormalizer removes all dashes
|
|
func DefaultNameNormalizer(name string) string {
|
|
return strings.ReplaceAll(name, "-", "")
|
|
}
|
|
|
|
type defaultFormats struct {
|
|
sync.Mutex
|
|
data []knownFormat
|
|
normalizeName NameNormalizer
|
|
}
|
|
|
|
// NewFormats creates a new formats registry seeded with the values from the default
|
|
func NewFormats() Registry {
|
|
//nolint:forcetypeassert
|
|
return NewSeededFormats(Default.(*defaultFormats).data, nil)
|
|
}
|
|
|
|
// NewSeededFormats creates a new formats registry
|
|
func NewSeededFormats(seeds []knownFormat, normalizer NameNormalizer) Registry {
|
|
if normalizer == nil {
|
|
normalizer = DefaultNameNormalizer
|
|
}
|
|
// copy here, don't modify original
|
|
d := append([]knownFormat(nil), seeds...)
|
|
return &defaultFormats{
|
|
data: d,
|
|
normalizeName: normalizer,
|
|
}
|
|
}
|
|
|
|
// MapStructureHookFunc is a decode hook function for mapstructure
|
|
func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc { //nolint:gocyclo,cyclop
|
|
return func(from reflect.Type, to reflect.Type, obj interface{}) (interface{}, error) {
|
|
if from.Kind() != reflect.String {
|
|
return obj, nil
|
|
}
|
|
data, ok := obj.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to cast %+v to string", obj)
|
|
}
|
|
|
|
for _, v := range f.data {
|
|
tpe, _ := f.GetType(v.Name)
|
|
if to == tpe {
|
|
switch v.Name {
|
|
case "date":
|
|
d, err := time.ParseInLocation(RFC3339FullDate, data, DefaultTimeLocation)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Date(d), nil
|
|
case "datetime":
|
|
input := data
|
|
if len(input) == 0 {
|
|
return nil, fmt.Errorf("empty string is an invalid datetime format")
|
|
}
|
|
return ParseDateTime(input)
|
|
case "duration":
|
|
dur, err := ParseDuration(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Duration(dur), nil
|
|
case "uri":
|
|
return URI(data), nil
|
|
case "email":
|
|
return Email(data), nil
|
|
case "uuid":
|
|
return UUID(data), nil
|
|
case "uuid3":
|
|
return UUID3(data), nil
|
|
case "uuid4":
|
|
return UUID4(data), nil
|
|
case "uuid5":
|
|
return UUID5(data), nil
|
|
case "hostname":
|
|
return Hostname(data), nil
|
|
case "ipv4":
|
|
return IPv4(data), nil
|
|
case "ipv6":
|
|
return IPv6(data), nil
|
|
case "cidr":
|
|
return CIDR(data), nil
|
|
case "mac":
|
|
return MAC(data), nil
|
|
case "isbn":
|
|
return ISBN(data), nil
|
|
case "isbn10":
|
|
return ISBN10(data), nil
|
|
case "isbn13":
|
|
return ISBN13(data), nil
|
|
case "creditcard":
|
|
return CreditCard(data), nil
|
|
case "ssn":
|
|
return SSN(data), nil
|
|
case "hexcolor":
|
|
return HexColor(data), nil
|
|
case "rgbcolor":
|
|
return RGBColor(data), nil
|
|
case "byte":
|
|
return Base64(data), nil
|
|
case "password":
|
|
return Password(data), nil
|
|
case "ulid":
|
|
ulid, err := ParseULID(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ulid, nil
|
|
default:
|
|
return nil, errors.InvalidTypeName(v.Name)
|
|
}
|
|
}
|
|
}
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// Add adds a new format, return true if this was a new item instead of a replacement
|
|
func (f *defaultFormats) Add(name string, strfmt Format, validator Validator) bool {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
|
|
nme := f.normalizeName(name)
|
|
|
|
tpe := reflect.TypeOf(strfmt)
|
|
if tpe.Kind() == reflect.Ptr {
|
|
tpe = tpe.Elem()
|
|
}
|
|
|
|
for i := range f.data {
|
|
v := &f.data[i]
|
|
if v.Name == nme {
|
|
v.Type = tpe
|
|
v.Validator = validator
|
|
return false
|
|
}
|
|
}
|
|
|
|
// turns out it's new after all
|
|
f.data = append(f.data, knownFormat{Name: nme, OrigName: name, Type: tpe, Validator: validator})
|
|
return true
|
|
}
|
|
|
|
// GetType gets the type for the specified name
|
|
func (f *defaultFormats) GetType(name string) (reflect.Type, bool) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
nme := f.normalizeName(name)
|
|
for _, v := range f.data {
|
|
if v.Name == nme {
|
|
return v.Type, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// DelByName removes the format by the specified name, returns true when an item was actually removed
|
|
func (f *defaultFormats) DelByName(name string) bool {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
|
|
nme := f.normalizeName(name)
|
|
|
|
for i, v := range f.data {
|
|
if v.Name == nme {
|
|
f.data[i] = knownFormat{} // release
|
|
f.data = append(f.data[:i], f.data[i+1:]...)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DelByFormat removes the specified format, returns true when an item was actually removed
|
|
func (f *defaultFormats) DelByFormat(strfmt Format) bool {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
|
|
tpe := reflect.TypeOf(strfmt)
|
|
if tpe.Kind() == reflect.Ptr {
|
|
tpe = tpe.Elem()
|
|
}
|
|
|
|
for i, v := range f.data {
|
|
if v.Type == tpe {
|
|
f.data[i] = knownFormat{} // release
|
|
f.data = append(f.data[:i], f.data[i+1:]...)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ContainsName returns true if this registry contains the specified name
|
|
func (f *defaultFormats) ContainsName(name string) bool {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
nme := f.normalizeName(name)
|
|
for _, v := range f.data {
|
|
if v.Name == nme {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ContainsFormat returns true if this registry contains the specified format
|
|
func (f *defaultFormats) ContainsFormat(strfmt Format) bool {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
tpe := reflect.TypeOf(strfmt)
|
|
if tpe.Kind() == reflect.Ptr {
|
|
tpe = tpe.Elem()
|
|
}
|
|
|
|
for _, v := range f.data {
|
|
if v.Type == tpe {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Validates passed data against format.
|
|
//
|
|
// Note that the format name is automatically normalized, e.g. one may
|
|
// use "date-time" to use the "datetime" format validator.
|
|
func (f *defaultFormats) Validates(name, data string) bool {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
nme := f.normalizeName(name)
|
|
for _, v := range f.data {
|
|
if v.Name == nme {
|
|
return v.Validator(data)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Parse a string into the appropriate format representation type.
|
|
//
|
|
// E.g. parsing a string a "date" will return a Date type.
|
|
func (f *defaultFormats) Parse(name, data string) (interface{}, error) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
nme := f.normalizeName(name)
|
|
for _, v := range f.data {
|
|
if v.Name == nme {
|
|
nw := reflect.New(v.Type).Interface()
|
|
if dec, ok := nw.(encoding.TextUnmarshaler); ok {
|
|
if err := dec.UnmarshalText([]byte(data)); err != nil {
|
|
return nil, err
|
|
}
|
|
return nw, nil
|
|
}
|
|
return nil, errors.InvalidTypeName(name)
|
|
}
|
|
}
|
|
return nil, errors.InvalidTypeName(name)
|
|
}
|