<commit_before>// schema is a high-level framework for easily writing new providers
// for Terraform. Usage of schema is recommended over attempting to write
// to the low-level plugin interfaces manually.
//
// schema breaks down provider creation into simple CRUD operations for
// resources. The logic of diffing, destroying before creating, updating
// or creating, etc. is all handled by the framework. The plugin author
// only needs to implement a configuration schema and the CRUD operations and
// everything else is meant to just work.
//
// A good starting point is to view the Provider structure.
package schema

import (
	"fmt"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform/config"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/copystructure"
	"github.com/mitchellh/mapstructure"
)

// Name of ENV variable which (if not empty) prefers panic over error
const PanicOnErr = "TF_SCHEMA_PANIC_ON_ERROR"

// type used for schema package context keys
type contextKey string

// Schema is used to describe the structure of a value.
//
// Read the documentation of the struct elements for important details.
type Schema struct {
	// Type is the type of the value and must be one of the ValueType values.
	//
	// This type not only determines what type is expected/valid in configuring
	// this value, but also what type is returned when ResourceData.Get is
	// called. The types returned by Get are:
	//
	//   TypeBool - bool
	//   TypeInt - int
	//   TypeFloat - float64
	//   TypeString - string
	//   TypeList - []interface{}
	//   TypeMap - map[string]interface{}
	//   TypeSet - *schema.Set
	//
	Type ValueType

	// If one of these is set, then this item can come from the configuration.
	// Both cannot be set. If Optional is set, the value is optional. If
	// Required is set, the value is required.
	//
	// One of these must be set if the value is not computed. That is:
	// value either comes from the config, is computed, or is both.
	Optional bool
	Required bool

	// If this is non-nil, the provided function will be used during diff
	// of this field. If this is nil, a default diff for the type of the
	// schema will be used.
	//
	// This allows comparison based on something other than primitive, list
	// or map equality - for example SSH public keys may be considered
	// equivalent regardless of trailing whitespace.
	DiffSuppressFunc SchemaDiffSuppressFunc

	// If this is non-nil, then this will be a default value that is used
	// when this item is not set in the configuration.
	//
	// DefaultFunc can be specified to compute a dynamic default.
	// Only one of Default or DefaultFunc can be set. If DefaultFunc is
	// used then its return value should be stable to avoid generating
	// confusing/perpetual diffs.
	//
	// Changing either Default or the return value of DefaultFunc can be
	// a breaking change, especially if the attribute in question has
	// ForceNew set. If a default needs to change to align with changing
	// assumptions in an upstream API then it may be necessary to also use
	// the MigrateState function on the resource to change the state to match,
	// or have the Read function adjust the state value to align with the
	// new default.
	//
	// If Required is true above, then Default cannot be set. DefaultFunc
	// can be set with Required. If the DefaultFunc returns nil, then there
	// will be no default and the user will be asked to fill it in.
	//
	// If either of these is set, then the user won't be asked for input
	// for this key if the default is not nil.
	Default     interface{}
	DefaultFunc SchemaDefaultFunc

	// Description is used as the description for docs or asking for user
	// input. It should be relatively short (a few sentences max) and should
	// be formatted to fit a CLI.
	Description string

	// InputDefault is the default value to use for when inputs are requested.
	// This differs from Default in that if Default is set, no input is
	// asked for. If Input is asked, this will be the default value offered.
	InputDefault string

	// The fields below relate to diffs.
	//
	// If Computed is true, then the result of this value is computed
	// (unless specified by config) on creation.
	//
	// If ForceNew is true, then a change in this resource necessitates
	// the creation of a new resource.
	//
	// StateFunc is a function called to change the value of this before
	// storing it in the state (and likewise before comparing for diffs).
	// The use for this is for example with large strings, you may want
	// to simply store the hash of it.
	Computed  bool
	ForceNew  bool
	StateFunc SchemaStateFunc

	// The following fields are only set for a TypeList, TypeSet, or TypeMap.
	//
	// Elem represents the element type. For a TypeMap, it must be a *Schema
	// with a Type of TypeString, otherwise it may be either a *Schema or a
	// *Resource. If it is *Schema, the element type is just a simple value.
	// If it is *Resource, the element type is a complex structure,
	// potentially with its own lifecycle.
	Elem interface{}

	// The following fields are only set for a TypeList or TypeSet.
	//
	// MaxItems defines a maximum amount of items that can exist within a
	// TypeSet or TypeList. Specific use cases would be if a TypeSet is being
	// used to wrap a complex structure, however more than one instance would
	// cause instability.
	//
	// MinItems defines a minimum amount of items that can exist within a
	// TypeSet or TypeList. Specific use cases would be if a TypeSet is being
	// used to wrap a complex structure, however less than one instance would
	// cause instability.
	//
	// If the field Optional is set to true then MinItems is ignored and thus
	// effectively zero.
	MaxItems int
	MinItems int

	// PromoteSingle originally allowed for a single element to be assigned
	// where a primitive list was expected, but this no longer works from
	// Terraform v0.12 onwards (Terraform Core will require a list to be set
	// regardless of what this is set to) and so only applies to Terraform v0.11
	// and earlier, and so should be used only to retain this functionality
	// for those still using v0.11 with a provider that formerly used this.
	PromoteSingle bool

	// The following fields are only valid for a TypeSet type.
	//
	// Set defines a function to determine the unique ID of an item so that
	// a proper set can be built.
	Set SchemaSetFunc

	// ComputedWhen is a set of queries on the configuration. Whenever any
	// of these things is changed, it will require a recompute (this requires
	// that Computed is set to true).
	//
	// NOTE: This currently does not work.
	ComputedWhen []string

	// ConflictsWith is a set of schema keys that conflict with this schema.
	// This will only check that they're set in the _config_. This will not
	// raise an error for a malfunctioning resource that sets a conflicting
	// key.
	ConflictsWith []string

	// When Deprecated is set, this attribute is deprecated.
	//
	// A deprecated field still works, but will probably stop working in near
	// future. This string is the message shown to the user with instructions on
	// how to address the deprecation.
	Deprecated string

	// When Removed is set, this attribute has been removed from the schema
	//
	// Removed attributes can be left in the Schema to generate informative error
	// messages for the user when they show up in resource configurations.
	// This string is the message shown to the user with instructions on
	// what do to about the removed attribute.
	Removed string

	// ValidateFunc allows individual fields to define arbitrary validation
	// logic. It is yielded the provided config value as an interface{} that is
	// guaranteed to be of the proper Schema type, and it can yield warnings or
	// errors based on inspection of that value.
	//
	// ValidateFunc currently only works for primitive types.
	ValidateFunc SchemaValidateFunc

	// Sensitive ensures that the attribute's value does not get displayed in
	// logs or regular output. It should be used for passwords or other
	// secret fields. Future versions of Terraform may encrypt these
	// values.
	Sensitive bool
}

