Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider Allowing Usage of Custom Types for Nested Attributes #532

Closed
bendbennett opened this issue Nov 10, 2022 · 2 comments
Closed

Consider Allowing Usage of Custom Types for Nested Attributes #532

bendbennett opened this issue Nov 10, 2022 · 2 comments
Labels
enhancement New feature or request
Milestone

Comments

@bendbennett
Copy link
Contributor

Module version

github.com/hashicorp/terraform-plugin-framework v0.15.0

Use-cases

In order to provider greater flexibility when using nested attributes, the ability to use a custom type for a nested attribute would be beneficial. This would provide the same advantages afforded when using custom types for simple types, for example using an RFC3339 type rather than a string type for the handling of strings that hold RFC3339 timestamps. See usage of a prototype for an RFC3339 type for an example.

Attempted Solutions

Helper functions can be created which will handle nested attributes in a specific way, but this is not as streamlined as having custom types for nested attributes where functions can be directly associated with the type.

Proposal

Refactoring to expose the NestedAttributes interface and having all attr.Value and any custom nested attribute implement a new FrameworkValue interface allows custom types to be used for nested attributes.

For example, the following terraform configuration, provider resource schema, model (for unmarshalling onto) and create function should allow the usage of a function associated with the custom type. Both a "standard" list nested attribute and a list nested attribute using a custom type are illustrated for comparison

Terraform Configuration

resource "example_resource" "example" {
  list_nested_attribute = [
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
    },
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
    }
  ]

  list_nested_attribute_custom = [
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
    },
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
    }
  ]

Provider Resource Schema

func (e *exampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
	return tfsdk.Schema{
		Attributes: map[string]tfsdk.Attribute{
			"list_nested_attribute": {
				Optional: true,
				Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
					"int64_attribute": {
						Optional: true,
						Type:     types.Int64Type,
					},
					"list_attribute": {
						Optional: true,
						Type:     types.ListType{ElemType: types.StringType},
					},
				}),
			},
			"list_nested_attribute_custom": {
				Optional: true,
				Attributes: ListNestedAttributesCustomType{
					tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
						"int64_attribute": {
							Optional: true,
							Type:     types.Int64Type,
						},
						"list_attribute": {
							Optional: true,
							Type:     types.ListType{ElemType: types.StringType},
						},
					}),
				},
			},
		},
	}, nil
}

Model

type exampleResourceData struct {
	ListNestedAttribute       types.List                      `tfsdk:"list_nested_attribute"`
	ListNestedAttributeCustom ListNestedAttributesCustomValue `tfsdk:"list_nested_attribute_custom"`

Create func

func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
	var data exampleResourceData

	diags := req.Plan.Get(ctx, &data)
	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}

	data.ListNestedAttributeCustom.CustomFunc(ctx)

	diags = resp.State.Set(ctx, &data)
	resp.Diagnostics.Append(diags...)
}

Custom Type and Value Implementation

The following is an example of of a custom type and custom value that can be used for a custom nested list type described above.

type ListNestedAttributesCustomType struct {
	types.NestedAttributes
}

func (l ListNestedAttributesCustomType) Type() attr.Type {
	return ListNestedAttributesCustomTypeType{
		l.NestedAttributes.Type(),
	}
}

type ListNestedAttributesCustomTypeType struct {
	attr.Type
}

func (l ListNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) {
	val, err := l.Type.ValueFromTerraform(ctx, value)
	if err != nil {
		return nil, err
	}

	return ListNestedAttributesCustomValue{
		val.(types.List),
	}, nil
}

type ListNestedAttributesCustomValue struct {
	types.List
}

func (l ListNestedAttributesCustomValue) ToFrameworkValue() attr.Value {
	return l.List
}

func (l ListNestedAttributesCustomValue) CustomFunc(ctx context.Context) {
	tflog.Info(ctx, "calling CustomFunc on custom list nested attribute")
}

References

@bendbennett bendbennett added the enhancement New feature or request label Nov 10, 2022
bflad added a commit that referenced this issue Nov 18, 2022
…terfaces

Reference: #132
Reference: #508
Reference: #532

