package cty import ( "fmt" ) type typeObject struct { typeImplSigil AttrTypes map[string]Type } // Object creates an object type with the given attribute types. // // After a map is passed to this function the caller must no longer access it, // since ownership is transferred to this library. func Object(attrTypes map[string]Type) Type { attrTypesNorm := make(map[string]Type, len(attrTypes)) for k, v := range attrTypes { attrTypesNorm[NormalizeString(k)] = v } return Type{ typeObject{ AttrTypes: attrTypesNorm, }, } } func (t typeObject) Equals(other Type) bool { if ot, ok := other.typeImpl.(typeObject); ok { if len(t.AttrTypes) != len(ot.AttrTypes) { // Fast path: if we don't have the same number of attributes // then we can't possibly be equal. This also avoids the need // to test attributes in both directions below, since we know // there can't be extras in "other". return false } for attr, ty := range t.AttrTypes { oty, ok := ot.AttrTypes[attr] if !ok { return false } if !oty.Equals(ty) { return false } } return true } return false } func (t typeObject) FriendlyName(mode friendlyTypeNameMode) string { // There isn't really a friendly way to write an object type due to its // complexity, so we'll just do something English-ish. Callers will // probably want to make some extra effort to avoid ever printing out // an object type FriendlyName in its entirety. For example, could // produce an error message by diffing two object types and saying // something like "Expected attribute foo to be string, but got number". // TODO: Finish this return "object" } func (t typeObject) GoString() string { if len(t.AttrTypes) == 0 { return "cty.EmptyObject" } return fmt.Sprintf("cty.Object(%#v)", t.AttrTypes) } // EmptyObject is a shorthand for Object(map[string]Type{}), to more // easily talk about the empty object type. var EmptyObject Type // EmptyObjectVal is the only possible non-null, non-unknown value of type // EmptyObject. var EmptyObjectVal Value func init() { EmptyObject = Object(map[string]Type{}) EmptyObjectVal = Value{ ty: EmptyObject, v: map[string]interface{}{}, } } // IsObjectType returns true if the given type is an object type, regardless // of its element type. func (t Type) IsObjectType() bool { _, ok := t.typeImpl.(typeObject) return ok } // HasAttribute returns true if the receiver has an attribute with the given // name, regardless of its type. Will panic if the reciever isn't an object // type; use IsObjectType to determine whether this operation will succeed. func (t Type) HasAttribute(name string) bool { name = NormalizeString(name) if ot, ok := t.typeImpl.(typeObject); ok { _, hasAttr := ot.AttrTypes[name] return hasAttr } panic("HasAttribute on non-object Type") } // AttributeType returns the type of the attribute with the given name. Will // panic if the receiver is not an object type (use IsObjectType to confirm) // or if the object type has no such attribute (use HasAttribute to confirm). func (t Type) AttributeType(name string) Type { name = NormalizeString(name) if ot, ok := t.typeImpl.(typeObject); ok { aty, hasAttr := ot.AttrTypes[name] if !hasAttr { panic("no such attribute") } return aty } panic("AttributeType on non-object Type") } // AttributeTypes returns a map from attribute names to their associated // types. Will panic if the receiver is not an object type (use IsObjectType // to confirm). // // The returned map is part of the internal state of the type, and is provided // for read access only. It is forbidden for any caller to modify the returned // map. For many purposes the attribute-related methods of Value are more // appropriate and more convenient to use. func (t Type) AttributeTypes() map[string]Type { if ot, ok := t.typeImpl.(typeObject); ok { return ot.AttrTypes } panic("AttributeTypes on non-object Type") }