// SchemaDiffSuppressFunc is a function which can be used to determine
// whether a detected diff on a schema element is "valid" or not, and
// suppress it from the plan if necessary.
//
// Return true if the diff should be suppressed, false to retain it.
type SchemaDiffSuppressFunc func(k, old, new string, d *ResourceData) bool

// SchemaDefaultFunc is a function called to return a default value for
// a field.
type SchemaDefaultFunc func() (interface{}, error)

// EnvDefaultFunc is a helper function that returns the value of the
// given environment variable, if one exists, or the default value
// otherwise.
func EnvDefaultFunc(k string, dv interface{}) SchemaDefaultFunc {
	return func() (interface{}, error) {
		if v := os.Getenv(k); v != "" {
			return v, nil
		}

		return dv, nil
	}
}

// MultiEnvDefaultFunc is a helper function that returns the value of the first
// environment variable in the given list that returns a non-empty value. If
// none of the environment variables return a value, the default value is
// returned.
func MultiEnvDefaultFunc(ks []string, dv interface{}) SchemaDefaultFunc {
	return func() (interface{}, error) {
		for _, k := range ks {
			if v := os.Getenv(k); v != "" {
				return v, nil
			}
		}
		return dv, nil
	}
}

// SchemaSetFunc is a function that must return a unique ID for the given
// element. This unique ID is used to store the element in a hash.
type SchemaSetFunc func(interface{}) int

// SchemaStateFunc is a function used to convert some type to a string
// to be stored in the state.
type SchemaStateFunc func(interface{}) string

// SchemaValidateFunc is a function used to validate a single field in the
// schema.
type SchemaValidateFunc func(interface{}, string) ([]string, []error)

func (s *Schema) GoString() string {
	return fmt.Sprintf("*%#v", *s)
}

// Returns a default value for this schema by either reading Default or
// evaluating DefaultFunc. If neither of these are defined, returns nil.
func (s *Schema) DefaultValue() (interface{}, error) {
	if s.Default != nil {
		return s.Default, nil
	}

	if s.DefaultFunc != nil {
		defaultValue, err := s.DefaultFunc()
		if err != nil {
			return nil, fmt.Errorf("error loading default: %s", err)
		}
		return defaultValue, nil
	}

	return nil, nil
}

// Returns a zero value for the schema.
func (s *Schema) ZeroValue() interface{} {
	// If it's a set then we'll do a bit of extra work to provide the
	// right hashing function in our empty value.
	if s.Type == TypeSet {
		setFunc := s.Set
		if setFunc == nil {
			// Default set function uses the schema to hash the whole value
			elem := s.Elem
			switch t := elem.(type) {
			case *Schema:
				setFunc = HashSchema(t)
			case *Resource:
				setFunc = HashResource(t)
			default:
				panic("invalid set element type")
			}
		}
		return &Set{F: setFunc}
	} else {
		return s.Type.Zero()
	}
}