Since the initial version of the framework, it has treated nested attributes and blocks as a singular entity with some internal gynastics to handle the one (single; object) or two (list, map, set to object) actual types that make up the nested attribute or block. Having these schema abstractions as single entities caused developer confusion when attempting to extract data or create paths.

Other desirable features for the framework are the ability to customize the types, define validators, and define plan modifiers of nested attributes and blocks. This type customization, validation, or plan modification may need to be on the nested object to simplify provider implementations. Exposing these options with a flat abstraction could be confusing.

This change introduces the concept of a nested attribute object and a nested block object into the `internal/fwschema` package and makes the necessary implementation changes to `tfsdk.Attribute` and `tfsdk.Block`. While the `tfsdk` package schema handling does not expose the new potential enhancements, the upcoming `datasource`, `provider`, and `resource` schema handling will. The existing unit testing shows no regressions and the new unit tests with validation show it should be possible to implement nested object validation (and when implemented in the near future, plan modification).

This changeset additionally migrates more `tfsdk.Schema` logic into the `internal/fwschema` package in further preparation for creating additional schema implementations that will benefit from the existing schema logic.
bflad added a commit that referenced this issue Nov 18, 2022
…terfaces

Reference: #132
Reference: #508
Reference: #532

Since the initial version of the framework, it has treated nested attributes and blocks as a singular entity with some internal gynastics to handle the one (single; object) or two (list, map, set to object) actual types that make up the nested attribute or block. Having these schema abstractions as single entities caused developer confusion when attempting to extract data or create paths.

Other desirable features for the framework are the ability to customize the types, define validators, and define plan modifiers of nested attributes and blocks. This type customization, validation, or plan modification may need to be on the nested object to simplify provider implementations. Exposing these options with a flat abstraction could be confusing.

This change introduces the concept of a nested attribute object and a nested block object into the `internal/fwschema` package and makes the necessary implementation changes to `tfsdk.Attribute` and `tfsdk.Block`. While the `tfsdk` package schema handling does not expose the new potential enhancements, the upcoming `datasource`, `provider`, and `resource` schema handling will. The existing unit testing shows no regressions and the new unit tests with validation show it should be possible to implement nested object validation (and when implemented in the near future, plan modification).

This changeset additionally migrates more `tfsdk.Schema` logic into the `internal/fwschema` package in further preparation for creating additional schema implementations that will benefit from the existing schema logic.
bflad added a commit that referenced this issue Nov 18, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `datasource/schema` package, which contains schema interfaces and types relevant to data sources, such as omitting plan modifiers and schema versioning. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for data sources. The framework design will also raise compiler-time errors for errant typing of validators. Upstream updates to terraform-plugin-framework-validators to support the new `schema/validator` package are in progress.

