1package convert
2
3import (
4	"encoding/json"
5	"reflect"
6	"sort"
7
8	"github.com/hashicorp/terraform/internal/configs/configschema"
9	"github.com/hashicorp/terraform/internal/providers"
10	proto "github.com/hashicorp/terraform/internal/tfplugin5"
11)
12
13// ConfigSchemaToProto takes a *configschema.Block and converts it to a
14// proto.Schema_Block for a grpc response.
15func ConfigSchemaToProto(b *configschema.Block) *proto.Schema_Block {
16	block := &proto.Schema_Block{
17		Description:     b.Description,
18		DescriptionKind: protoStringKind(b.DescriptionKind),
19		Deprecated:      b.Deprecated,
20	}
21
22	for _, name := range sortedKeys(b.Attributes) {
23		a := b.Attributes[name]
24
25		attr := &proto.Schema_Attribute{
26			Name:            name,
27			Description:     a.Description,
28			DescriptionKind: protoStringKind(a.DescriptionKind),
29			Optional:        a.Optional,
30			Computed:        a.Computed,
31			Required:        a.Required,
32			Sensitive:       a.Sensitive,
33			Deprecated:      a.Deprecated,
34		}
35
36		ty, err := json.Marshal(a.Type)
37		if err != nil {
38			panic(err)
39		}
40
41		attr.Type = ty
42
43		block.Attributes = append(block.Attributes, attr)
44	}
45
46	for _, name := range sortedKeys(b.BlockTypes) {
47		b := b.BlockTypes[name]
48		block.BlockTypes = append(block.BlockTypes, protoSchemaNestedBlock(name, b))
49	}
50
51	return block
52}
53
54func protoStringKind(k configschema.StringKind) proto.StringKind {
55	switch k {
56	default:
57		return proto.StringKind_PLAIN
58	case configschema.StringMarkdown:
59		return proto.StringKind_MARKDOWN
60	}
61}
62
63func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Schema_NestedBlock {
64	var nesting proto.Schema_NestedBlock_NestingMode
65	switch b.Nesting {
66	case configschema.NestingSingle:
67		nesting = proto.Schema_NestedBlock_SINGLE
68	case configschema.NestingGroup:
69		nesting = proto.Schema_NestedBlock_GROUP
70	case configschema.NestingList:
71		nesting = proto.Schema_NestedBlock_LIST
72	case configschema.NestingSet:
73		nesting = proto.Schema_NestedBlock_SET
74	case configschema.NestingMap:
75		nesting = proto.Schema_NestedBlock_MAP
76	default:
77		nesting = proto.Schema_NestedBlock_INVALID
78	}
79	return &proto.Schema_NestedBlock{
80		TypeName: name,
81		Block:    ConfigSchemaToProto(&b.Block),
82		Nesting:  nesting,
83		MinItems: int64(b.MinItems),
84		MaxItems: int64(b.MaxItems),
85	}
86}
87
88// ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
89func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
90	return providers.Schema{
91		Version: s.Version,
92		Block:   ProtoToConfigSchema(s.Block),
93	}
94}
95
96// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
97// to a terraform *configschema.Block.
98func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {
99	block := &configschema.Block{
100		Attributes: make(map[string]*configschema.Attribute),
101		BlockTypes: make(map[string]*configschema.NestedBlock),
102
103		Description:     b.Description,
104		DescriptionKind: schemaStringKind(b.DescriptionKind),
105		Deprecated:      b.Deprecated,
106	}
107
108	for _, a := range b.Attributes {
109		attr := &configschema.Attribute{
110			Description:     a.Description,
111			DescriptionKind: schemaStringKind(a.DescriptionKind),
112			Required:        a.Required,
113			Optional:        a.Optional,
114			Computed:        a.Computed,
115			Sensitive:       a.Sensitive,
116			Deprecated:      a.Deprecated,
117		}
118
119		if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
120			panic(err)
121		}
122
123		block.Attributes[a.Name] = attr
124	}
125
126	for _, b := range b.BlockTypes {
127		block.BlockTypes[b.TypeName] = schemaNestedBlock(b)
128	}
129
130	return block
131}
132
133func schemaStringKind(k proto.StringKind) configschema.StringKind {
134	switch k {
135	default:
136		return configschema.StringPlain
137	case proto.StringKind_MARKDOWN:
138		return configschema.StringMarkdown
139	}
140}
141
142func schemaNestedBlock(b *proto.Schema_NestedBlock) *configschema.NestedBlock {
143	var nesting configschema.NestingMode
144	switch b.Nesting {
145	case proto.Schema_NestedBlock_SINGLE:
146		nesting = configschema.NestingSingle
147	case proto.Schema_NestedBlock_GROUP:
148		nesting = configschema.NestingGroup
149	case proto.Schema_NestedBlock_LIST:
150		nesting = configschema.NestingList
151	case proto.Schema_NestedBlock_MAP:
152		nesting = configschema.NestingMap
153	case proto.Schema_NestedBlock_SET:
154		nesting = configschema.NestingSet
155	default:
156		// In all other cases we'll leave it as the zero value (invalid) and
157		// let the caller validate it and deal with this.
158	}
159
160	nb := &configschema.NestedBlock{
161		Nesting:  nesting,
162		MinItems: int(b.MinItems),
163		MaxItems: int(b.MaxItems),
164	}
165
166	nested := ProtoToConfigSchema(b.Block)
167	nb.Block = *nested
168	return nb
169}
170
171// sortedKeys returns the lexically sorted keys from the given map. This is
172// used to make schema conversions are deterministic. This panics if map keys
173// are not a string.
174func sortedKeys(m interface{}) []string {
175	v := reflect.ValueOf(m)
176	keys := make([]string, v.Len())
177
178	mapKeys := v.MapKeys()
179	for i, k := range mapKeys {
180		keys[i] = k.Interface().(string)
181	}
182
183	sort.Strings(keys)
184	return keys
185}
186