@@ -2,13 +2,19 @@ package provider
2
2
3
3
import (
4
4
"context"
5
+ "fmt"
6
+ "reflect"
5
7
"time"
6
8
7
9
"github.com/hashicorp/terraform-plugin-framework/attr"
8
10
"github.com/hashicorp/terraform-plugin-framework/diag"
11
+ "github.com/hashicorp/terraform-plugin-framework/path"
9
12
"github.com/hashicorp/terraform-plugin-framework/types"
13
+ "github.com/iancoleman/strcase"
10
14
"github.com/pomerium/enterprise-client-go/pb"
11
15
"google.golang.org/protobuf/types/known/durationpb"
16
+
17
+ "google.golang.org/protobuf/types/known/structpb"
12
18
)
13
19
14
20
func FromStringSlice (slice []string ) types.List {
@@ -104,3 +110,114 @@ func FromDuration(d *durationpb.Duration) types.String {
104
110
}
105
111
return types .StringValue (d .AsDuration ().String ())
106
112
}
113
+
114
+ // GoStructToPB converts a Go struct to a protobuf Struct.
115
+ // It only supports protobuf types.String field types
116
+ // Field names are converted to snake_case.
117
+ func GoStructToPB (input interface {}) (* structpb.Struct , error ) {
118
+ if input == nil {
119
+ return nil , nil
120
+ }
121
+
122
+ val := reflect .ValueOf (input )
123
+ if val .Kind () != reflect .Struct {
124
+ return nil , fmt .Errorf ("input must be a struct, got %v" , val .Kind ())
125
+ }
126
+
127
+ fields := make (map [string ]* structpb.Value )
128
+ typ := val .Type ()
129
+
130
+ typeString := reflect .TypeOf (types.String {})
131
+ for i := 0 ; i < typ .NumField (); i ++ {
132
+ field := typ .Field (i )
133
+ fieldValue := val .Field (i )
134
+ fieldName := strcase .ToSnake (field .Name )
135
+
136
+ if fieldValue .Type () != typeString {
137
+ return nil , fmt .Errorf ("unsupported field type %s for field %s" , fieldValue .Type (), fieldName )
138
+ }
139
+ protoValue , ok := fieldValue .Interface ().(types.String )
140
+ if ! ok {
141
+ return nil , fmt .Errorf ("unexpected type assertion for field %s" , fieldName )
142
+ }
143
+ if ! protoValue .IsNull () {
144
+ fields [fieldName ] = structpb .NewStringValue (protoValue .ValueString ())
145
+ }
146
+ }
147
+
148
+ return & structpb.Struct {Fields : fields }, nil
149
+ }
150
+
151
+ // PBStructToTF converts a protobuf Struct to a types.Object,
152
+ // by enumerating the `tfsdk` tags on the struct fields.
153
+ // only supports string fields
154
+ func PBStructToTF [T any ](
155
+ dst * types.Object ,
156
+ src * structpb.Struct ,
157
+ diags * diag.Diagnostics ,
158
+ ) {
159
+ attrTypes , err := GetTFObjectTypes [T ]()
160
+ if err != nil {
161
+ diags .AddError ("failed to get object types" , err .Error ())
162
+ return
163
+ }
164
+
165
+ if src == nil {
166
+ * dst = types .ObjectNull (attrTypes )
167
+ return
168
+ }
169
+
170
+ attrs := make (map [string ]attr.Value )
171
+ for k , v := range src .Fields {
172
+ _ , ok := attrTypes [k ]
173
+ if ! ok {
174
+ diags .AddAttributeWarning (
175
+ path .Root (k ),
176
+ "unexpected field" ,
177
+ fmt .Sprintf ("unexpected field %s" , k ),
178
+ )
179
+ continue
180
+ }
181
+ str , ok := v .GetKind ().(* structpb.Value_StringValue )
182
+ if ! ok {
183
+ diags .AddAttributeError (
184
+ path .Root (k ),
185
+ "unsupported field type" ,
186
+ fmt .Sprintf ("%T for field %s" , v , k ))
187
+ return
188
+ }
189
+ attrs [k ] = types .StringValue (str .StringValue )
190
+ }
191
+
192
+ for k := range attrTypes {
193
+ if _ , ok := src .Fields [k ]; ok {
194
+ continue
195
+ }
196
+ attrs [k ] = types .StringNull ()
197
+ }
198
+
199
+ v , d := types .ObjectValue (attrTypes , attrs )
200
+ diags .Append (d ... )
201
+ if ! diags .HasError () {
202
+ * dst = v
203
+ }
204
+ }
205
+
206
+ func GetTFObjectTypes [T any ]() (map [string ]attr.Type , error ) {
207
+ tm := make (map [string ]attr.Type )
208
+ var v T
209
+ typ := reflect .TypeOf (v )
210
+ typeString := reflect .TypeOf (types.String {})
211
+ for i := 0 ; i < typ .NumField (); i ++ {
212
+ field := typ .Field (i )
213
+ if field .Type != typeString {
214
+ return nil , fmt .Errorf ("unsupported field type %s for field %s" , field .Type , field .Name )
215
+ }
216
+ tfsdkTag := field .Tag .Get ("tfsdk" )
217
+ if tfsdkTag == "" {
218
+ return nil , fmt .Errorf ("missing tfsdk tag for field %s" , field .Name )
219
+ }
220
+ tm [tfsdkTag ] = types .StringType
221
+ }
222
+ return tm , nil
223
+ }
0 commit comments