| package function |
| |
| import ( |
| "fmt" |
| |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| // Function represents a function. This is the main type in this package. |
| type Function struct { |
| spec *Spec |
| } |
| |
| // Spec is the specification of a function, used to instantiate |
| // a new Function. |
| type Spec struct { |
| // Params is a description of the positional parameters for the function. |
| // The standard checking logic rejects any calls that do not provide |
| // arguments conforming to this definition, freeing the function |
| // implementer from dealing with such inconsistencies. |
| Params []Parameter |
| |
| // VarParam is an optional specification of additional "varargs" the |
| // function accepts. If this is non-nil then callers may provide an |
| // arbitrary number of additional arguments (after those matching with |
| // the fixed parameters in Params) that conform to the given specification, |
| // which will appear as additional values in the slices of values |
| // provided to the type and implementation functions. |
| VarParam *Parameter |
| |
| // Type is the TypeFunc that decides the return type of the function |
| // given its arguments, which may be Unknown. See the documentation |
| // of TypeFunc for more information. |
| // |
| // Use StaticReturnType if the function's return type does not vary |
| // depending on its arguments. |
| Type TypeFunc |
| |
| // Impl is the ImplFunc that implements the function's behavior. |
| // |
| // Functions are expected to behave as pure functions, and not create |
| // any visible side-effects. |
| // |
| // If a TypeFunc is also provided, the value returned from Impl *must* |
| // conform to the type it returns, or a call to the function will panic. |
| Impl ImplFunc |
| } |
| |
| // New creates a new function with the given specification. |
| // |
| // After passing a Spec to this function, the caller must no longer read from |
| // or mutate it. |
| func New(spec *Spec) Function { |
| f := Function{ |
| spec: spec, |
| } |
| return f |
| } |
| |
| // TypeFunc is a callback type for determining the return type of a function |
| // given its arguments. |
| // |
| // Any of the values passed to this function may be unknown, even if the |
| // parameters are not configured to accept unknowns. |
| // |
| // If any of the given values are *not* unknown, the TypeFunc may use the |
| // values for pre-validation and for choosing the return type. For example, |
| // a hypothetical JSON-unmarshalling function could return |
| // cty.DynamicPseudoType if the given JSON string is unknown, but return |
| // a concrete type based on the JSON structure if the JSON string is already |
| // known. |
| type TypeFunc func(args []cty.Value) (cty.Type, error) |
| |
| // ImplFunc is a callback type for the main implementation of a function. |
| // |
| // "args" are the values for the arguments, and this slice will always be at |
| // least as long as the argument definition slice for the function. |
| // |
| // "retType" is the type returned from the Type callback, included as a |
| // convenience to avoid the need to re-compute the return type for generic |
| // functions whose return type is a function of the arguments. |
| type ImplFunc func(args []cty.Value, retType cty.Type) (cty.Value, error) |
| |
| // StaticReturnType returns a TypeFunc that always returns the given type. |
| // |
| // This is provided as a convenience for defining a function whose return |
| // type does not depend on the argument types. |
| func StaticReturnType(ty cty.Type) TypeFunc { |
| return func([]cty.Value) (cty.Type, error) { |
| return ty, nil |
| } |
| } |
| |
| // ReturnType returns the return type of a function given a set of candidate |
| // argument types, or returns an error if the given types are unacceptable. |
| // |
| // If the caller already knows values for at least some of the arguments |
| // it can be better to call ReturnTypeForValues, since certain functions may |
| // determine their return types from their values and return DynamicVal if |
| // the values are unknown. |
| func (f Function) ReturnType(argTypes []cty.Type) (cty.Type, error) { |
| vals := make([]cty.Value, len(argTypes)) |
| for i, ty := range argTypes { |
| vals[i] = cty.UnknownVal(ty) |
| } |
| return f.ReturnTypeForValues(vals) |
| } |
| |
| // ReturnTypeForValues is similar to ReturnType but can be used if the caller |
| // already knows the values of some or all of the arguments, in which case |
| // the function may be able to determine a more definite result if its |
| // return type depends on the argument *values*. |
| // |
| // For any arguments whose values are not known, pass an Unknown value of |
| // the appropriate type. |
| func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error) { |
| var posArgs []cty.Value |
| var varArgs []cty.Value |
| |
| if f.spec.VarParam == nil { |
| if len(args) != len(f.spec.Params) { |
| return cty.Type{}, fmt.Errorf( |
| "wrong number of arguments (%d required; %d given)", |
| len(f.spec.Params), len(args), |
| ) |
| } |
| |
| posArgs = args |
| varArgs = nil |
| } else { |
| if len(args) < len(f.spec.Params) { |
| return cty.Type{}, fmt.Errorf( |
| "wrong number of arguments (at least %d required; %d given)", |
| len(f.spec.Params), len(args), |
| ) |
| } |
| |
| posArgs = args[0:len(f.spec.Params)] |
| varArgs = args[len(f.spec.Params):] |
| } |
| |
| for i, spec := range f.spec.Params { |
| val := posArgs[i] |
| |
| if val.IsNull() && !spec.AllowNull { |
| return cty.Type{}, NewArgErrorf(i, "argument must not be null") |
| } |
| |
| // AllowUnknown is ignored for type-checking, since we expect to be |
| // able to type check with unknown values. We *do* still need to deal |
| // with DynamicPseudoType here though, since the Type function might |
| // not be ready to deal with that. |
| |
| if val.Type() == cty.DynamicPseudoType { |
| if !spec.AllowDynamicType { |
| return cty.DynamicPseudoType, nil |
| } |
| } else if errs := val.Type().TestConformance(spec.Type); errs != nil { |
| // For now we'll just return the first error in the set, since |
| // we don't have a good way to return the whole list here. |
| // Would be good to do something better at some point... |
| return cty.Type{}, NewArgError(i, errs[0]) |
| } |
| } |
| |
| if varArgs != nil { |
| spec := f.spec.VarParam |
| for i, val := range varArgs { |
| realI := i + len(posArgs) |
| |
| if val.IsNull() && !spec.AllowNull { |
| return cty.Type{}, NewArgErrorf(realI, "argument must not be null") |
| } |
| |
| if val.Type() == cty.DynamicPseudoType { |
| if !spec.AllowDynamicType { |
| return cty.DynamicPseudoType, nil |
| } |
| } else if errs := val.Type().TestConformance(spec.Type); errs != nil { |
| // For now we'll just return the first error in the set, since |
| // we don't have a good way to return the whole list here. |
| // Would be good to do something better at some point... |
| return cty.Type{}, NewArgError(i, errs[0]) |
| } |
| } |
| } |
| |
| // Intercept any panics from the function and return them as normal errors, |
| // so a calling language runtime doesn't need to deal with panics. |
| defer func() { |
| if r := recover(); r != nil { |
| ty = cty.NilType |
| err = errorForPanic(r) |
| } |
| }() |
| |
| return f.spec.Type(args) |
| } |
| |
| // Call actually calls the function with the given arguments, which must |
| // conform to the function's parameter specification or an error will be |
| // returned. |
| func (f Function) Call(args []cty.Value) (val cty.Value, err error) { |
| expectedType, err := f.ReturnTypeForValues(args) |
| if err != nil { |
| return cty.NilVal, err |
| } |
| |
| // Type checking already dealt with most situations relating to our |
| // parameter specification, but we still need to deal with unknown |
| // values. |
| posArgs := args[:len(f.spec.Params)] |
| varArgs := args[len(f.spec.Params):] |
| |
| for i, spec := range f.spec.Params { |
| val := posArgs[i] |
| |
| if !val.IsKnown() && !spec.AllowUnknown { |
| return cty.UnknownVal(expectedType), nil |
| } |
| } |
| |
| if f.spec.VarParam != nil { |
| spec := f.spec.VarParam |
| for _, val := range varArgs { |
| if !val.IsKnown() && !spec.AllowUnknown { |
| return cty.UnknownVal(expectedType), nil |
| } |
| } |
| } |
| |
| var retVal cty.Value |
| { |
| // Intercept any panics from the function and return them as normal errors, |
| // so a calling language runtime doesn't need to deal with panics. |
| defer func() { |
| if r := recover(); r != nil { |
| val = cty.NilVal |
| err = errorForPanic(r) |
| } |
| }() |
| |
| retVal, err = f.spec.Impl(args, expectedType) |
| if err != nil { |
| return cty.NilVal, err |
| } |
| } |
| |
| // Returned value must conform to what the Type function expected, to |
| // protect callers from having to deal with inconsistencies. |
| if errs := retVal.Type().TestConformance(expectedType); errs != nil { |
| panic(fmt.Errorf( |
| "returned value %#v does not conform to expected return type %#v: %s", |
| retVal, expectedType, errs[0], |
| )) |
| } |
| |
| return retVal, nil |
| } |
| |
| // ProxyFunc the type returned by the method Function.Proxy. |
| type ProxyFunc func(args ...cty.Value) (cty.Value, error) |
| |
| // Proxy returns a function that can be called with cty.Value arguments |
| // to run the function. This is provided as a convenience for when using |
| // a function directly within Go code. |
| func (f Function) Proxy() ProxyFunc { |
| return func(args ...cty.Value) (cty.Value, error) { |
| return f.Call(args) |
| } |
| } |
| |
| // Params returns information about the function's fixed positional parameters. |
| // This does not include information about any variadic arguments accepted; |
| // for that, call VarParam. |
| func (f Function) Params() []Parameter { |
| new := make([]Parameter, len(f.spec.Params)) |
| copy(new, f.spec.Params) |
| return new |
| } |
| |
| // VarParam returns information about the variadic arguments the function |
| // expects, or nil if the function is not variadic. |
| func (f Function) VarParam() *Parameter { |
| if f.spec.VarParam == nil { |
| return nil |
| } |
| |
| ret := *f.spec.VarParam |
| return &ret |
| } |