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

Start defining the Schema type. #11

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
86 changes: 86 additions & 0 deletions attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package tfsdk

import (
"context"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// AttributeType defines an interface for describing a kind of attribute.
// AttributeTypes are collections of constraints and behaviors such that they
// can be reused on multiple attributes easily.
type AttributeType interface {
// TerraformType returns the tftypes.Type that should be used to
// represent this type. This constrains what user input will be
// accepted and what kind of data can be set in state. The framework
// will use this to translate the AttributeType to something Terraform
// can understand.
TerraformType(context.Context) tftypes.Type

// ValueFromTerraform returns an AttributeValue given a tftypes.Value.
// This is meant to convert the tftypes.Value into a more convenient Go
// type for the provider to consume the data with.
ValueFromTerraform(context.Context, tftypes.Value) (AttributeValue, error)

// Equal must return true if the AttributeType is considered
// semantically equal to the AttributeType passed as an argument.
Equal(AttributeType) bool
}

// AttributeTypeWithValidate extends the AttributeType interface to include a
// Validate method, used to bundle consistent validation logic with the
// AttributeType.
type AttributeTypeWithValidate interface {
AttributeType

// Validate returns any warnings or errors about the value that is
// being used to populate the AttributeType. It is generally used to
// check the data format and ensure that it complies with the
// requirements of the AttributeType.
//
// TODO: don't use tfprotov6.Diagnostic, use our type
Validate(context.Context, tftypes.Value) []*tfprotov6.Diagnostic
}

// AttributeTypeWithPlaintextDescription extends the AttributeType interface to
// include a Description method, used to bundle extra information to include in
// attribute descriptions with the AttributeType. It expects the description to
// be written as plain text, with no special formatting.
type AttributeTypeWithPlaintextDescription interface {
AttributeType

// Description returns a practitioner-friendly explanation of the type
// and the constraints of the data it accepts and returns. It will be
// combined with the Description associated with the Attribute.
Description(context.Context) string
}

// AttributeTypeWithMarkdownDescription extends the AttributeType interface to
// include a MarkdownDescription method, used to bundle extra information to
// include in attribute descriptions with the AttributeType. It expects the
// description to be formatted for display with Markdown.
type AttributeTypeWithMarkdownDescription interface {
// MarkdownDescription returns a practitioner-friendly explanation of
// the type and the constraints of the data it accepts and returns. It
// will be combined with the MarkdownDescription associated with the
// Attribute.
MarkdownDescription(context.Context) string
}

// AttributeValue defines an interface for describing data associated with an attribute.
// AttributeValues allow provider developers to specify data in a convenient format, and
// have it transparently be converted to formats Terraform understands.
type AttributeValue interface {
// ToTerraformValue returns the data contained in the AttributeValue as
// a Go type that tftypes.NewAttributeValue will accept.
ToTerraformValue(context.Context) (interface{}, error)

// SetTerraformValue updates the data in AttributeValue to match the
// passed tftypes.Value
SetTerraformValue(context.Context, tftypes.Value) error

// Equal must return true if the AttributeValue is considered
// semantically equal to the AttributeValue passed as an argument.
Equal(AttributeValue) bool
}
12 changes: 0 additions & 12 deletions framework.go

This file was deleted.

5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/hashicorp/terraform-plugin-framework

go 1.16

require github.com/hashicorp/terraform-plugin-go v0.2.1
require (
github.com/google/go-cmp v0.5.2
github.com/hashicorp/terraform-plugin-go v0.3.0
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
github.com/hashicorp/terraform-plugin-go v0.2.1 h1:EW/R8bB2Zbkjmugzsy1d27yS8/0454b3MtYHkzOknqA=
github.com/hashicorp/terraform-plugin-go v0.2.1/go.mod h1:10V6F3taeDWVAoLlkmArKttR3IULlRWFAGtQIQTIDr4=
github.com/hashicorp/terraform-plugin-go v0.3.0 h1:AJqYzP52JFYl9NABRI7smXI1pNjgR5Q/y2WyVJ/BOZA=
github.com/hashicorp/terraform-plugin-go v0.3.0/go.mod h1:dFHsQMaTLpON2gWhVWT96fvtlc/MF1vSy3OdMhWBzdM=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
Expand Down
96 changes: 96 additions & 0 deletions internal/reflect/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package reflect

import (
"context"
"errors"
"reflect"
"regexp"
"strings"

"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// trueReflectValue returns the reflect.Value for `in` after derefencing all
// the pointers and unwrapping all the interfaces. It's the concrete value
// beneath it all.
func trueReflectValue(val reflect.Value) reflect.Value {
kind := val.Type().Kind()
for kind == reflect.Interface || kind == reflect.Ptr {
innerVal := val.Elem()
if !innerVal.IsValid() {
break
}
val = innerVal
kind = val.Type().Kind()
}
return val
}

// commaSeparatedString returns an English joining of the strings in `in`,
// using "and" and commas as appropriate.
func commaSeparatedString(in []string) string {
switch len(in) {
case 0:
return ""
case 1:
return in[0]
case 2:
return strings.Join(in, " and ")
default:
in[len(in)-1] = "and " + in[len(in)-1]
return strings.Join(in, ", ")
}
}

// getStructTags returns a map of Terraform field names to their position in
// the tags of the struct `in`. `in` must be a struct.
func getStructTags(ctx context.Context, in reflect.Value, path *tftypes.AttributePath) (map[string]int, error) {
tags := map[string]int{}
typ := trueReflectValue(in).Type()
if typ.Kind() != reflect.Struct {
return nil, path.NewErrorf("can't get struct tags of %s, is not a struct", in.Type())
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" {
// skip unexported fields
continue
}
tag := field.Tag.Get(`tfsdk`)
if tag == "-" {
// skip explicitly excluded fields
continue
}
if tag == "" {
return nil, path.NewErrorf(`need a struct tag for "tfsdk" on %s`, field.Name)
}
path := path.WithAttributeName(tag)
if !isValidFieldName(tag) {
return nil, path.NewError(errors.New("invalid field name, must only use lowercase letters, underscores, and numbers, and must start with a letter"))
}
if other, ok := tags[tag]; ok {
return nil, path.NewErrorf("can't use field name for both %s and %s", typ.Field(other).Name, field.Name)
}
tags[tag] = i
}
return tags, nil
}

// isValidFieldName returns true if `name` can be used as a field name in a
// Terraform resource or data source.
func isValidFieldName(name string) bool {
re := regexp.MustCompile("^[a-z][a-z0-9_]*$")
return re.MatchString(name)
}

// canBeNil returns true if `target`'s type can hold a nil value
func canBeNil(target reflect.Value) bool {
switch target.Kind() {
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface:
// these types can all hold nils
return true
default:
// nothing else can be set to nil
return false
}
}