// Copyright 2016 Pulumi, Inc. All rights reserved. package resource import ( "fmt" "reflect" "github.com/pulumi/coconut/pkg/tokens" ) // PropertyKey is the name of a property. type PropertyKey tokens.Name // PropertyMap is a simple map keyed by property name with "JSON-like" values. type PropertyMap map[PropertyKey]PropertyValue // PropertyValue is the value of a property, limited to a select few types (see below). type PropertyValue struct { V interface{} } type ReqError struct { K PropertyKey } func IsReqError(err error) bool { _, isreq := err.(*ReqError) return isreq } func (err *ReqError) Error() string { return fmt.Sprintf("required property '%v' is missing", err.K) } // BoolOrErr checks that the given property has the type bool, issuing an error if not; req indicates if required. func (m PropertyMap) BoolOrErr(k PropertyKey, req bool) (*bool, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsBool() { return nil, fmt.Errorf("property '%v' is not a bool (%v)", k, reflect.TypeOf(v.V)) } b := v.BoolValue() return &b, nil } else if req { return nil, &ReqError{k} } return nil, nil } // NumberOrErr checks that the given property has the type float64, issuing an error if not; req indicates if required. func (m PropertyMap) NumberOrErr(k PropertyKey, req bool) (*float64, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsNumber() { return nil, fmt.Errorf("property '%v' is not a number (%v)", k, reflect.TypeOf(v.V)) } n := v.NumberValue() return &n, nil } else if req { return nil, &ReqError{k} } return nil, nil } // StringOrErr checks that the given property has the type string, issuing an error if not; req indicates if required. func (m PropertyMap) StringOrErr(k PropertyKey, req bool) (*string, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsString() { return nil, fmt.Errorf("property '%v' is not a string (%v)", k, reflect.TypeOf(v.V)) } s := v.StringValue() return &s, nil } else if req { return nil, &ReqError{k} } return nil, nil } // ArrayOrErr checks that the given property has the type array, issuing an error if not; req indicates if required. func (m PropertyMap) ArrayOrErr(k PropertyKey, req bool) (*[]PropertyValue, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsArray() { return nil, fmt.Errorf("property '%v' is not an array (%v)", k, reflect.TypeOf(v.V)) } a := v.ArrayValue() return &a, nil } else if req { return nil, &ReqError{k} } return nil, nil } // ObjectArrayOrErr ensures a property is an array of objects, issuing an error if not; req indicates if required. func (m PropertyMap) ObjectArrayOrErr(k PropertyKey, req bool) (*[]PropertyMap, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsArray() { return nil, fmt.Errorf("property '%v' is not an array (%v)", k, reflect.TypeOf(v.V)) } a := v.ArrayValue() var objs []PropertyMap for i, e := range a { if e.IsObject() { objs = append(objs, e.ObjectValue()) } else { return nil, fmt.Errorf( "property '%v' array element %v is not an object (%v)", k, i, reflect.TypeOf(e)) } } return &objs, nil } else if req { return nil, &ReqError{k} } return nil, nil } // StringArrayOrErr ensures a property is an array of strings, issuing an error if not; req indicates if required. func (m PropertyMap) StringArrayOrErr(k PropertyKey, req bool) (*[]string, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsArray() { return nil, fmt.Errorf("property '%v' is not an array (%v)", k, reflect.TypeOf(v.V)) } a := v.ArrayValue() var strs []string for i, e := range a { if e.IsString() { strs = append(strs, e.StringValue()) } else { return nil, fmt.Errorf( "property '%v' array element %v is not a string (%v)", k, i, reflect.TypeOf(e)) } } return &strs, nil } else if req { return nil, &ReqError{k} } return nil, nil } // ObjectOrErr checks that the given property is an object, issuing an error if not; req indicates if required. func (m PropertyMap) ObjectOrErr(k PropertyKey, req bool) (*PropertyMap, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsObject() { return nil, fmt.Errorf("property '%v' is not an object (%v)", k, reflect.TypeOf(v.V)) } o := v.ObjectValue() return &o, nil } else if req { return nil, &ReqError{k} } return nil, nil } // ResourceOrErr checks that the given property is a resource, issuing an error if not; req indicates if required. func (m PropertyMap) ResourceOrErr(k PropertyKey, req bool) (*Moniker, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsResource() { return nil, fmt.Errorf("property '%v' is not an object (%v)", k, reflect.TypeOf(v.V)) } m := v.ResourceValue() return &m, nil } else if req { return nil, &ReqError{k} } return nil, nil } // ReqBoolOrErr checks that the given property exists and has the type bool. func (m PropertyMap) ReqBoolOrErr(k PropertyKey) (bool, error) { b, err := m.BoolOrErr(k, true) if err != nil { return false, err } return *b, nil } // ReqNumberOrErr checks that the given property exists and has the type float64. func (m PropertyMap) ReqNumberOrErr(k PropertyKey) (float64, error) { n, err := m.NumberOrErr(k, true) if err != nil { return 0, err } return *n, nil } // ReqStringOrErr checks that the given property exists and has the type string. func (m PropertyMap) ReqStringOrErr(k PropertyKey) (string, error) { s, err := m.StringOrErr(k, true) if err != nil { return "", err } return *s, nil } // ReqArrayOrErr checks that the given property exists and has the type array. func (m PropertyMap) ReqArrayOrErr(k PropertyKey) ([]PropertyValue, error) { a, err := m.ArrayOrErr(k, true) if err != nil { return nil, err } return *a, nil } // ReqObjectArrayOrErr checks that the given property exists and has the type array of objects. func (m PropertyMap) ReqObjectArrayOrErr(k PropertyKey) ([]PropertyMap, error) { a, err := m.ObjectArrayOrErr(k, true) if err != nil { return nil, err } return *a, nil } // ReqStringArrayOrErr checks that the given property exists and has the type array of objects. func (m PropertyMap) ReqStringArrayOrErr(k PropertyKey) ([]string, error) { a, err := m.StringArrayOrErr(k, true) if err != nil { return nil, err } return *a, nil } // ReqObjectOrErr checks that the given property exists and has the type object. func (m PropertyMap) ReqObjectOrErr(k PropertyKey) (PropertyMap, error) { o, err := m.ObjectOrErr(k, true) if err != nil { return nil, err } return *o, nil } // ReqResourceOrErr checks that the given property exists and has the type moniker. func (m PropertyMap) ReqResourceOrErr(k PropertyKey) (Moniker, error) { r, err := m.ResourceOrErr(k, true) if err != nil { return Moniker(""), err } return *r, nil } // OptBoolOrErr checks that the given property has the type bool, if it exists. func (m PropertyMap) OptBoolOrErr(k PropertyKey) (*bool, error) { return m.BoolOrErr(k, false) } // OptNumberOrErr checks that the given property has the type float64, if it exists. func (m PropertyMap) OptNumberOrErr(k PropertyKey) (*float64, error) { return m.NumberOrErr(k, false) } // OptStringOrErr checks that the given property has the type string, if it exists. func (m PropertyMap) OptStringOrErr(k PropertyKey) (*string, error) { return m.StringOrErr(k, false) } // OptArrayOrErr checks that the given property has the type array, if it exists. func (m PropertyMap) OptArrayOrErr(k PropertyKey) (*[]PropertyValue, error) { return m.ArrayOrErr(k, false) } // OptObjectArrayOrErr checks that the given property has the type array of objects, if it exists. func (m PropertyMap) OptObjectArrayOrErr(k PropertyKey) (*[]PropertyMap, error) { return m.ObjectArrayOrErr(k, false) } // OptStringArrayOrErr checks that the given property has the type array of objects, if it exists. func (m PropertyMap) OptStringArrayOrErr(k PropertyKey) (*[]string, error) { return m.StringArrayOrErr(k, false) } // OptObjectOrErr checks that the given property has the type object, if it exists. func (m PropertyMap) OptObjectOrErr(k PropertyKey) (*PropertyMap, error) { return m.ObjectOrErr(k, false) } // OptResourceOrErr checks that the given property has the type moniker, if it exists. func (m PropertyMap) OptResourceOrErr(k PropertyKey) (*Moniker, error) { return m.ResourceOrErr(k, false) } // AllResources finds all resource monikers, transitively throughout the property map, and returns them. func (props PropertyMap) AllResources() map[Moniker]bool { monikers := make(map[Moniker]bool) for _, k := range StablePropertyKeys(props) { for m, v := range props[k].AllResources() { monikers[m] = v } } return monikers } // ReplaceResources finds all resources and lets an updater function update them if necessary. This is often used // during a "replacement"-style updated, to replace all monikers of a certain value with another. func (props PropertyMap) ReplaceResources(updater func(Moniker) Moniker) PropertyMap { result := make(PropertyMap) for _, k := range StablePropertyKeys(props) { result[k] = props[k].ReplaceResources(updater) } return result } func NewPropertyNull() PropertyValue { return PropertyValue{nil} } func NewPropertyBool(v bool) PropertyValue { return PropertyValue{v} } func NewPropertyNumber(v float64) PropertyValue { return PropertyValue{v} } func NewPropertyString(v string) PropertyValue { return PropertyValue{v} } func NewPropertyArray(v []PropertyValue) PropertyValue { return PropertyValue{v} } func NewPropertyObject(v PropertyMap) PropertyValue { return PropertyValue{v} } func NewPropertyResource(v Moniker) PropertyValue { return PropertyValue{v} } func (v PropertyValue) BoolValue() bool { return v.V.(bool) } func (v PropertyValue) NumberValue() float64 { return v.V.(float64) } func (v PropertyValue) StringValue() string { return v.V.(string) } func (v PropertyValue) ArrayValue() []PropertyValue { return v.V.([]PropertyValue) } func (v PropertyValue) ObjectValue() PropertyMap { return v.V.(PropertyMap) } func (v PropertyValue) ResourceValue() Moniker { return v.V.(Moniker) } func (b PropertyValue) IsNull() bool { return b.V == nil } func (b PropertyValue) IsBool() bool { _, is := b.V.(bool) return is } func (b PropertyValue) IsNumber() bool { _, is := b.V.(float64) return is } func (b PropertyValue) IsString() bool { _, is := b.V.(string) return is } func (b PropertyValue) IsArray() bool { _, is := b.V.([]PropertyValue) return is } func (b PropertyValue) IsObject() bool { _, is := b.V.(PropertyMap) return is } func (b PropertyValue) IsResource() bool { _, is := b.V.(Moniker) return is } // AllResources finds all resource monikers, transitively throughout the property value, and returns them. func (v PropertyValue) AllResources() map[Moniker]bool { monikers := make(map[Moniker]bool) if v.IsResource() { monikers[v.ResourceValue()] = true } else if v.IsArray() { for _, elem := range v.ArrayValue() { for m, v := range elem.AllResources() { monikers[m] = v } } } else if v.IsObject() { for m, v := range v.ObjectValue().AllResources() { monikers[m] = v } } return monikers } // ReplaceResources finds all resources and lets an updater function update them if necessary. This is often used // during a "replacement"-style updated, to replace all monikers of a certain value with another. func (v PropertyValue) ReplaceResources(updater func(Moniker) Moniker) PropertyValue { if v.IsResource() { m := v.ResourceValue() return NewPropertyResource(updater(m)) } else if v.IsArray() { arr := v.ArrayValue() elems := make([]PropertyValue, len(arr)) for i, elem := range arr { elems[i] = elem.ReplaceResources(updater) } return NewPropertyArray(elems) } else if v.IsObject() { rep := v.ObjectValue().ReplaceResources(updater) return NewPropertyObject(rep) } return v }