func (s *Schema) finalizeDiff(d *terraform.ResourceAttrDiff, customized bool) *terraform.ResourceAttrDiff {
	if d == nil {
		return d
	}

	if s.Type == TypeBool {
		normalizeBoolString := func(s string) string {
			switch s {
			case "0":
				return "false"
			case "1":
				return "true"
			}
			return s
		}
		d.Old = normalizeBoolString(d.Old)
		d.New = normalizeBoolString(d.New)
	}

	if s.Computed && !d.NewRemoved && d.New == "" {
		// Computed attribute without a new value set
		d.NewComputed = true
	}

	if s.ForceNew {
		// ForceNew, mark that this field is requiring new under the
		// following conditions, explained below:
		//
		//   * Old != New - There is a change in value. This field
		//       is therefore causing a new resource.
		//
		//   * NewComputed - This field is being computed, hence a
		//       potential change in value, mark as causing a new resource.
		d.RequiresNew = d.Old != d.New || d.NewComputed
	}

	if d.NewRemoved {
		return d
	}

	if s.Computed {
		// FIXME: This is where the customized bool from getChange finally
		//        comes into play.  It allows the previously incorrect behavior
		//        of an empty string being used as "unset" when the value is
		//        computed. This should be removed once we can properly
		//        represent an unset/nil value from the configuration.
		if !customized {
			if d.Old != "" && d.New == "" {
				// This is a computed value with an old value set already,
				// just let it go.
				return nil
			}
		}

		if d.New == "" && !d.NewComputed {
			// Computed attribute without a new value set
			d.NewComputed = true
		}
	}

	if s.Sensitive {
		// Set the Sensitive flag so output is hidden in the UI
		d.Sensitive = true
	}

	return d
}

// InternalMap is used to aid in the transition to the new schema types and
// protocol. The name is not meant to convey any usefulness, as this is not to
// be used directly by any providers.
type InternalMap = schemaMap

// schemaMap is a wrapper that adds nice functions on top of schemas.
type schemaMap map[string]*Schema

func (m schemaMap) panicOnError() bool {
	if os.Getenv(PanicOnErr) != "" {
		return true
	}
	return false
}

// Data returns a ResourceData for the given schema, state, and diff.
//
// The diff is optional.
func (m schemaMap) Data(
	s *terraform.InstanceState,
	d *terraform.InstanceDiff) (*ResourceData, error) {
	return &ResourceData{
		schema:       m,
		state:        s,
		diff:         d,
		panicOnError: m.panicOnError(),
	}, nil
}

// DeepCopy returns a copy of this schemaMap. The copy can be safely modified
// without affecting the original.
func (m *schemaMap) DeepCopy() schemaMap {
	copy, err := copystructure.Config{Lock: true}.Copy(m)
	if err != nil {
		panic(err)
	}
	return *copy.(*schemaMap)
}

// Diff returns the diff for a resource given the schema map,
// state, and configuration.
func (m schemaMap) Diff(
	s *terraform.InstanceState,
	c *terraform.ResourceConfig,
	customizeDiff CustomizeDiffFunc,
	meta interface{},
	handleRequiresNew bool) (*terraform.InstanceDiff, error) {
	result := new(terraform.InstanceDiff)
	result.Attributes = make(map[string]*terraform.ResourceAttrDiff)

	// Make sure to mark if the resource is tainted
	if s != nil {
		result.DestroyTainted = s.Tainted
	}

	d := &ResourceData{
		schema:       m,
		state:        s,
		config:       c,
		panicOnError: m.panicOnError(),
	}

	for k, schema := range m {
		err := m.diff(k, schema, result, d, false)
		if err != nil {
			return nil, err
		}
	}

	// Remove any nil diffs just to keep things clean
	for k, v := range result.Attributes {
		if v == nil {
			delete(result.Attributes, k)
		}
	}

	// If this is a non-destroy diff, call any custom diff logic that has been
	// defined.
	if !result.DestroyTainted && customizeDiff != nil {
		mc := m.DeepCopy()
		rd := newResourceDiff(mc, c, s, result)
		if err := customizeDiff(rd, meta); err != nil {
			return nil, err
		}
		for _, k := range rd.UpdatedKeys() {
			err := m.diff(k, mc[k], result, rd, false)
			if err != nil {
				return nil, err
			}
		}
	}

	if handleRequiresNew {
		// If the diff requires a new resource, then we recompute the diff
		// so we have the complete new resource diff, and preserve the
		// RequiresNew fields where necessary so the user knows exactly what
		// caused that.
		if result.RequiresNew() {
			// Create the new diff
			result2 := new(terraform.InstanceDiff)
			result2.Attributes = make(map[string]*terraform.ResourceAttrDiff)

			// Preserve the DestroyTainted flag
			result2.DestroyTainted = result.DestroyTainted

			// Reset the data to not contain state. We have to call init()
			// again in order to reset the FieldReaders.
			d.state = nil
			d.init()

			// Perform the diff again
			for k, schema := range m {
				err := m.diff(k, schema, result2, d, false)
				if err != nil {
					return nil, err
				}
			}

			// Re-run customization
			if !result2.DestroyTainted && customizeDiff != nil {
				mc := m.DeepCopy()
				rd := newResourceDiff(mc, c, d.state, result2)
				if err := customizeDiff(rd, meta); err != nil {
					return nil, err
				}
				for _, k := range rd.UpdatedKeys() {
					err := m.diff(k, mc[k], result2, rd, false)
					if err != nil {
						return nil, err
					}
				}
			}

			// Force all the fields to not force a new since we know what we
			// want to force new.
			for k, attr := range result2.Attributes {
				if attr == nil {
					continue
				}

				if attr.RequiresNew {
					attr.RequiresNew = false
				}

				if s != nil {
					attr.Old = s.Attributes[k]
				}
			}

			// Now copy in all the requires new diffs...
			for k, attr := range result.Attributes {
				if attr == nil {
					continue
				}

				newAttr, ok := result2.Attributes[k]
				if !ok {
					newAttr = attr
				}

				if attr.RequiresNew {
					newAttr.RequiresNew = true
				}

				result2.Attributes[k] = newAttr
			}

			// And set the diff!
			result = result2
		}

	}

	// Go through and detect all of the ComputedWhens now that we've
	// finished the diff.
	// TODO

	if result.Empty() {
		// If we don't have any diff elements, just return nil
		return nil, nil
	}

	return result, nil
}

// Input implements the terraform.ResourceProvider method by asking
// for input for required configuration keys that don't have a value.
func (m schemaMap) Input(
	input terraform.UIInput,
	c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
	keys := make([]string, 0, len(m))
	for k, _ := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		v := m[k]

		// Skip things that don't require config, if that is even valid
		// for a provider schema.
		// Required XOR Optional must always be true to validate, so we only
		// need to check one.
		if v.Optional {
			continue
		}

		// Deprecated fields should never prompt
		if v.Deprecated != "" {
			continue
		}

		// Skip things that have a value of some sort already
		if _, ok := c.Raw[k]; ok {
			continue
		}

		// Skip if it has a default value
		defaultValue, err := v.DefaultValue()
		if err != nil {
			return nil, fmt.Errorf("%s: error loading default: %s", k, err)
		}
		if defaultValue != nil {
			continue
		}

		var value interface{}
		switch v.Type {
		case TypeBool, TypeInt, TypeFloat, TypeSet, TypeList:
			continue
		case TypeString:
			value, err = m.inputString(input, k, v)
		default:
			panic(fmt.Sprintf("Unknown type for input: %#v", v.Type))
		}

		if err != nil {
			return nil, fmt.Errorf(
				"%s: %s", k, err)
		}

		c.Config[k] = value
	}

	return c, nil
}

// Validate validates the configuration against this schema mapping.
func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
	return m.validateObject("", m, c)
}

