blob: 75f612040ed863c057bd85697271c8e9c57183d5 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2015 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package schema
5
6import (
7 "fmt"
8 "net/url"
9 "reflect"
10 "regexp"
11)
12
13// String returns a Checker that accepts a string value only and returns
14// it unprocessed.
15func String() Checker {
16 return stringC{}
17}
18
19type stringC struct{}
20
21func (c stringC) Coerce(v interface{}, path []string) (interface{}, error) {
22 if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
23 return reflect.ValueOf(v).String(), nil
24 }
25 return nil, error_{"string", v, path}
26}
27
28// URL returns a Checker that accepts a string value that must be parseable as a
29// URL, and returns a *net.URL.
30func URL() Checker {
31 return urlC{}
32}
33
34type urlC struct{}
35
36func (c urlC) Coerce(v interface{}, path []string) (interface{}, error) {
37 if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
38 s := reflect.ValueOf(v).String()
39 u, err := url.Parse(s)
40 if err != nil {
41 return nil, error_{"valid url", s, path}
42 }
43 return u, nil
44 }
45 return nil, error_{"url string", v, path}
46}
47
48// SimpleRegexp returns a checker that accepts a string value that is
49// a valid regular expression and returns it unprocessed.
50func SimpleRegexp() Checker {
51 return sregexpC{}
52}
53
54type sregexpC struct{}
55
56func (c sregexpC) Coerce(v interface{}, path []string) (interface{}, error) {
57 // XXX The regexp package happens to be extremely simple right now.
58 // Once exp/regexp goes mainstream, we'll have to update this
59 // logic to use a more widely accepted regexp subset.
60 if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
61 s := reflect.ValueOf(v).String()
62 _, err := regexp.Compile(s)
63 if err != nil {
64 return nil, error_{"valid regexp", s, path}
65 }
66 return v, nil
67 }
68 return nil, error_{"regexp string", v, path}
69}
70
71// UUID returns a Checker that accepts a string value only and returns
72// it unprocessed.
73func UUID() Checker {
74 return uuidC{}
75}
76
77type uuidC struct{}
78
79var uuidregex = regexp.MustCompile(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`)
80
81func (c uuidC) Coerce(v interface{}, path []string) (interface{}, error) {
82 if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
83 uuid := reflect.ValueOf(v).String()
84 if uuidregex.MatchString(uuid) {
85 return uuid, nil
86 }
87 }
88 return nil, error_{"uuid", v, path}
89}
90
91// Stringified returns a checker that accepts a bool/int/float/string
92// value and returns its string. Other value types may be supported by
93// passing in their checkers.
94func Stringified(checkers ...Checker) Checker {
95 return stringifiedC{
96 checkers: checkers,
97 }
98}
99
100type stringifiedC struct {
101 checkers []Checker
102}
103
104func (c stringifiedC) Coerce(v interface{}, path []string) (interface{}, error) {
105 if newStr, err := String().Coerce(v, path); err == nil {
106 return newStr, nil
107 }
108 _, err := OneOf(append(c.checkers,
109 Bool(),
110 Int(),
111 Float(),
112 String(),
113 URL(),
114 )...).Coerce(v, path)
115 if err != nil {
116 return nil, err
117 }
118 return fmt.Sprintf("%#v", v), nil
119}
120
121// NonEmptyString returns a Checker that only accepts non-empty strings. To
122// tweak the error message, valueLabel can contain a label of the value being
123// checked, e.g. "my special name". If valueLabel is "", "string" will be used
124// as a label instead.
125//
126// Example 1:
127// schema.NonEmptyString("widget").Coerce("", nil) will return an error message
128// like `expected non-empty widget, got string("")`.
129//
130// Example 2:
131// schema.NonEmptyString("").Coerce("", nil) will return an error message like
132// `expected non-empty string, got string("")`.
133func NonEmptyString(valueLabel string) Checker {
134 if valueLabel == "" {
135 valueLabel = "string"
136 }
137 return nonEmptyStringC{valueLabel}
138}
139
140type nonEmptyStringC struct {
141 valueLabel string
142}
143
144func (c nonEmptyStringC) Coerce(v interface{}, path []string) (interface{}, error) {
145 label := fmt.Sprintf("non-empty %s", c.valueLabel)
146 invalidError := error_{label, v, path}
147
148 if v == nil || reflect.TypeOf(v).Kind() != reflect.String {
149 return nil, invalidError
150 }
151 if stringValue := reflect.ValueOf(v).String(); stringValue != "" {
152 return stringValue, nil
153 }
154 return nil, invalidError
155}