@@ -123,6 +123,13 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
123
123
fieldV .Set (reflect .ValueOf (attr ))
124
124
case exprType .AssignableTo (field .Type ):
125
125
fieldV .Set (reflect .ValueOf (attr .Expr ))
126
+ case field .Type .Kind () == reflect .Ptr && field .Type .Elem ().Kind () == reflect .Struct :
127
+ // TODO might want to check for nil here
128
+ rn := reflect .New (field .Type .Elem ())
129
+ fieldV .Set (rn )
130
+ diags = append (diags , DecodeExpression (
131
+ attr .Expr , ctx , fieldV .Interface (),
132
+ )... )
126
133
default :
127
134
diags = append (diags , DecodeExpression (
128
135
attr .Expr , ctx , fieldV .Addr ().Interface (),
@@ -276,7 +283,9 @@ func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value)
276
283
277
284
// DecodeExpression extracts the value of the given expression into the given
278
285
// value. This value must be something that gocty is able to decode into,
279
- // since the final decoding is delegated to that package.
286
+ // since the final decoding is delegated to that package. If a reference to
287
+ // a struct is provided which contains gohcl tags, it will be decoded using
288
+ // the attr and optional tags.
280
289
//
281
290
// The given EvalContext is used to resolve any variables or functions in
282
291
// expressions encountered while decoding. This may be nil to require only
@@ -290,20 +299,59 @@ func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value)
290
299
// integration use-cases.
291
300
func DecodeExpression (expr hcl.Expression , ctx * hcl.EvalContext , val interface {}) hcl.Diagnostics {
292
301
srcVal , diags := expr .Value (ctx )
302
+ if diags .HasErrors () {
303
+ return diags
304
+ }
305
+
306
+ return append (diags , DecodeValue (srcVal , expr .StartRange (), expr .Range (), val )... )
307
+ }
308
+
309
+ // DecodeValue extracts the given value into the provided target.
310
+ // This value must be something that gocty is able to decode into,
311
+ // since the final decoding is delegated to that package. If a reference to
312
+ // a struct is provided which contains gohcl tags, it will be decoded using
313
+ // the attr and optional tags.
314
+ //
315
+ // The returned diagnostics should be inspected with its HasErrors method to
316
+ // determine if the populated value is valid and complete. If error diagnostics
317
+ // are returned then the given value may have been partially-populated but
318
+ // may still be accessed by a careful caller for static analysis and editor
319
+ // integration use-cases.
320
+ func DecodeValue (srcVal cty.Value , subject hcl.Range , context hcl.Range , val interface {}) hcl.Diagnostics {
321
+ rv := reflect .ValueOf (val )
322
+ if rv .Type ().Kind () == reflect .Ptr && rv .Type ().Elem ().Kind () == reflect .Struct && hasFieldTags (rv .Elem ().Type ()) {
323
+ attrs := make (hcl.Attributes )
324
+ for k , v := range srcVal .AsValueMap () {
325
+ attrs [k ] = & hcl.Attribute {
326
+ Name : k ,
327
+ Expr : hcl .StaticExpr (v , context ),
328
+ Range : subject ,
329
+ }
330
+
331
+ }
332
+ return decodeBodyToStruct (synthBody {
333
+ attrs : attrs ,
334
+ subject : subject ,
335
+ context : context ,
336
+ }, nil , rv .Elem ())
337
+
338
+ }
293
339
294
340
convTy , err := gocty .ImpliedType (val )
295
341
if err != nil {
296
342
panic (fmt .Sprintf ("unsuitable DecodeExpression target: %s" , err ))
297
343
}
298
344
345
+ var diags hcl.Diagnostics
346
+
299
347
srcVal , err = convert .Convert (srcVal , convTy )
300
348
if err != nil {
301
349
diags = append (diags , & hcl.Diagnostic {
302
350
Severity : hcl .DiagError ,
303
351
Summary : "Unsuitable value type" ,
304
352
Detail : fmt .Sprintf ("Unsuitable value: %s" , err .Error ()),
305
- Subject : expr . StartRange () .Ptr (),
306
- Context : expr . Range () .Ptr (),
353
+ Subject : subject .Ptr (),
354
+ Context : context .Ptr (),
307
355
})
308
356
return diags
309
357
}
@@ -314,10 +362,80 @@ func DecodeExpression(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}
314
362
Severity : hcl .DiagError ,
315
363
Summary : "Unsuitable value type" ,
316
364
Detail : fmt .Sprintf ("Unsuitable value: %s" , err .Error ()),
317
- Subject : expr . StartRange () .Ptr (),
318
- Context : expr . Range () .Ptr (),
365
+ Subject : subject .Ptr (),
366
+ Context : context .Ptr (),
319
367
})
320
368
}
321
369
322
370
return diags
323
371
}
372
+
373
+ type synthBody struct {
374
+ attrs hcl.Attributes
375
+ subject hcl.Range
376
+ context hcl.Range
377
+ }
378
+
379
+ func (s synthBody ) Content (schema * hcl.BodySchema ) (* hcl.BodyContent , hcl.Diagnostics ) {
380
+ body , partial , diags := s .PartialContent (schema )
381
+
382
+ attrs , _ := partial .JustAttributes ()
383
+ for name := range attrs {
384
+ diags = append (diags , & hcl.Diagnostic {
385
+ Severity : hcl .DiagError ,
386
+ Summary : "Unsupported argument" ,
387
+ Detail : fmt .Sprintf ("An argument named %q is not expected here." , name ),
388
+ Subject : s .subject .Ptr (),
389
+ Context : s .context .Ptr (),
390
+ })
391
+ }
392
+
393
+ return body , diags
394
+ }
395
+
396
+ func (s synthBody ) PartialContent (schema * hcl.BodySchema ) (* hcl.BodyContent , hcl.Body , hcl.Diagnostics ) {
397
+ var diags hcl.Diagnostics
398
+
399
+ for _ , block := range schema .Blocks {
400
+ panic ("hcl block tags are not allowed in attribute structs: " + block .Type )
401
+ }
402
+
403
+ attrs := make (hcl.Attributes )
404
+ remainder := make (hcl.Attributes )
405
+
406
+ for _ , attr := range schema .Attributes {
407
+ v , ok := s .attrs [attr .Name ]
408
+ if ! ok {
409
+ if attr .Required {
410
+ diags = append (diags , & hcl.Diagnostic {
411
+ Severity : hcl .DiagError ,
412
+ Summary : "Missing required argument" ,
413
+ Detail : fmt .Sprintf ("The argument %q is required, but no definition was found." , attr .Name ),
414
+ Subject : s .subject .Ptr (),
415
+ Context : s .context .Ptr (),
416
+ })
417
+ }
418
+ continue
419
+ }
420
+
421
+ attrs [attr .Name ] = v
422
+ }
423
+
424
+ for k , v := range s .attrs {
425
+ if _ , ok := attrs [k ]; ! ok {
426
+ remainder [k ] = v
427
+ }
428
+ }
429
+
430
+ return & hcl.BodyContent {
431
+ Attributes : attrs ,
432
+ MissingItemRange : s .context ,
433
+ }, synthBody {attrs : remainder }, diags
434
+ }
435
+
436
+ func (s synthBody ) JustAttributes () (hcl.Attributes , hcl.Diagnostics ) {
437
+ return s .attrs , nil
438
+ }
439
+ func (s synthBody ) MissingItemRange () hcl.Range {
440
+ return s .context
441
+ }
0 commit comments