func createPropertiesFromSchema()

in tpgtools/property.go [517:1003]


func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher, overrides Overrides, resource *Resource, parent *Property, location string) (props []Property, err error) {
	identityFields := []string{} // always empty if parent != nil
	if parent == nil {
		identityFields = idParts(resource.ID)
	}

	// Maps PackageJSONName back to property Name
	// for conflict fields
	conflictsMap := make(map[string]string)

	for k, v := range schema.Properties {
		ref := ""
		packageName := ""

		if pName, ok := v.Extension["x-dcl-go-name"].(string); ok {
			packageName = pName
		}

		if v.Ref != "" {
			ref = v.Ref
			v, err = typeFetcher.ResolveSchema(v.Ref)
			if err != nil {
				return nil, err
			}
			ref = typeFetcher.PackagePathForReference(ref, v.Extension["x-dcl-go-type"].(string))
		}

		// Sub-properties are referenced by name, and the explicit title value
		// won't be set initially.
		v.Title = k

		if parent == nil && v.Title == "id" {
			// If top-level field is named `id`, rename to avoid collision with Terraform id
			v.Title = fmt.Sprintf("%s%s", resource.Name(), "Id")
		}

		p := Property{
			title:       jsonToSnakeCase(v.Title).snakecase(),
			Type:        Type{typ: v},
			PackageName: packageName,
			Description: v.Description,
			resource:    resource,
			parent:      parent,
			ref:         ref,
		}

		if overrides.PropertyOverride(Exclude, p, location) {
			continue
		}

		do := CustomDefaultDetails{}
		doOk, err := overrides.PropertyOverrideWithDetails(CustomDefault, p, &do, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom list size details")
		}

		if v.Default != "" || doOk {
			def := v.Default
			if doOk {
				def = do.Default
			}
			d, err := renderDefault(p.Type, def)
			if err != nil {
				return nil, fmt.Errorf("failed to render default: %v", err)
			}
			p.Default = &d
		}

		cn := CustomNameDetails{}
		cnOk, err := overrides.PropertyOverrideWithDetails(CustomName, p, &cn, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom name details: %v", err)
		}
		if cnOk {
			p.customName = cn.Name
		}

		if p.Type.String() == SchemaTypeMap {
			e := "&schema.Schema{Type: schema.TypeString}"
			p.Elem = &e
			p.ElemIsBasicType = true
		}

		if sens, ok := v.Extension["x-dcl-sensitive"].(bool); ok {
			p.Sensitive = sens
		}

		if v, ok := v.Extension["x-dcl-conflicts"].([]interface{}); ok {
			// NOTE: DCL not label x-dcl-conflicts for reused types
			// TODO(shuya): handle nested field when b/213503595 got fixed

			if parent == nil {
				for _, ci := range v {
					p.JSONCaseConflictsWith = append(p.JSONCaseConflictsWith, ci.(string))
				}

				conflictsMap[p.PackageJSONName()] = p.Name()
			}
		}

		// Do this before handling properties so we can check if the parent is readOnly
		isSGP := false
		if sgp, ok := v.Extension["x-dcl-server-generated-parameter"].(bool); ok {
			isSGP = sgp
		}
		if v.ReadOnly || isSGP || (parent != nil && parent.Computed) {
			p.Computed = true

			if stringInSlice(p.Name(), identityFields) {
				sg := p.DefaultStateGetter()
				p.StateGetter = &sg
			}
		}

		p.Parameter, _ = v.Extension["x-dcl-parameter"].(bool)
		p.HasLongForm, _ = v.Extension["x-dcl-has-long-form"].(bool)

		// Handle object properties
		if len(v.Properties) > 0 {
			props, err := createPropertiesFromSchema(v, typeFetcher, overrides, resource, &p, location)
			if err != nil {
				return nil, err
			}

			p.Properties = props
			if !p.Computed {
				// Computed fields cannot specify MaxItems
				mi := int64(1)
				p.MaxItems = &mi
			}
			e := fmt.Sprintf("%s%sSchema()", resource.PathType(), p.PackagePath())
			p.Elem = &e
			p.ElemIsBasicType = false
		}

		// Handle array properties
		if v.Items != nil {
			ls := CustomListSizeConstraintDetails{}
			lsOk, err := overrides.PropertyOverrideWithDetails(CustomListSize, p, &ls, location)
			if err != nil {
				return nil, fmt.Errorf("failed to decode custom list size details")
			}
			if lsOk {
				if ls.Max > 0 {
					p.MaxItems = &ls.Max
				}
				if ls.Min > 0 {
					p.MinItems = &ls.Min
				}
			}

			// We end up handling arrays of objects very similarly to nested objects
			// themselves
			if len(v.Items.Properties) > 0 {
				props, err := createPropertiesFromSchema(v.Items, typeFetcher, overrides, resource, &p, location)
				if err != nil {
					return nil, err
				}

				p.Properties = props
				e := fmt.Sprintf("%s%sSchema()", resource.PathType(), p.PackagePath())
				p.Elem = &e
				p.ElemIsBasicType = false
			} else {
				i := Type{typ: v.Items}
				e := fmt.Sprintf("&schema.Schema{Type: schema.%s}", i.String())
				if _, ok := v.Extension["x-dcl-references"]; ok {
					e = fmt.Sprintf("&schema.Schema{Type: schema.%s, DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, }", i.String())
				}
				p.Elem = &e
				p.ElemIsBasicType = true
			}
		}
		// Complex maps are represented as TypeSet but don't have v.Items set.
		// Use AdditionalProperties instead, and add an additional `name` field
		// that represents the key in the map
		if p.Type.IsComplexMap() {
			props, err := createPropertiesFromSchema(p.Type.typ.AdditionalProperties, typeFetcher, overrides, resource, &p, location)
			if err != nil {
				return nil, err
			}
			cm := ComplexMapKeyDetails{}
			cmOk, err := overrides.PropertyOverrideWithDetails(ComplexMapKey, p, &cm, location)
			if err != nil {
				return nil, fmt.Errorf("failed to decode complex map key name details")
			}
			if !cmOk {
				return nil, fmt.Errorf("failed to find complex map key name for map named: %s", p.Name())
			}
			keyProp := Property{
				title:       cm.KeyName,
				Type:        Type{&openapi.Schema{Type: "string"}},
				resource:    resource,
				parent:      &p,
				Required:    true,
				Description: "The name for the key in the map for which this object is mapped to in the API",
			}
			props = append([]Property{keyProp}, props...)

			p.Properties = props
			e := fmt.Sprintf("%s%sSchema()", resource.PathType(), p.PackagePath())
			p.Elem = &e
			p.ElemIsBasicType = false
			p.ComplexMapKeyName = cm.KeyName
		}

		if !p.Computed {
			if stringInSlice(v.Title, schema.Required) {
				p.Required = true
			} else {
				p.Optional = true
			}
		}
		cr := CustomSchemaValuesDetails{}
		crOk, err := overrides.PropertyOverrideWithDetails(CustomSchemaValues, p, &cr, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom required details")
		}
		if crOk {
			p.Required = cr.Required
			p.Optional = cr.Optional
			p.Computed = cr.Computed
		}

		// Handle settable fields. If the field is computed it's not settable but
		// if it's also optional (O+C), it is.
		if !p.Computed || (p.Optional) {
			p.Settable = true

			// NOTE: x-kubernetes-immmutable implies that all children of a field
			// are actually immutable. However, in practice, DCL specs will label
			// every immutable subfield.
			if isImmutable, ok := v.Extension["x-kubernetes-immutable"].(bool); ok && isImmutable {
				p.ForceNew = true
			}

			serverDefault, _ := v.Extension["x-dcl-server-default"].(bool)
			extractIfEmpty, _ := v.Extension["x-dcl-extract-if-empty"].(bool)

			if serverDefault || extractIfEmpty {
				p.Computed = true
			}

			if forwardSlashAllowed, ok := v.Extension["x-dcl-forward-slash-allowed"].(bool); ok && forwardSlashAllowed {
				p.forwardSlashAllowed = true
			}

			// special handling for project/region/zone/other fields with
			// provider defaults
			if stringInSlice(p.title, []string{"project", "region", "zone"}) || stringInSlice(p.customName, []string{"region", "project", "zone"}) {
				p.Optional = true
				p.Required = false
				p.Computed = true

				sg := fmt.Sprintf("dcl.String(%v)", p.Name())
				p.StateGetter = &sg

				cig := &CustomIdentityGetterDetails{}
				cigOk, err := overrides.PropertyOverrideWithDetails(CustomIdentityGetter, p, cig, location)
				if err != nil {
					return nil, fmt.Errorf("failed to decode custom identity getter details")
				}

				propertyName := p.title
				if p.customName != "" {
					propertyName = p.customName
				}
				ig := fmt.Sprintf("tpgresource.Get%s(d, config)", renderSnakeAsTitle(miscellaneousNameSnakeCase(propertyName)))
				if cigOk {
					ig = fmt.Sprintf("%s(d, config)", cig.Function)
				}

				p.IdentityGetter = &ig
			} else {
				sg := p.DefaultStateGetter()
				p.StateGetter = &sg
			}
		}

		ss := p.DefaultStateSetter()
		p.StateSetter = &ss

		if p.Sensitive && p.Settable {
			p.StateSetter = nil
		}

		css := CustomStateSetterDetails{}
		cssOk, err := overrides.PropertyOverrideWithDetails(CustomStateSetter, p, &css, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom stateSetter func: %v", err)
		}

		if cssOk {
			p.StateSetter = &css.Function
		}

		irOk := overrides.PropertyOverride(IgnoreRead, p, location)
		if irOk {
			p.StateSetter = nil
		}

		cd := CustomDescriptionDetails{}
		cdOk, err := overrides.PropertyOverrideWithDetails(CustomDescription, p, &cd, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom description details: %v", err)
		}

		if cdOk {
			p.Description = cd.Description
		}

		dsf := CustomDiffSuppressFuncDetails{}
		dsfOk, err := overrides.PropertyOverrideWithDetails(DiffSuppressFunc, p, &dsf, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom diff suppress func: %v", err)
		}

		if dsfOk {
			p.DiffSuppressFunc = &dsf.DiffSuppressFunc
		} else if !(p.Computed && !p.Optional) {
			p.DiffSuppressFunc = p.DefaultDiffSuppress()
		}

		vf := CustomValidationDetails{}
		vfOk, err := overrides.PropertyOverrideWithDetails(CustomValidation, p, &vf, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom validation func: %v", err)
		}

		if vfOk {
			p.ValidateFunc = &vf.Function
		}

		if p.Type.IsSet() {
			shf := SetHashFuncDetails{}
			shfOk, err := overrides.PropertyOverrideWithDetails(SetHashFunc, p, &shf, location)
			if err != nil {
				return nil, fmt.Errorf("failed to decode set hash func: %v", err)
			}

			if shfOk {
				p.SetHashFunc = &shf.Function
			} else {
				p.SetHashFunc = p.DefaultSetHashFunc()
			}
		}

		cm := CustomConfigModeDetails{}
		cmOk, err := overrides.PropertyOverrideWithDetails(CustomConfigMode, p, &cm, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom config mode func: %v", err)
		}
		if cmOk {
			p.ConfigMode = &cm.Mode
		}

		rd := &RemovedDetails{}
		rdOk, err := overrides.PropertyOverrideWithDetails(Removed, p, rd, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode removed details")
		}
		if rdOk {
			p.Removed = &rd.Message
		}

		dd := &DeprecatedDetails{}
		ddOk, err := overrides.PropertyOverrideWithDetails(Deprecated, p, dd, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode deprecated details")
		}
		if ddOk {
			p.Deprecated = &dd.Message
		}

		if overrides.PropertyOverride(CollapsedObject, p, location) {
			p.Collapsed = true
			if p.parent == nil {
				collapseSS := fmt.Sprintf("setStateForCollapsedObject(d, %s)", p.flattenGetterWithParent("res"))
				p.StateSetter = &collapseSS
			}
			collapseSG := fmt.Sprintf("expand%s%sCollapsed(d)", p.resource.PathType(), p.PackagePath())
			p.StateGetter = &collapseSG
		}

		// Add any new imports as needed
		if ls := p.GetRequiredFileImports(); len(ls) > 0 {
			resource.additionalFileImportSet.Add(ls...)
		}

		csgd := CustomStateGetterDetails{}
		csgdOk, err := overrides.PropertyOverrideWithDetails(CustomStateGetter, p, &csgd, location)
		if err != nil {
			return nil, fmt.Errorf("failed to decode custom state getter details with err %v", err)
		}
		if csgdOk {
			p.StateGetter = &csgd.Function
		}

		if overrides.PropertyOverride(EnumBool, p, location) {
			p.EnumBool = true
			p.Type.typ.Type = "string"
			var parent string
			if p.parent == nil {
				parent = "res"
			} else {
				parent = "obj"
			}
			enumBoolSS := fmt.Sprintf("d.Set(%q, tpgdclresource.FlattenEnumBool(%s.%s))", p.Name(), parent, p.PackageName)
			p.StateSetter = &enumBoolSS
			enumBoolSG := fmt.Sprintf("tpgdclresource.ExpandEnumBool(d.Get(%q))", p.Name())
			p.StateGetter = &enumBoolSG
		}

		if overrides.PropertyOverride(GenerateIfNotSet, p, location) {
			p.Computed = true
			p.Required = false
			p.Optional = true
			ig := fmt.Sprintf("generateIfNotSet(d, %q, %q)", p.Name(), "tfgen")
			p.IdentityGetter = &ig
			n := fmt.Sprintf("&%s", p.Name())
			p.StateGetter = &n
		}

		if overrides.PropertyOverride(NamePrefix, p, location) {
			p.Computed = true
			p.Required = false
			p.Optional = true
			ig := fmt.Sprintf("generateIfNotSet(d, %q, d.Get(%q).(string))", p.Name(), "name_prefix")
			p.IdentityGetter = &ig
			n := fmt.Sprintf("&%s", p.Name())
			p.StateGetter = &n

			// plus, add the "name_prefix" property.
			props = append(props, Property{
				title:    "name_prefix",
				Type:     p.Type,
				resource: resource,
				parent:   parent,
				Optional: true,
				Computed: true,
				ForceNew: true,
			})
		}

		if p.ref != "" {
			resource.ReusedTypes = resource.RegisterReusedType(p)
		}

		// Add the "effective_labels" property when the current property is top level "labels" or
		// add the "effective_annotations" property when the current property is top level "annotations"

		if p.IsResourceLabels() || p.IsResourceAnnotations() {
			p.Description = fmt.Sprintf("%s\n\n%s", p.Description, get_labels_field_note(p.title))
			p.Settable = false
			p.StateGetter = nil

			props = append(props, build_effective_labels_field(p, resource, parent))

			if p.IsResourceLabels() {
				props = append(props, build_terraform_labels_field(p, resource, parent))
				p.ForceNew = false
			}
		}

		props = append(props, p)
	}

	// handle conflict fields
	for i, _ := range props {
		p := &props[i]
		if p.JSONCaseConflictsWith != nil {
			for _, cf := range p.JSONCaseConflictsWith {
				if val, ok := conflictsMap[cf]; ok {
					p.ConflictsWith = append(p.ConflictsWith, val)
				} else {
					return nil, fmt.Errorf("Error generating conflict fields. %s is not labeled as a conflict field in DCL", cf)
				}

			}
		}
	}

	// sort the properties so they're in a nice order
	sort.SliceStable(props, propComparator(props))

	return props, nil
}