blob: 75f612040ed863c057bd85697271c8e9c57183d5 [file] [log] [blame]
// Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package schema
import (
"fmt"
"net/url"
"reflect"
"regexp"
)
// String returns a Checker that accepts a string value only and returns
// it unprocessed.
func String() Checker {
return stringC{}
}
type stringC struct{}
func (c stringC) Coerce(v interface{}, path []string) (interface{}, error) {
if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
return reflect.ValueOf(v).String(), nil
}
return nil, error_{"string", v, path}
}
// URL returns a Checker that accepts a string value that must be parseable as a
// URL, and returns a *net.URL.
func URL() Checker {
return urlC{}
}
type urlC struct{}
func (c urlC) Coerce(v interface{}, path []string) (interface{}, error) {
if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
s := reflect.ValueOf(v).String()
u, err := url.Parse(s)
if err != nil {
return nil, error_{"valid url", s, path}
}
return u, nil
}
return nil, error_{"url string", v, path}
}
// SimpleRegexp returns a checker that accepts a string value that is
// a valid regular expression and returns it unprocessed.
func SimpleRegexp() Checker {
return sregexpC{}
}
type sregexpC struct{}
func (c sregexpC) Coerce(v interface{}, path []string) (interface{}, error) {
// XXX The regexp package happens to be extremely simple right now.
// Once exp/regexp goes mainstream, we'll have to update this
// logic to use a more widely accepted regexp subset.
if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
s := reflect.ValueOf(v).String()
_, err := regexp.Compile(s)
if err != nil {
return nil, error_{"valid regexp", s, path}
}
return v, nil
}
return nil, error_{"regexp string", v, path}
}
// UUID returns a Checker that accepts a string value only and returns
// it unprocessed.
func UUID() Checker {
return uuidC{}
}
type uuidC struct{}
var uuidregex = regexp.MustCompile(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`)
func (c uuidC) Coerce(v interface{}, path []string) (interface{}, error) {
if v != nil && reflect.TypeOf(v).Kind() == reflect.String {
uuid := reflect.ValueOf(v).String()
if uuidregex.MatchString(uuid) {
return uuid, nil
}
}
return nil, error_{"uuid", v, path}
}
// Stringified returns a checker that accepts a bool/int/float/string
// value and returns its string. Other value types may be supported by
// passing in their checkers.
func Stringified(checkers ...Checker) Checker {
return stringifiedC{
checkers: checkers,
}
}
type stringifiedC struct {
checkers []Checker
}
func (c stringifiedC) Coerce(v interface{}, path []string) (interface{}, error) {
if newStr, err := String().Coerce(v, path); err == nil {
return newStr, nil
}
_, err := OneOf(append(c.checkers,
Bool(),
Int(),
Float(),
String(),
URL(),
)...).Coerce(v, path)
if err != nil {
return nil, err
}
return fmt.Sprintf("%#v", v), nil
}
// NonEmptyString returns a Checker that only accepts non-empty strings. To
// tweak the error message, valueLabel can contain a label of the value being
// checked, e.g. "my special name". If valueLabel is "", "string" will be used
// as a label instead.
//
// Example 1:
// schema.NonEmptyString("widget").Coerce("", nil) will return an error message
// like `expected non-empty widget, got string("")`.
//
// Example 2:
// schema.NonEmptyString("").Coerce("", nil) will return an error message like
// `expected non-empty string, got string("")`.
func NonEmptyString(valueLabel string) Checker {
if valueLabel == "" {
valueLabel = "string"
}
return nonEmptyStringC{valueLabel}
}
type nonEmptyStringC struct {
valueLabel string
}
func (c nonEmptyStringC) Coerce(v interface{}, path []string) (interface{}, error) {
label := fmt.Sprintf("non-empty %s", c.valueLabel)
invalidError := error_{label, v, path}
if v == nil || reflect.TypeOf(v).Kind() != reflect.String {
return nil, invalidError
}
if stringValue := reflect.ValueOf(v).String(); stringValue != "" {
return stringValue, nil
}
return nil, invalidError
}