These changes require unit testing, terraform-provider-corner testing, and website documentation updates before being ready for primetime. Now is a great time for general feedback before those efforts begin in earnest.
bflad added a commit that referenced this issue Nov 18, 2022
…terfaces (#545)

Reference: #132
Reference: #508
Reference: #532

Since the initial version of the framework, it has treated nested attributes and blocks as a singular entity with some internal gynastics to handle the one (single; object) or two (list, map, set to object) actual types that make up the nested attribute or block. Having these schema abstractions as single entities caused developer confusion when attempting to extract data or create paths.

Other desirable features for the framework are the ability to customize the types, define validators, and define plan modifiers of nested attributes and blocks. This type customization, validation, or plan modification may need to be on the nested object to simplify provider implementations. Exposing these options with a flat abstraction could be confusing.

This change introduces the concept of a nested attribute object and a nested block object into the `internal/fwschema` package and makes the necessary implementation changes to `tfsdk.Attribute` and `tfsdk.Block`. While the `tfsdk` package schema handling does not expose the new potential enhancements, the upcoming `datasource`, `provider`, and `resource` schema handling will. The existing unit testing shows no regressions and the new unit tests with validation show it should be possible to implement nested object validation (and when implemented in the near future, plan modification).

This changeset additionally migrates more `tfsdk.Schema` logic into the `internal/fwschema` package in further preparation for creating additional schema implementations that will benefit from the existing schema logic.
bflad added a commit that referenced this issue Nov 22, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `datasource/schema` package, which contains schema interfaces and types relevant to data sources, such as omitting plan modifiers and schema versioning. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for data sources. The framework design will also raise compiler-time errors for errant typing of validators. Upstream updates to terraform-plugin-framework-validators to support the new `schema/validator` package are in progress.

These changes require unit testing, terraform-provider-corner testing, and website documentation updates before being ready for primetime. Now is a great time for general feedback before those efforts begin in earnest.
bflad added a commit that referenced this issue Nov 22, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `datasource/schema` package, which contains schema interfaces and types relevant to data sources, such as omitting plan modifiers and schema versioning. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for data sources. The framework design will also raise compiler-time errors for errant typing of validators.
bflad added a commit that referenced this issue Nov 28, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `datasource/schema` package, which contains schema interfaces and types relevant to data sources, such as omitting plan modifiers and schema versioning. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for data sources. The framework design will also raise compiler-time errors for errant typing of validators.

No changes are required for data handling in the `Read` method.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ThingDataSource struct{}

func (d ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
				Validators: []validator.String{
					stringvalidator.LengthBetween(3, 256),
				},
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Optional: true,
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
		Blocks: map[string]schema.Block{
			"list_block": schema.ListNestedBlock{
				NestedObject: schema.NestedBlockObject{
					Attributes: map[string]schema.Attribute{
						"float64_attribute": schema.Float64Attribute{
							Optional: true,
							Validators: []validator.Float64{
								float64validator.OneOf(1.2, 2.4),
							},
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
		},
	}
}
```

To migrate a data source schema:

- Add `github.com/hashicorp/terraform-plugin-framework/datasource/schema` to the `import` statement 
- Switch the `datasource.DataSource` implementation `GetSchema` method to `Schema` whose response includes a `schema.Schema` from the new package.

Prior implementation:

```go
func (d ThingDataSource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (d ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
  resp.Schema = schema.Schema{/*...*/}
}
```

- Switch `map[string]tfsdk.Attribute` with `map[string]schema.Attribute`
- Switch `map[string]tfsdk.Block` with `map[string]schema.Block`
- Switch individual attribute and block definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `schema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.SingleNestedAttribute{
  Attributes: map[string]schema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListNestedAttribute{
  NestedObject: schema.NestedAttributeObject{
    Attributes: map[string]schema.Attribute{/*...*/},
  },
  Required: true,
}
```

Prior collection blocks type (`tfsdk.Block`) attribute implementation:

```go
// The "tfsdk.Block" could be omitted inside a map[string]tfsdk.Block
tfsdk.Block{
  Attributes: map[string]tfsdk.Attribute{/*...*/},
  Blocks: map[string]tfsdk.Block{/*...*/},
  NestingMode: tfsdk.BlockNestingModeList,
},
```

Migrated implementation:

```go
// The schema.XXXBlock must be declared inside map[string]schema.Block
schema.ListNestedBlock{
  NestedObject: schema.NestedBlockObject{
    Attributes: map[string]schema.Attribute{/*...*/},
    Blocks: map[string]schema.Block{/*...*/},
  },
}
```
bflad added a commit that referenced this issue Nov 28, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `provider/schema` package, which contains schema interfaces and types relevant to providers, such as omitting `Computed`, plan modifiers, and schema versioning. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for providers. The framework design will also raise compiler-time errors for errant typing of validators.

No changes are required for data handling in the `Read` method.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/provider/schema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ExampleCloudProvider struct{}

func (p ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
				Validators: []validator.String{
					stringvalidator.LengthBetween(3, 256),
				},
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Optional: true,
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
		Blocks: map[string]schema.Block{
			"list_block": schema.ListNestedBlock{
				NestedObject: schema.NestedBlockObject{
					Attributes: map[string]schema.Attribute{
						"float64_attribute": schema.Float64Attribute{
							Optional: true,
							Validators: []validator.Float64{
								float64validator.OneOf(1.2, 2.4),
							},
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
		},
	}
}
```

To migrate a provider schema:

- Add `github.com/hashicorp/terraform-plugin-framework/provider/schema` to the `import` statement
- Switch the `provider.Provider` implementation `GetSchema` method to `Schema` whose response includes a `schema.Schema` from the new package.

Prior implementation:

```go
func (p ExampleCloudProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (p ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
  resp.Schema = schema.Schema{/*...*/}
}
```

If the provider requires no schema, the method can be entirely empty.

- Switch `map[string]tfsdk.Attribute` with `map[string]schema.Attribute`
- Switch `map[string]tfsdk.Block` with `map[string]schema.Block`
- Switch individual attribute and block definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `schema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.SingleNestedAttribute{
  Attributes: map[string]schema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListNestedAttribute{
  NestedObject: schema.NestedAttributeObject{
    Attributes: map[string]schema.Attribute{/*...*/},
  },
  Required: true,
}
```

Prior collection blocks type (`tfsdk.Block`) attribute implementation:

```go
// The "tfsdk.Block" could be omitted inside a map[string]tfsdk.Block
tfsdk.Block{
  Attributes: map[string]tfsdk.Attribute{/*...*/},
  Blocks: map[string]tfsdk.Block{/*...*/},
  NestingMode: tfsdk.BlockNestingModeList,
},
```

Migrated implementation:

```go
// The schema.XXXBlock must be declared inside map[string]schema.Block
schema.ListNestedBlock{
  NestedObject: schema.NestedBlockObject{
    Attributes: map[string]schema.Attribute{/*...*/},
    Blocks: map[string]schema.Block{/*...*/},
  },
}
```
bflad added a commit that referenced this issue Nov 28, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `provider/schema` package, which contains schema interfaces and types relevant to providers, such as omitting `Computed`, plan modifiers, and schema versioning. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for providers. The framework design will also raise compiler-time errors for errant typing of validators.

No changes are required for data handling in the `Read` method.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/provider/schema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ExampleCloudProvider struct{}

func (p ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
				Validators: []validator.String{
					stringvalidator.LengthBetween(3, 256),
				},
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Optional: true,
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
		Blocks: map[string]schema.Block{
			"list_block": schema.ListNestedBlock{
				NestedObject: schema.NestedBlockObject{
					Attributes: map[string]schema.Attribute{
						"float64_attribute": schema.Float64Attribute{
							Optional: true,
							Validators: []validator.Float64{
								float64validator.OneOf(1.2, 2.4),
							},
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
		},
	}
}
```

To migrate a provider schema:

- Add `github.com/hashicorp/terraform-plugin-framework/provider/schema` to the `import` statement
- Switch the `provider.Provider` implementation `GetSchema` method to `Schema` whose response includes a `schema.Schema` from the new package.

Prior implementation:

```go
func (p ExampleCloudProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (p ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
  resp.Schema = schema.Schema{/*...*/}
}
```

If the provider requires no schema, the method can be entirely empty.

- Switch `map[string]tfsdk.Attribute` with `map[string]schema.Attribute`
- Switch `map[string]tfsdk.Block` with `map[string]schema.Block`
- Switch individual attribute and block definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `schema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.SingleNestedAttribute{
  Attributes: map[string]schema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListNestedAttribute{
  NestedObject: schema.NestedAttributeObject{
    Attributes: map[string]schema.Attribute{/*...*/},
  },
  Required: true,
}
```

Prior collection blocks type (`tfsdk.Block`) attribute implementation:

```go
// The "tfsdk.Block" could be omitted inside a map[string]tfsdk.Block
tfsdk.Block{
  Attributes: map[string]tfsdk.Attribute{/*...*/},
  Blocks: map[string]tfsdk.Block{/*...*/},
  NestingMode: tfsdk.BlockNestingModeList,
},
```

Migrated implementation:

```go
// The schema.XXXBlock must be declared inside map[string]schema.Block
schema.ListNestedBlock{
  NestedObject: schema.NestedBlockObject{
    Attributes: map[string]schema.Attribute{/*...*/},
    Blocks: map[string]schema.Block{/*...*/},
  },
}
```
bflad added a commit that referenced this issue Nov 29, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `resource/schema` package, which contains schema interfaces and types relevant to resources. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization, plan modification, and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for resources. The framework design will also raise compiler-time errors for errant typing of validators.

No changes are required for data handling in any other `resource.Resource` methods.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/resource"
	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ThingResource struct{}

func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
				Validators: []validator.String{
					stringvalidator.LengthBetween(3, 256),
				},
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Optional: true,
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
		Blocks: map[string]schema.Block{
			"list_block": schema.ListNestedBlock{
				NestedObject: schema.NestedBlockObject{
					Attributes: map[string]schema.Attribute{
						"float64_attribute": schema.Float64Attribute{
							Optional: true,
							Validators: []validator.Float64{
								float64validator.OneOf(1.2, 2.4),
							},
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
		},
	}
}
```

To migrate a resource schema:

- Add `github.com/hashicorp/terraform-plugin-framework/resource/schema` to the `import` statement
- Switch the `resource.Resource` implementation `GetSchema` method to `Schema` whose response includes a `schema.Schema` from the new package.

Prior implementation:

```go
func (r ThingResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
  resp.Schema = schema.Schema{/*...*/}
}
```

If the resource requires no schema, the method can be entirely empty.

- Switch `map[string]tfsdk.Attribute` with `map[string]schema.Attribute`
- Switch `map[string]tfsdk.Block` with `map[string]schema.Block`
- Switch individual attribute and block definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `schema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.SingleNestedAttribute{
  Attributes: map[string]schema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListNestedAttribute{
  NestedObject: schema.NestedAttributeObject{
    Attributes: map[string]schema.Attribute{/*...*/},
  },
  Required: true,
}
```

Prior collection blocks type (`tfsdk.Block`) attribute implementation:

```go
// The "tfsdk.Block" could be omitted inside a map[string]tfsdk.Block
tfsdk.Block{
  Attributes: map[string]tfsdk.Attribute{/*...*/},
  Blocks: map[string]tfsdk.Block{/*...*/},
  NestingMode: tfsdk.BlockNestingModeList,
},
```

Migrated implementation:

```go
// The schema.XXXBlock must be declared inside map[string]schema.Block
schema.ListNestedBlock{
  NestedObject: schema.NestedBlockObject{
    Attributes: map[string]schema.Attribute{/*...*/},
    Blocks: map[string]schema.Block{/*...*/},
  },
}
```
bflad added a commit that referenced this issue Nov 29, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `resource/schema` package, which contains schema interfaces and types relevant to resources. This new schema implementation also provides strongly typed attributes, nested attributes, and blocks with customizable types. Nested attributes and blocks are exposed with a separate nested object for customization, plan modification, and validation.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for resources. The framework design will also raise compiler-time errors for errant typing of validators.

No changes are required for data handling in any other `resource.Resource` methods.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/resource"
	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ThingResource struct{}

func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
				Validators: []validator.String{
					stringvalidator.LengthBetween(3, 256),
				},
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Optional: true,
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
		Blocks: map[string]schema.Block{
			"list_block": schema.ListNestedBlock{
				NestedObject: schema.NestedBlockObject{
					Attributes: map[string]schema.Attribute{
						"float64_attribute": schema.Float64Attribute{
							Optional: true,
							Validators: []validator.Float64{
								float64validator.OneOf(1.2, 2.4),
							},
						},
					},
					Validators: []validator.Object{ /*...*/ },
				},
				Validators: []validator.List{
					listvalidator.SizeAtMost(2),
				},
			},
		},
	}
}
```

To migrate a resource schema:

- Add `github.com/hashicorp/terraform-plugin-framework/resource/schema` to the `import` statement
- Switch the `resource.Resource` implementation `GetSchema` method to `Schema` whose response includes a `schema.Schema` from the new package.

Prior implementation:

```go
func (r ThingResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
  resp.Schema = schema.Schema{/*...*/}
}
```

If the resource requires no schema, the method can be entirely empty.

- Switch `map[string]tfsdk.Attribute` with `map[string]schema.Attribute`
- Switch `map[string]tfsdk.Block` with `map[string]schema.Block`
- Switch individual attribute and block definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `schema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.SingleNestedAttribute{
  Attributes: map[string]schema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The schema.XXXAttribute must be declared inside map[string]schema.Attribute
schema.ListNestedAttribute{
  NestedObject: schema.NestedAttributeObject{
    Attributes: map[string]schema.Attribute{/*...*/},
  },
  Required: true,
}
```

Prior collection blocks type (`tfsdk.Block`) attribute implementation:

```go
// The "tfsdk.Block" could be omitted inside a map[string]tfsdk.Block
tfsdk.Block{
  Attributes: map[string]tfsdk.Attribute{/*...*/},
  Blocks: map[string]tfsdk.Block{/*...*/},
  NestingMode: tfsdk.BlockNestingModeList,
},
```

Migrated implementation:

```go
// The schema.XXXBlock must be declared inside map[string]schema.Block
schema.ListNestedBlock{
  NestedObject: schema.NestedBlockObject{
    Attributes: map[string]schema.Attribute{/*...*/},
    Blocks: map[string]schema.Block{/*...*/},
  },
}
```
bflad added a commit that referenced this issue Nov 29, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `provider/metaschema` package, which contains schema interfaces and types relevant to provider meta schemas, such as omitting `Computed`, `DeprecatedMessage`, `Sensitive`, plan modifiers, validators, blocks, schema descriptions, and schema versioning. This new schema implementation also provides strongly typed attributes and nested attributes with customizable types. Nested attributes are exposed with a separate nested object for customization.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for provider meta schemas.

No changes are required for data handling during requests.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ExampleCloudProvider struct{}

func (p ExampleCloudProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
	resp.Schema = metaschema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
				},
				Optional: true,
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
	}
}
```

To migrate a provider meta schema:

- Add `github.com/hashicorp/terraform-plugin-framework/provider/metaschema` to the `import` statement
- Switch the `provider.ProviderWithMetaSchema` implementation `GetMetaSchema` method to `MetaSchema` whose response includes a `metaschema.Schema` from the new package.

Prior implementation:

```go
func (p ExampleCloudProvider) GetMetaSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (p ExampleCloudProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
  resp.Schema =  metaschema.Schema{/*...*/}
}
```

If the provider requires no meta schema, the method can be removed entirely.

- Switch `map[string]tfsdk.Attribute` with `map[string]metaschema.Attribute`
- Switch individual attribute  definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `metaschema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.SingleNestedAttribute{
  Attributes: map[string]metaschema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.ListNestedAttribute{
  NestedObject: metaschema.NestedAttributeObject{
    Attributes: map[string]metaschema.Attribute{/*...*/},
  },
  Required: true,
}
```
bflad added a commit that referenced this issue Nov 29, 2022
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `provider/metaschema` package, which contains schema interfaces and types relevant to provider meta schemas, such as omitting `Computed`, `DeprecatedMessage`, `Sensitive`, plan modifiers, validators, blocks, schema descriptions, and schema versioning. This new schema implementation also provides strongly typed attributes and nested attributes with customizable types. Nested attributes are exposed with a separate nested object for customization.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for provider meta schemas.

No changes are required for data handling during requests.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ExampleCloudProvider struct{}

func (p ExampleCloudProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
	resp.Schema = metaschema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
				},
				Optional: true,
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
	}
}
```

To migrate a provider meta schema:

- Add `github.com/hashicorp/terraform-plugin-framework/provider/metaschema` to the `import` statement
- Switch the `provider.ProviderWithMetaSchema` implementation `GetMetaSchema` method to `MetaSchema` whose response includes a `metaschema.Schema` from the new package.

Prior implementation:

```go
func (p ExampleCloudProvider) GetMetaSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (p ExampleCloudProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
  resp.Schema =  metaschema.Schema{/*...*/}
}
```

If the provider requires no meta schema, the method can be removed entirely.

- Switch `map[string]tfsdk.Attribute` with `map[string]metaschema.Attribute`
- Switch individual attribute  definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `metaschema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.SingleNestedAttribute{
  Attributes: map[string]metaschema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.ListNestedAttribute{
  NestedObject: metaschema.NestedAttributeObject{
    Attributes: map[string]metaschema.Attribute{/*...*/},
  },
  Required: true,
}
```
@bflad bflad added this to the v0.17.0 milestone Dec 12, 2022
@bflad
Copy link
Member

bflad commented Dec 12, 2022

Providers should now be able to implement this using the new datasource/schema, provider/schema, and resource/schema packages available in v0.17.0. 👍

@bflad bflad closed this as completed Dec 12, 2022
@github-actions
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants