Skip to content

Commit 0aeb0df

Browse files
authored
chore: Improves schema Description / MarkdownDescription (#2605)
* add conversion calls * UpdateSchemaDescription and UpdateDSSchemaDescription * only update empty Description * fix Description * set MarkdownDescripton if empty * remove redundant Description * project_ip_access_list * case SingleNestedBlock * validate blocks * reduce error message len * use reflection * don't allow both descriptions * unify description checks * simplify attr names * refactor tests * only 1 func UpdateSchemaDescription * unit testsg
1 parent 436096d commit 0aeb0df

36 files changed

+332
-143
lines changed

docs/data-sources/encryption_at_rest_private_endpoints.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ output "number_of_endpoints" {
2525

2626
### Required
2727

28-
- `cloud_provider` (String) Human-readable label that identifies the cloud provider for the private endpoints to return.
28+
- `cloud_provider` (String) Label that identifies the cloud provider of the private endpoint.
2929
- `project_id` (String) Unique 24-hexadecimal digit string that identifies your project.
3030

3131
### Read-Only
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package conversion
2+
3+
import (
4+
"reflect"
5+
6+
dsschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
7+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
8+
)
9+
10+
func UpdateSchemaDescription[T schema.Schema | dsschema.Schema](s *T) {
11+
UpdateAttr(s)
12+
}
13+
14+
// UpdateAttr is exported for testing purposes only and should not be used directly.
15+
func UpdateAttr(attr any) {
16+
ptr := reflect.ValueOf(attr)
17+
if ptr.Kind() != reflect.Ptr {
18+
panic("not ptr, please fix caller")
19+
}
20+
v := ptr.Elem()
21+
if v.Kind() != reflect.Struct {
22+
panic("not struct, please fix caller")
23+
}
24+
updateDesc(v)
25+
updateMap(v, "Attributes")
26+
updateMap(v, "Blocks")
27+
updateNested(v, "NestedObject")
28+
}
29+
30+
func updateDesc(v reflect.Value) {
31+
fDescr, fMDDescr := v.FieldByName("Description"), v.FieldByName("MarkdownDescription")
32+
if !fDescr.IsValid() || !fMDDescr.IsValid() {
33+
return
34+
}
35+
if !fDescr.CanSet() || fDescr.Kind() != reflect.String ||
36+
!fMDDescr.CanSet() || fMDDescr.Kind() != reflect.String {
37+
panic("invalid desc fields, please fix caller")
38+
}
39+
strDescr, strMDDescr := fDescr.String(), fMDDescr.String()
40+
if strDescr != "" && strMDDescr != "" {
41+
panic("both descriptions exist, please fix caller: " + strDescr)
42+
}
43+
if strDescr == "" {
44+
fDescr.SetString(fMDDescr.String())
45+
} else {
46+
fMDDescr.SetString(fDescr.String())
47+
}
48+
}
49+
50+
func updateMap(v reflect.Value, mapName string) {
51+
f := v.FieldByName(mapName)
52+
if !f.IsValid() {
53+
return
54+
}
55+
if f.Kind() != reflect.Map {
56+
panic("not map, please fix caller: " + mapName)
57+
}
58+
for _, k := range f.MapKeys() {
59+
v := f.MapIndex(k).Elem()
60+
newPtr := reflect.New(v.Type())
61+
newPtr.Elem().Set(v)
62+
UpdateAttr(newPtr.Interface())
63+
f.SetMapIndex(k, newPtr.Elem())
64+
}
65+
}
66+
67+
func updateNested(v reflect.Value, nestedName string) {
68+
f := v.FieldByName(nestedName)
69+
if !f.IsValid() {
70+
return
71+
}
72+
ptr := f.Addr()
73+
UpdateAttr(ptr.Interface())
74+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package conversion_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
7+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestUpdateSchemaDescription(t *testing.T) {
12+
s := schema.Schema{
13+
Attributes: map[string]schema.Attribute{
14+
"id": schema.StringAttribute{
15+
Computed: true,
16+
},
17+
"project_id": schema.StringAttribute{
18+
Required: true,
19+
MarkdownDescription: "mddesc project_id",
20+
},
21+
"nested": schema.ListNestedAttribute{
22+
Computed: true,
23+
MarkdownDescription: "mddesc nested",
24+
NestedObject: schema.NestedAttributeObject{
25+
Attributes: map[string]schema.Attribute{
26+
"attr1": schema.StringAttribute{
27+
Description: "desc attr1",
28+
Computed: true,
29+
},
30+
"attr2": schema.StringAttribute{
31+
MarkdownDescription: "mddesc attr2",
32+
Computed: true,
33+
},
34+
},
35+
},
36+
},
37+
},
38+
Blocks: map[string]schema.Block{
39+
"list": schema.ListNestedBlock{
40+
MarkdownDescription: "mddesc list",
41+
NestedObject: schema.NestedBlockObject{
42+
Attributes: map[string]schema.Attribute{
43+
"attr3": schema.BoolAttribute{
44+
Optional: true,
45+
Computed: true,
46+
MarkdownDescription: "mddesc attr3",
47+
},
48+
},
49+
},
50+
},
51+
"set": schema.SetNestedBlock{
52+
MarkdownDescription: "mddesc set",
53+
NestedObject: schema.NestedBlockObject{
54+
Attributes: map[string]schema.Attribute{
55+
"attr4": schema.StringAttribute{
56+
Optional: true,
57+
MarkdownDescription: "mddesc attr4",
58+
},
59+
"attr5": schema.Int64Attribute{
60+
Required: true,
61+
MarkdownDescription: "mddesc attr5",
62+
},
63+
},
64+
},
65+
},
66+
},
67+
}
68+
69+
expected := schema.Schema{
70+
Attributes: map[string]schema.Attribute{
71+
"id": schema.StringAttribute{
72+
Computed: true,
73+
},
74+
"project_id": schema.StringAttribute{
75+
Required: true,
76+
Description: "mddesc project_id",
77+
MarkdownDescription: "mddesc project_id",
78+
},
79+
"nested": schema.ListNestedAttribute{
80+
Computed: true,
81+
Description: "mddesc nested",
82+
MarkdownDescription: "mddesc nested",
83+
NestedObject: schema.NestedAttributeObject{
84+
Attributes: map[string]schema.Attribute{
85+
"attr1": schema.StringAttribute{
86+
Description: "desc attr1",
87+
MarkdownDescription: "desc attr1",
88+
Computed: true,
89+
},
90+
"attr2": schema.StringAttribute{
91+
Description: "mddesc attr2",
92+
MarkdownDescription: "mddesc attr2",
93+
Computed: true,
94+
},
95+
},
96+
},
97+
},
98+
},
99+
Blocks: map[string]schema.Block{
100+
"list": schema.ListNestedBlock{
101+
Description: "mddesc list",
102+
MarkdownDescription: "mddesc list",
103+
NestedObject: schema.NestedBlockObject{
104+
Attributes: map[string]schema.Attribute{
105+
"attr3": schema.BoolAttribute{
106+
Optional: true,
107+
Computed: true,
108+
Description: "mddesc attr3",
109+
MarkdownDescription: "mddesc attr3",
110+
},
111+
},
112+
},
113+
},
114+
"set": schema.SetNestedBlock{
115+
Description: "mddesc set",
116+
MarkdownDescription: "mddesc set",
117+
NestedObject: schema.NestedBlockObject{
118+
Attributes: map[string]schema.Attribute{
119+
"attr4": schema.StringAttribute{
120+
Optional: true,
121+
Description: "mddesc attr4",
122+
MarkdownDescription: "mddesc attr4",
123+
},
124+
"attr5": schema.Int64Attribute{
125+
Required: true,
126+
Description: "mddesc attr5",
127+
MarkdownDescription: "mddesc attr5",
128+
},
129+
},
130+
},
131+
},
132+
},
133+
}
134+
135+
conversion.UpdateSchemaDescription(&s)
136+
assert.Equal(t, expected, s)
137+
}
138+
139+
func TestUpdateAttrPanic(t *testing.T) {
140+
testCases := map[string]any{
141+
"not ptr, please fix caller": "no ptr",
142+
"not struct, please fix caller": conversion.Pointer("no struct"),
143+
"invalid desc fields, please fix caller": conversion.Pointer(struct {
144+
Description int
145+
MarkdownDescription int
146+
}{}),
147+
"both descriptions exist, please fix caller: description": conversion.Pointer(struct {
148+
Description string
149+
MarkdownDescription string
150+
}{
151+
Description: "description",
152+
MarkdownDescription: "markdown description",
153+
}),
154+
"not map, please fix caller: Attributes": conversion.Pointer(struct {
155+
Attributes string
156+
}{}),
157+
}
158+
159+
for name, tc := range testCases {
160+
t.Run(name, func(t *testing.T) {
161+
assert.PanicsWithValue(t, name, func() {
162+
conversion.UpdateAttr(tc)
163+
})
164+
})
165+
}
166+
}

internal/provider/provider_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package provider_test
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67

78
"github.com/hashicorp/terraform-plugin-framework/datasource"
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
810
providerfw "github.com/hashicorp/terraform-plugin-framework/provider"
11+
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
912
"github.com/hashicorp/terraform-plugin-framework/resource"
13+
1014
"github.com/mongodb/terraform-provider-mongodbatlas/internal/provider"
1115
)
1216

@@ -30,6 +34,7 @@ func TestResourceSchemas(t *testing.T) {
3034
schemaRequest := resource.SchemaRequest{}
3135
schemaResponse := &resource.SchemaResponse{}
3236
res.Schema(ctx, schemaRequest, schemaResponse)
37+
validateDocumentation(metadataRes.TypeName, schemaResponse)
3338

3439
if schemaResponse.Diagnostics.HasError() {
3540
t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics)
@@ -62,6 +67,7 @@ func TestDataSourceSchemas(t *testing.T) {
6267
schemaRequest := datasource.SchemaRequest{}
6368
schemaResponse := &datasource.SchemaResponse{}
6469
res.Schema(ctx, schemaRequest, schemaResponse)
70+
validateDSDocumentation(metadataRes.TypeName, schemaResponse)
6571

6672
if schemaResponse.Diagnostics.HasError() {
6773
t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics)
@@ -73,3 +79,54 @@ func TestDataSourceSchemas(t *testing.T) {
7379
})
7480
}
7581
}
82+
83+
func validateDocumentation(name string, resp *resource.SchemaResponse) {
84+
checkDescriptor(name, resp.Schema, &resp.Diagnostics)
85+
validateAttributes(name, resp.Schema.GetAttributes(), &resp.Diagnostics)
86+
validateBlocks(name, resp.Schema.GetBlocks(), &resp.Diagnostics)
87+
}
88+
89+
func validateDSDocumentation(name string, resp *datasource.SchemaResponse) {
90+
checkDescriptor(name, resp.Schema, &resp.Diagnostics)
91+
validateAttributes(name, resp.Schema.GetAttributes(), &resp.Diagnostics)
92+
validateBlocks(name, resp.Schema.GetBlocks(), &resp.Diagnostics)
93+
}
94+
95+
func validateAttribute(name string, attr schema.Attribute, diagnostics *diag.Diagnostics) {
96+
checkDescriptor(name, attr, diagnostics)
97+
if nested, ok := attr.(schema.NestedAttribute); ok {
98+
validateAttributes(name, nested.GetNestedObject().GetAttributes(), diagnostics)
99+
}
100+
}
101+
102+
func validateBlock(name string, block schema.Block, diagnostics *diag.Diagnostics) {
103+
checkDescriptor(name, block, diagnostics)
104+
validateAttributes(name, block.GetNestedObject().GetAttributes(), diagnostics)
105+
validateBlocks(name, block.GetNestedObject().GetBlocks(), diagnostics)
106+
}
107+
108+
func validateAttributes[T schema.Attribute](name string, attrs map[string]T, diagnostics *diag.Diagnostics) {
109+
for k, v := range attrs {
110+
validateAttribute(name+"."+k, v, diagnostics)
111+
}
112+
}
113+
114+
func validateBlocks[T schema.Block](name string, blocks map[string]T, diagnostics *diag.Diagnostics) {
115+
for k, v := range blocks {
116+
validateBlock(name+"."+k, v, diagnostics)
117+
}
118+
}
119+
120+
type descriptor interface {
121+
GetDescription() string
122+
GetMarkdownDescription() string
123+
}
124+
125+
func checkDescriptor(name string, d descriptor, diagnostics *diag.Diagnostics) {
126+
if d.GetDescription() != d.GetMarkdownDescription() {
127+
diagnostics.Append(diag.NewErrorDiagnostic(
128+
"Conflicting Attribute Description",
129+
fmt.Sprintf("Description and MarkdownDescription differ for %q.", name),
130+
))
131+
}
132+
}

internal/service/controlplaneipaddresses/data_source.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55

66
"github.com/hashicorp/terraform-plugin-framework/datasource"
7+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
78
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
89
)
910

@@ -26,6 +27,7 @@ type controlPlaneIPAddressesDS struct {
2627

2728
func (d *controlPlaneIPAddressesDS) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
2829
resp.Schema = DataSourceSchema(ctx)
30+
conversion.UpdateSchemaDescription(&resp.Schema)
2931
}
3032

3133
func (d *controlPlaneIPAddressesDS) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {

0 commit comments

Comments
 (0)