// InternalValidate validates the format of this schema. This should be called
// from a unit test (and not in user-path code) to verify that a schema
// is properly built.
func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
	if topSchemaMap == nil {
		topSchemaMap = m
	}
	for k, v := range m {
		if v.Type == TypeInvalid {
			return fmt.Errorf("%s: Type must be specified", k)
		}

		if v.Optional && v.Required {
			return fmt.Errorf("%s: Optional or Required must be set, not both", k)
		}

		if v.Required && v.Computed {
			return fmt.Errorf("%s: Cannot be both Required and Computed", k)
		}

		if !v.Required && !v.Optional && !v.Computed {
			return fmt.Errorf("%s: One of optional, required, or computed must be set", k)
		}

		if v.Computed && v.Default != nil {
			return fmt.Errorf("%s: Default must be nil if computed", k)
		}

		if v.Required && v.Default != nil {
			return fmt.Errorf("%s: Default cannot be set with Required", k)
		}

		if len(v.ComputedWhen) > 0 && !v.Computed {
			return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
		}

		if len(v.ConflictsWith) > 0 && v.Required {
			return fmt.Errorf("%s: ConflictsWith cannot be set with Required", k)
		}

		if len(v.ConflictsWith) > 0 {
			for _, key := range v.ConflictsWith {
				parts := strings.Split(key, ".")
				sm := topSchemaMap
				var target *Schema
				for _, part := range parts {
					// Skip index fields
					if _, err := strconv.Atoi(part); err == nil {
						continue
					}

					var ok bool
					if target, ok = sm[part]; !ok {
						return fmt.Errorf("%s: ConflictsWith references unknown attribute (%s)", k, key)
					}

					if subResource, ok := target.Elem.(*Resource); ok {<commit_msg>add "part" to ConflictsWith validation error<commit_after>// schema is a high-level framework for easily writing new providers
// for Terraform. Usage of schema is recommended over attempting to write
// to the low-level plugin interfaces manually.
//
// schema breaks down provider creation into simple CRUD operations for
// resources. The logic of diffing, destroying before creating, updating
// or creating, etc. is all handled by the framework. The plugin author
// only needs to implement a configuration schema and the CRUD operations and
// everything else is meant to just work.
//
// A good starting point is to view the Provider structure.
package schema

import (
	"fmt"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform/config"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/copystructure"
	"github.com/mitchellh/mapstructure"
)

// Name of ENV variable which (if not empty) prefers panic over error
const PanicOnErr = "TF_SCHEMA_PANIC_ON_ERROR"

// type used for schema package context keys
type contextKey string

// Schema is used to describe the structure of a value.
//
// Read the documentation of the struct elements for important details.
type Schema struct {
	// Type is the type of the value and must be one of the ValueType values.
	//
	// This type not only determines what type is expected/valid in configuring
	// this value, but also what type is returned when ResourceData.Get is
	// called. The types returned by Get are:
	//
	//   TypeBool - bool
	//   TypeInt - int
	//   TypeFloat - float64
	//   TypeString - string
	//   TypeList - []interface{}
	//   TypeMap - map[string]interface{}
	//   TypeSet - *schema.Set
	//
	Type ValueType

	// If one of these is set, then this item can come from the configuration.
	// Both cannot be set. If Optional is set, the value is optional. If
	// Required is set, the value is required.
	//
	// One of these must be set if the value is not computed. That is:
	// value either comes from the config, is computed, or is both.
	Optional bool
	Required bool

	// If this is non-nil, the provided function will be used during diff
	// of this field. If this is nil, a default diff for the type of the
	// schema will be used.
	//
	// This allows comparison based on something other than primitive, list
	// or map equality - for example SSH public keys may be considered
	// equivalent regardless of trailing whitespace.
	DiffSuppressFunc SchemaDiffSuppressFunc

	// If this is non-nil, then this will be a default value that is used
	// when this item is not set in the configuration.
	//
	// DefaultFunc can be specified to compute a dynamic default.
	// Only one of Default or DefaultFunc can be set. If DefaultFunc is
	// used then its return value should be stable to avoid generating
	// confusing/perpetual diffs.
	//
	// Changing either Default or the return value of DefaultFunc can be
	// a breaking change, especially if the attribute in question has
	// ForceNew set. If a default needs to change to align with changing
	// assumptions in an upstream API then it may be necessary to also use
	// the MigrateState function on the resource to change the state to match,
	// or have the Read function adjust the state value to align with the
	// new default.
	//
	// If Required is true above, then Default cannot be set. DefaultFunc
	// can be set with Required. If the DefaultFunc returns nil, then there
	// will be no default and the user will be asked to fill it in.
	//
	// If either of these is set, then the user won't be asked for input
	// for this key if the default is not nil.
	Default     interface{}
	DefaultFunc SchemaDefaultFunc

	// Description is used as the description for docs or asking for user
	// input. It should be relatively short (a few sentences max) and should
	// be formatted to fit a CLI.
	Description string

	// InputDefault is the default value to use for when inputs are requested.
	// This differs from Default in that if Default is set, no input is
	// asked for. If Input is asked, this will be the default value offered.
	InputDefault string

	// The fields below relate to diffs.
	//
	// If Computed is true, then the result of this value is computed
	// (unless specified by config) on creation.
	//
	// If ForceNew is true, then a change in this resource necessitates
	// the creation of a new resource.
	//
	// StateFunc is a function called to change the value of this before
	// storing it in the state (and likewise before comparing for diffs).
	// The use for this is for example with large strings, you may want
	// to simply store the hash of it.
	Computed  bool
	ForceNew  bool
	StateFunc SchemaStateFunc

	// The following fields are only set for a TypeList, TypeSet, or TypeMap.
	//
	// Elem represents the element type. For a TypeMap, it must be a *Schema
	// with a Type of TypeString, otherwise it may be either a *Schema or a
	// *Resource. If it is *Schema, the element type is just a simple value.
	// If it is *Resource, the element type is a complex structure,
	// potentially with its own lifecycle.
	Elem interface{}

	// The following fields are only set for a TypeList or TypeSet.
	//
	// MaxItems defines a maximum amount of items that can exist within a
	// TypeSet or TypeList. Specific use cases would be if a TypeSet is being
	// used to wrap a complex structure, however more than one instance would
	// cause instability.
	//
	// MinItems defines a minimum amount of items that can exist within a
	// TypeSet or TypeList. Specific use cases would be if a TypeSet is being
	// used to wrap a complex structure, however less than one instance would
	// cause instability.
	//
	// If the field Optional is set to true then MinItems is ignored and thus
	// effectively zero.
	MaxItems int
	MinItems int

	// PromoteSingle originally allowed for a single element to be assigned
	// where a primitive list was expected, but this no longer works from
	// Terraform v0.12 onwards (Terraform Core will require a list to be set
	// regardless of what this is set to) and so only applies to Terraform v0.11
	// and earlier, and so should be used only to retain this functionality
	// for those still using v0.11 with a provider that formerly used this.
	PromoteSingle bool

	// The following fields are only valid for a TypeSet type.
	//
	// Set defines a function to determine the unique ID of an item so that
	// a proper set can be built.
	Set SchemaSetFunc

	// ComputedWhen is a set of queries on the configuration. Whenever any
	// of these things is changed, it will require a recompute (this requires
	// that Computed is set to true).
	//
	// NOTE: This currently does not work.
	ComputedWhen []string

	// ConflictsWith is a set of schema keys that conflict with this schema.
	// This will only check that they're set in the _config_. This will not
	// raise an error for a malfunctioning resource that sets a conflicting
	// key.
	ConflictsWith []string

	// When Deprecated is set, this attribute is deprecated.
	//
	// A deprecated field still works, but will probably stop working in near
	// future. This string is the message shown to the user with instructions on
	// how to address the deprecation.
	Deprecated string

	// When Removed is set, this attribute has been removed from the schema
	//
	// Removed attributes can be left in the Schema to generate informative error
	// messages for the user when they show up in resource configurations.
	// This string is the message shown to the user with instructions on
	// what do to about the removed attribute.
	Removed string

	// ValidateFunc allows individual fields to define arbitrary validation
	// logic. It is yielded the provided config value as an interface{} that is
	// guaranteed to be of the proper Schema type, and it can yield warnings or
	// errors based on inspection of that value.
	//
	// ValidateFunc currently only works for primitive types.
	ValidateFunc SchemaValidateFunc

	// Sensitive ensures that the attribute's value does not get displayed in
	// logs or regular output. It should be used for passwords or other
	// secret fields. Future versions of Terraform may encrypt these
	// values.
	Sensitive bool
}

// SchemaDiffSuppressFunc is a function which can be used to determine
// whether a detected diff on a schema element is "valid" or not, and
// suppress it from the plan if necessary.
//
// Return true if the diff should be suppressed, false to retain it.
type SchemaDiffSuppressFunc func(k, old, new string, d *ResourceData) bool

// SchemaDefaultFunc is a function called to return a default value for
// a field.
type SchemaDefaultFunc func() (interface{}, error)

// EnvDefaultFunc is a helper function that returns the value of the
// given environment variable, if one exists, or the default value
// otherwise.
func EnvDefaultFunc(k string, dv interface{}) SchemaDefaultFunc {
	return func() (interface{}, error) {
		if v := os.Getenv(k); v != "" {
			return v, nil
		}

		return dv, nil
	}
}

// MultiEnvDefaultFunc is a helper function that returns the value of the first
// environment variable in the given list that returns a non-empty value. If
// none of the environment variables return a value, the default value is
// returned.
func MultiEnvDefaultFunc(ks []string, dv interface{}) SchemaDefaultFunc {
	return func() (interface{}, error) {
		for _, k := range ks {
			if v := os.Getenv(k); v != "" {
				return v, nil
			}
		}
		return dv, nil
	}
}

// SchemaSetFunc is a function that must return a unique ID for the given
// element. This unique ID is used to store the element in a hash.
type SchemaSetFunc func(interface{}) int

// SchemaStateFunc is a function used to convert some type to a string
// to be stored in the state.
type SchemaStateFunc func(interface{}) string

// SchemaValidateFunc is a function used to validate a single field in the
// schema.
type SchemaValidateFunc func(interface{}, string) ([]string, []error)

func (s *Schema) GoString() string {
	return fmt.Sprintf("*%#v", *s)
}

// Returns a default value for this schema by either reading Default or
// evaluating DefaultFunc. If neither of these are defined, returns nil.
func (s *Schema) DefaultValue() (interface{}, error) {
	if s.Default != nil {
		return s.Default, nil
	}

	if s.DefaultFunc != nil {
		defaultValue, err := s.DefaultFunc()
		if err != nil {
			return nil, fmt.Errorf("error loading default: %s", err)
		}
		return defaultValue, nil
	}

	return nil, nil
}

// Returns a zero value for the schema.
func (s *Schema) ZeroValue() interface{} {
	// If it's a set then we'll do a bit of extra work to provide the
	// right hashing function in our empty value.
	if s.Type == TypeSet {
		setFunc := s.Set
		if setFunc == nil {
			// Default set function uses the schema to hash the whole value
			elem := s.Elem
			switch t := elem.(type) {
			case *Schema:
				setFunc = HashSchema(t)
			case *Resource:
				setFunc = HashResource(t)
			default:
				panic("invalid set element type")
			}
		}
		return &Set{F: setFunc}
	} else {
		return s.Type.Zero()
	}
}

func (s *Schema) finalizeDiff(d *terraform.ResourceAttrDiff, customized bool) *terraform.ResourceAttrDiff {
	if d == nil {
		return d
	}

	if s.Type == TypeBool {
		normalizeBoolString := func(s string) string {
			switch s {
			case "0":
				return "false"
			case "1":
				return "true"
			}
			return s
		}
		d.Old = normalizeBoolString(d.Old)
		d.New = normalizeBoolString(d.New)
	}

	if s.Computed && !d.NewRemoved && d.New == "" {
		// Computed attribute without a new value set
		d.NewComputed = true
	}

	if s.ForceNew {
		// ForceNew, mark that this field is requiring new under the
		// following conditions, explained below:
		//
		//   * Old != New - There is a change in value. This field
		//       is therefore causing a new resource.
		//
		//   * NewComputed - This field is being computed, hence a
		//       potential change in value, mark as causing a new resource.
		d.RequiresNew = d.Old != d.New || d.NewComputed
	}

	if d.NewRemoved {
		return d
	}

	if s.Computed {
		// FIXME: This is where the customized bool from getChange finally
		//        comes into play.  It allows the previously incorrect behavior
		//        of an empty string being used as "unset" when the value is
		//        computed. This should be removed once we can properly
		//        represent an unset/nil value from the configuration.
		if !customized {
			if d.Old != "" && d.New == "" {
				// This is a computed value with an old value set already,
				// just let it go.
				return nil
			}
		}

		if d.New == "" && !d.NewComputed {
			// Computed attribute without a new value set
			d.NewComputed = true
		}
	}

	if s.Sensitive {
		// Set the Sensitive flag so output is hidden in the UI
		d.Sensitive = true
	}

	return d
}

// InternalMap is used to aid in the transition to the new schema types and
// protocol. The name is not meant to convey any usefulness, as this is not to
// be used directly by any providers.
type InternalMap = schemaMap

// schemaMap is a wrapper that adds nice functions on top of schemas.
type schemaMap map[string]*Schema

func (m schemaMap) panicOnError() bool {
	if os.Getenv(PanicOnErr) != "" {
		return true
	}
	return false
}

// Data returns a ResourceData for the given schema, state, and diff.
//
// The diff is optional.
func (m schemaMap) Data(
	s *terraform.InstanceState,
	d *terraform.InstanceDiff) (*ResourceData, error) {
	return &ResourceData{
		schema:       m,
		state:        s,
		diff:         d,
		panicOnError: m.panicOnError(),
	}, nil
}

// DeepCopy returns a copy of this schemaMap. The copy can be safely modified
// without affecting the original.
func (m *schemaMap) DeepCopy() schemaMap {
	copy, err := copystructure.Config{Lock: true}.Copy(m)
	if err != nil {
		panic(err)
	}
	return *copy.(*schemaMap)
}

// Diff returns the diff for a resource given the schema map,
// state, and configuration.
func (m schemaMap) Diff(
	s *terraform.InstanceState,
	c *terraform.ResourceConfig,
	customizeDiff CustomizeDiffFunc,
	meta interface{},
	handleRequiresNew bool) (*terraform.InstanceDiff, error) {
	result := new(terraform.InstanceDiff)
	result.Attributes = make(map[string]*terraform.ResourceAttrDiff)

	// Make sure to mark if the resource is tainted
	if s != nil {
		result.DestroyTainted = s.Tainted
	}

	d := &ResourceData{
		schema:       m,
		state:        s,
		config:       c,
		panicOnError: m.panicOnError(),
	}

	for k, schema := range m {
		err := m.diff(k, schema, result, d, false)
		if err != nil {
			return nil, err
		}
	}

	// Remove any nil diffs just to keep things clean
	for k, v := range result.Attributes {
		if v == nil {
			delete(result.Attributes, k)
		}
	}

	// If this is a non-destroy diff, call any custom diff logic that has been
	// defined.
	if !result.DestroyTainted && customizeDiff != nil {
		mc := m.DeepCopy()
		rd := newResourceDiff(mc, c, s, result)
		if err := customizeDiff(rd, meta); err != nil {
			return nil, err
		}
		for _, k := range rd.UpdatedKeys() {
			err := m.diff(k, mc[k], result, rd, false)
			if err != nil {
				return nil, err
			}
		}
	}

	if handleRequiresNew {
		// If the diff requires a new resource, then we recompute the diff
		// so we have the complete new resource diff, and preserve the
		// RequiresNew fields where necessary so the user knows exactly what
		// caused that.
		if result.RequiresNew() {
			// Create the new diff
			result2 := new(terraform.InstanceDiff)
			result2.Attributes = make(map[string]*terraform.ResourceAttrDiff)

			// Preserve the DestroyTainted flag
			result2.DestroyTainted = result.DestroyTainted

			// Reset the data to not contain state. We have to call init()
			// again in order to reset the FieldReaders.
			d.state = nil
			d.init()

			// Perform the diff again
			for k, schema := range m {
				err := m.diff(k, schema, result2, d, false)
				if err != nil {
					return nil, err
				}
			}

			// Re-run customization
			if !result2.DestroyTainted && customizeDiff != nil {
				mc := m.DeepCopy()
				rd := newResourceDiff(mc, c, d.state, result2)
				if err := customizeDiff(rd, meta); err != nil {
					return nil, err
				}
				for _, k := range rd.UpdatedKeys() {
					err := m.diff(k, mc[k], result2, rd, false)
					if err != nil {
						return nil, err
					}
				}
			}

			// Force all the fields to not force a new since we know what we
			// want to force new.
			for k, attr := range result2.Attributes {
				if attr == nil {
					continue
				}

				if attr.RequiresNew {
					attr.RequiresNew = false
				}

				if s != nil {
					attr.Old = s.Attributes[k]
				}
			}

			// Now copy in all the requires new diffs...
			for k, attr := range result.Attributes {
				if attr == nil {
					continue
				}

				newAttr, ok := result2.Attributes[k]
				if !ok {
					newAttr = attr
				}

				if attr.RequiresNew {
					newAttr.RequiresNew = true
				}

				result2.Attributes[k] = newAttr
			}

			// And set the diff!
			result = result2
		}

	}

	// Go through and detect all of the ComputedWhens now that we've
	// finished the diff.
	// TODO

	if result.Empty() {
		// If we don't have any diff elements, just return nil
		return nil, nil
	}

	return result, nil
}

// Input implements the terraform.ResourceProvider method by asking
// for input for required configuration keys that don't have a value.
func (m schemaMap) Input(
	input terraform.UIInput,
	c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
	keys := make([]string, 0, len(m))
	for k, _ := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		v := m[k]

		// Skip things that don't require config, if that is even valid
		// for a provider schema.
		// Required XOR Optional must always be true to validate, so we only
		// need to check one.
		if v.Optional {
			continue
		}

		// Deprecated fields should never prompt
		if v.Deprecated != "" {
			continue
		}

		// Skip things that have a value of some sort already
		if _, ok := c.Raw[k]; ok {
			continue
		}

		// Skip if it has a default value
		defaultValue, err := v.DefaultValue()
		if err != nil {
			return nil, fmt.Errorf("%s: error loading default: %s", k, err)
		}
		if defaultValue != nil {
			continue
		}

		var value interface{}
		switch v.Type {
		case TypeBool, TypeInt, TypeFloat, TypeSet, TypeList:
			continue
		case TypeString:
			value, err = m.inputString(input, k, v)
		default:
			panic(fmt.Sprintf("Unknown type for input: %#v", v.Type))
		}

		if err != nil {
			return nil, fmt.Errorf(
				"%s: %s", k, err)
		}

		c.Config[k] = value
	}

	return c, nil
}

// Validate validates the configuration against this schema mapping.
func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
	return m.validateObject("", m, c)
}

// InternalValidate validates the format of this schema. This should be called
// from a unit test (and not in user-path code) to verify that a schema
// is properly built.
func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
	if topSchemaMap == nil {
		topSchemaMap = m
	}
	for k, v := range m {
		if v.Type == TypeInvalid {
			return fmt.Errorf("%s: Type must be specified", k)
		}

		if v.Optional && v.Required {
			return fmt.Errorf("%s: Optional or Required must be set, not both", k)
		}

		if v.Required && v.Computed {
			return fmt.Errorf("%s: Cannot be both Required and Computed", k)
		}

		if !v.Required && !v.Optional && !v.Computed {
			return fmt.Errorf("%s: One of optional, required, or computed must be set", k)
		}

		if v.Computed && v.Default != nil {
			return fmt.Errorf("%s: Default must be nil if computed", k)
		}

		if v.Required && v.Default != nil {
			return fmt.Errorf("%s: Default cannot be set with Required", k)
		}

		if len(v.ComputedWhen) > 0 && !v.Computed {
			return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
		}

		if len(v.ConflictsWith) > 0 && v.Required {
			return fmt.Errorf("%s: ConflictsWith cannot be set with Required", k)
		}

		if len(v.ConflictsWith) > 0 {
			for _, key := range v.ConflictsWith {
				parts := strings.Split(key, ".")
				sm := topSchemaMap
				var target *Schema
				for _, part := range parts {
					// Skip index fields
					if _, err := strconv.Atoi(part); err == nil {
						continue
					}

					var ok bool
					if target, ok = sm[part]; !ok {
						return fmt.Errorf("%s: ConflictsWith references unknown attribute (%s) at part (%s)", k, key, part)
					}

					if subResource, ok := target.Elem.(*Resource); ok {
{'fraction_non_alphanumeric': 0.09175071870584223, 'fraction_numerical': 0.0013643229547337134, 'mean_word_length': 2.9400076790170857, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '735573', 'n_tokens_mistral': 13110, 'n_tokens_neox': 11978, 'n_words': 5469}