1package json 2 3import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl2/hcl" 7 "github.com/hashicorp/hcl2/hcl/hclsyntax" 8 "github.com/zclconf/go-cty/cty" 9 "github.com/zclconf/go-cty/cty/convert" 10) 11 12// body is the implementation of "Body" used for files processed with the JSON 13// parser. 14type body struct { 15 val node 16 17 // If non-nil, the keys of this map cause the corresponding attributes to 18 // be treated as non-existing. This is used when Body.PartialContent is 19 // called, to produce the "remaining content" Body. 20 hiddenAttrs map[string]struct{} 21} 22 23// expression is the implementation of "Expression" used for files processed 24// with the JSON parser. 25type expression struct { 26 src node 27} 28 29func (b *body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { 30 content, newBody, diags := b.PartialContent(schema) 31 32 hiddenAttrs := newBody.(*body).hiddenAttrs 33 34 var nameSuggestions []string 35 for _, attrS := range schema.Attributes { 36 if _, ok := hiddenAttrs[attrS.Name]; !ok { 37 // Only suggest an attribute name if we didn't use it already. 38 nameSuggestions = append(nameSuggestions, attrS.Name) 39 } 40 } 41 for _, blockS := range schema.Blocks { 42 // Blocks can appear multiple times, so we'll suggest their type 43 // names regardless of whether they've already been used. 44 nameSuggestions = append(nameSuggestions, blockS.Type) 45 } 46 47 jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil) 48 diags = append(diags, attrDiags...) 49 50 for _, attr := range jsonAttrs { 51 k := attr.Name 52 if k == "//" { 53 // Ignore "//" keys in objects representing bodies, to allow 54 // their use as comments. 55 continue 56 } 57 58 if _, ok := hiddenAttrs[k]; !ok { 59 suggestion := nameSuggestion(k, nameSuggestions) 60 if suggestion != "" { 61 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 62 } 63 64 diags = append(diags, &hcl.Diagnostic{ 65 Severity: hcl.DiagError, 66 Summary: "Extraneous JSON object property", 67 Detail: fmt.Sprintf("No argument or block type is named %q.%s", k, suggestion), 68 Subject: &attr.NameRange, 69 Context: attr.Range().Ptr(), 70 }) 71 } 72 } 73 74 return content, diags 75} 76 77func (b *body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { 78 var diags hcl.Diagnostics 79 80 jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil) 81 diags = append(diags, attrDiags...) 82 83 usedNames := map[string]struct{}{} 84 if b.hiddenAttrs != nil { 85 for k := range b.hiddenAttrs { 86 usedNames[k] = struct{}{} 87 } 88 } 89 90 content := &hcl.BodyContent{ 91 Attributes: map[string]*hcl.Attribute{}, 92 Blocks: nil, 93 94 MissingItemRange: b.MissingItemRange(), 95 } 96 97 // Create some more convenient data structures for our work below. 98 attrSchemas := map[string]hcl.AttributeSchema{} 99 blockSchemas := map[string]hcl.BlockHeaderSchema{} 100 for _, attrS := range schema.Attributes { 101 attrSchemas[attrS.Name] = attrS 102 } 103 for _, blockS := range schema.Blocks { 104 blockSchemas[blockS.Type] = blockS 105 } 106 107 for _, jsonAttr := range jsonAttrs { 108 attrName := jsonAttr.Name 109 if _, used := b.hiddenAttrs[attrName]; used { 110 continue 111 } 112 113 if attrS, defined := attrSchemas[attrName]; defined { 114 if existing, exists := content.Attributes[attrName]; exists { 115 diags = append(diags, &hcl.Diagnostic{ 116 Severity: hcl.DiagError, 117 Summary: "Duplicate argument", 118 Detail: fmt.Sprintf("The argument %q was already set at %s.", attrName, existing.Range), 119 Subject: &jsonAttr.NameRange, 120 Context: jsonAttr.Range().Ptr(), 121 }) 122 continue 123 } 124 125 content.Attributes[attrS.Name] = &hcl.Attribute{ 126 Name: attrS.Name, 127 Expr: &expression{src: jsonAttr.Value}, 128 Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()), 129 NameRange: jsonAttr.NameRange, 130 } 131 usedNames[attrName] = struct{}{} 132 133 } else if blockS, defined := blockSchemas[attrName]; defined { 134 bv := jsonAttr.Value 135 blockDiags := b.unpackBlock(bv, blockS.Type, &jsonAttr.NameRange, blockS.LabelNames, nil, nil, &content.Blocks) 136 diags = append(diags, blockDiags...) 137 usedNames[attrName] = struct{}{} 138 } 139 140 // We ignore anything that isn't defined because that's the 141 // PartialContent contract. The Content method will catch leftovers. 142 } 143 144 // Make sure we got all the required attributes. 145 for _, attrS := range schema.Attributes { 146 if !attrS.Required { 147 continue 148 } 149 if _, defined := content.Attributes[attrS.Name]; !defined { 150 diags = append(diags, &hcl.Diagnostic{ 151 Severity: hcl.DiagError, 152 Summary: "Missing required argument", 153 Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name), 154 Subject: b.MissingItemRange().Ptr(), 155 }) 156 } 157 } 158 159 unusedBody := &body{ 160 val: b.val, 161 hiddenAttrs: usedNames, 162 } 163 164 return content, unusedBody, diags 165} 166 167// JustAttributes for JSON bodies interprets all properties of the wrapped 168// JSON object as attributes and returns them. 169func (b *body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { 170 var diags hcl.Diagnostics 171 attrs := make(map[string]*hcl.Attribute) 172 173 obj, ok := b.val.(*objectVal) 174 if !ok { 175 diags = append(diags, &hcl.Diagnostic{ 176 Severity: hcl.DiagError, 177 Summary: "Incorrect JSON value type", 178 Detail: "A JSON object is required here, setting the arguments for this block.", 179 Subject: b.val.StartRange().Ptr(), 180 }) 181 return attrs, diags 182 } 183 184 for _, jsonAttr := range obj.Attrs { 185 name := jsonAttr.Name 186 if name == "//" { 187 // Ignore "//" keys in objects representing bodies, to allow 188 // their use as comments. 189 continue 190 } 191 192 if _, hidden := b.hiddenAttrs[name]; hidden { 193 continue 194 } 195 196 if existing, exists := attrs[name]; exists { 197 diags = append(diags, &hcl.Diagnostic{ 198 Severity: hcl.DiagError, 199 Summary: "Duplicate attribute definition", 200 Detail: fmt.Sprintf("The argument %q was already set at %s.", name, existing.Range), 201 Subject: &jsonAttr.NameRange, 202 }) 203 continue 204 } 205 206 attrs[name] = &hcl.Attribute{ 207 Name: name, 208 Expr: &expression{src: jsonAttr.Value}, 209 Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()), 210 NameRange: jsonAttr.NameRange, 211 } 212 } 213 214 // No diagnostics possible here, since the parser already took care of 215 // finding duplicates and every JSON value can be a valid attribute value. 216 return attrs, diags 217} 218 219func (b *body) MissingItemRange() hcl.Range { 220 switch tv := b.val.(type) { 221 case *objectVal: 222 return tv.CloseRange 223 case *arrayVal: 224 return tv.OpenRange 225 default: 226 // Should not happen in correct operation, but might show up if the 227 // input is invalid and we are producing partial results. 228 return tv.StartRange() 229 } 230} 231 232func (b *body) unpackBlock(v node, typeName string, typeRange *hcl.Range, labelsLeft []string, labelsUsed []string, labelRanges []hcl.Range, blocks *hcl.Blocks) (diags hcl.Diagnostics) { 233 if len(labelsLeft) > 0 { 234 labelName := labelsLeft[0] 235 jsonAttrs, attrDiags := b.collectDeepAttrs(v, &labelName) 236 diags = append(diags, attrDiags...) 237 238 if len(jsonAttrs) == 0 { 239 diags = diags.Append(&hcl.Diagnostic{ 240 Severity: hcl.DiagError, 241 Summary: "Missing block label", 242 Detail: fmt.Sprintf("At least one object property is required, whose name represents the %s block's %s.", typeName, labelName), 243 Subject: v.StartRange().Ptr(), 244 }) 245 return 246 } 247 labelsUsed := append(labelsUsed, "") 248 labelRanges := append(labelRanges, hcl.Range{}) 249 for _, p := range jsonAttrs { 250 pk := p.Name 251 labelsUsed[len(labelsUsed)-1] = pk 252 labelRanges[len(labelRanges)-1] = p.NameRange 253 diags = append(diags, b.unpackBlock(p.Value, typeName, typeRange, labelsLeft[1:], labelsUsed, labelRanges, blocks)...) 254 } 255 return 256 } 257 258 // By the time we get here, we've peeled off all the labels and we're ready 259 // to deal with the block's actual content. 260 261 // need to copy the label slices because their underlying arrays will 262 // continue to be mutated after we return. 263 labels := make([]string, len(labelsUsed)) 264 copy(labels, labelsUsed) 265 labelR := make([]hcl.Range, len(labelRanges)) 266 copy(labelR, labelRanges) 267 268 switch tv := v.(type) { 269 case *nullVal: 270 // There is no block content, e.g the value is null. 271 return 272 case *objectVal: 273 // Single instance of the block 274 *blocks = append(*blocks, &hcl.Block{ 275 Type: typeName, 276 Labels: labels, 277 Body: &body{ 278 val: tv, 279 }, 280 281 DefRange: tv.OpenRange, 282 TypeRange: *typeRange, 283 LabelRanges: labelR, 284 }) 285 case *arrayVal: 286 // Multiple instances of the block 287 for _, av := range tv.Values { 288 *blocks = append(*blocks, &hcl.Block{ 289 Type: typeName, 290 Labels: labels, 291 Body: &body{ 292 val: av, // might be mistyped; we'll find out when content is requested for this body 293 }, 294 295 DefRange: tv.OpenRange, 296 TypeRange: *typeRange, 297 LabelRanges: labelR, 298 }) 299 } 300 default: 301 diags = diags.Append(&hcl.Diagnostic{ 302 Severity: hcl.DiagError, 303 Summary: "Incorrect JSON value type", 304 Detail: fmt.Sprintf("Either a JSON object or a JSON array is required, representing the contents of one or more %q blocks.", typeName), 305 Subject: v.StartRange().Ptr(), 306 }) 307 } 308 return 309} 310 311// collectDeepAttrs takes either a single object or an array of objects and 312// flattens it into a list of object attributes, collecting attributes from 313// all of the objects in a given array. 314// 315// Ordering is preserved, so a list of objects that each have one property 316// will result in those properties being returned in the same order as the 317// objects appeared in the array. 318// 319// This is appropriate for use only for objects representing bodies or labels 320// within a block. 321// 322// The labelName argument, if non-null, is used to tailor returned error 323// messages to refer to block labels rather than attributes and child blocks. 324// It has no other effect. 325func (b *body) collectDeepAttrs(v node, labelName *string) ([]*objectAttr, hcl.Diagnostics) { 326 var diags hcl.Diagnostics 327 var attrs []*objectAttr 328 329 switch tv := v.(type) { 330 case *nullVal: 331 // If a value is null, then we don't return any attributes or return an error. 332 333 case *objectVal: 334 attrs = append(attrs, tv.Attrs...) 335 336 case *arrayVal: 337 for _, ev := range tv.Values { 338 switch tev := ev.(type) { 339 case *objectVal: 340 attrs = append(attrs, tev.Attrs...) 341 default: 342 if labelName != nil { 343 diags = append(diags, &hcl.Diagnostic{ 344 Severity: hcl.DiagError, 345 Summary: "Incorrect JSON value type", 346 Detail: fmt.Sprintf("A JSON object is required here, to specify %s labels for this block.", *labelName), 347 Subject: ev.StartRange().Ptr(), 348 }) 349 } else { 350 diags = append(diags, &hcl.Diagnostic{ 351 Severity: hcl.DiagError, 352 Summary: "Incorrect JSON value type", 353 Detail: "A JSON object is required here, to define arguments and child blocks.", 354 Subject: ev.StartRange().Ptr(), 355 }) 356 } 357 } 358 } 359 360 default: 361 if labelName != nil { 362 diags = append(diags, &hcl.Diagnostic{ 363 Severity: hcl.DiagError, 364 Summary: "Incorrect JSON value type", 365 Detail: fmt.Sprintf("Either a JSON object or JSON array of objects is required here, to specify %s labels for this block.", *labelName), 366 Subject: v.StartRange().Ptr(), 367 }) 368 } else { 369 diags = append(diags, &hcl.Diagnostic{ 370 Severity: hcl.DiagError, 371 Summary: "Incorrect JSON value type", 372 Detail: "Either a JSON object or JSON array of objects is required here, to define arguments and child blocks.", 373 Subject: v.StartRange().Ptr(), 374 }) 375 } 376 } 377 378 return attrs, diags 379} 380 381func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 382 switch v := e.src.(type) { 383 case *stringVal: 384 if ctx != nil { 385 // Parse string contents as a HCL native language expression. 386 // We only do this if we have a context, so passing a nil context 387 // is how the caller specifies that interpolations are not allowed 388 // and that the string should just be returned verbatim. 389 templateSrc := v.Value 390 expr, diags := hclsyntax.ParseTemplate( 391 []byte(templateSrc), 392 v.SrcRange.Filename, 393 394 // This won't produce _exactly_ the right result, since 395 // the hclsyntax parser can't "see" any escapes we removed 396 // while parsing JSON, but it's better than nothing. 397 hcl.Pos{ 398 Line: v.SrcRange.Start.Line, 399 400 // skip over the opening quote mark 401 Byte: v.SrcRange.Start.Byte + 1, 402 Column: v.SrcRange.Start.Column + 1, 403 }, 404 ) 405 if diags.HasErrors() { 406 return cty.DynamicVal, diags 407 } 408 val, evalDiags := expr.Value(ctx) 409 diags = append(diags, evalDiags...) 410 return val, diags 411 } 412 413 return cty.StringVal(v.Value), nil 414 case *numberVal: 415 return cty.NumberVal(v.Value), nil 416 case *booleanVal: 417 return cty.BoolVal(v.Value), nil 418 case *arrayVal: 419 var diags hcl.Diagnostics 420 vals := []cty.Value{} 421 for _, jsonVal := range v.Values { 422 val, valDiags := (&expression{src: jsonVal}).Value(ctx) 423 vals = append(vals, val) 424 diags = append(diags, valDiags...) 425 } 426 return cty.TupleVal(vals), diags 427 case *objectVal: 428 var diags hcl.Diagnostics 429 attrs := map[string]cty.Value{} 430 attrRanges := map[string]hcl.Range{} 431 known := true 432 for _, jsonAttr := range v.Attrs { 433 // In this one context we allow keys to contain interpolation 434 // expressions too, assuming we're evaluating in interpolation 435 // mode. This achieves parity with the native syntax where 436 // object expressions can have dynamic keys, while block contents 437 // may not. 438 name, nameDiags := (&expression{src: &stringVal{ 439 Value: jsonAttr.Name, 440 SrcRange: jsonAttr.NameRange, 441 }}).Value(ctx) 442 valExpr := &expression{src: jsonAttr.Value} 443 val, valDiags := valExpr.Value(ctx) 444 diags = append(diags, nameDiags...) 445 diags = append(diags, valDiags...) 446 447 var err error 448 name, err = convert.Convert(name, cty.String) 449 if err != nil { 450 diags = append(diags, &hcl.Diagnostic{ 451 Severity: hcl.DiagError, 452 Summary: "Invalid object key expression", 453 Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err), 454 Subject: &jsonAttr.NameRange, 455 Expression: valExpr, 456 EvalContext: ctx, 457 }) 458 continue 459 } 460 if name.IsNull() { 461 diags = append(diags, &hcl.Diagnostic{ 462 Severity: hcl.DiagError, 463 Summary: "Invalid object key expression", 464 Detail: "Cannot use null value as an object key.", 465 Subject: &jsonAttr.NameRange, 466 Expression: valExpr, 467 EvalContext: ctx, 468 }) 469 continue 470 } 471 if !name.IsKnown() { 472 // This is a bit of a weird case, since our usual rules require 473 // us to tolerate unknowns and just represent the result as 474 // best we can but if we don't know the key then we can't 475 // know the type of our object at all, and thus we must turn 476 // the whole thing into cty.DynamicVal. This is consistent with 477 // how this situation is handled in the native syntax. 478 // We'll keep iterating so we can collect other errors in 479 // subsequent attributes. 480 known = false 481 continue 482 } 483 nameStr := name.AsString() 484 if _, defined := attrs[nameStr]; defined { 485 diags = append(diags, &hcl.Diagnostic{ 486 Severity: hcl.DiagError, 487 Summary: "Duplicate object attribute", 488 Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]), 489 Subject: &jsonAttr.NameRange, 490 Expression: e, 491 EvalContext: ctx, 492 }) 493 continue 494 } 495 attrs[nameStr] = val 496 attrRanges[nameStr] = jsonAttr.NameRange 497 } 498 if !known { 499 // We encountered an unknown key somewhere along the way, so 500 // we can't know what our type will eventually be. 501 return cty.DynamicVal, diags 502 } 503 return cty.ObjectVal(attrs), diags 504 case *nullVal: 505 return cty.NullVal(cty.DynamicPseudoType), nil 506 default: 507 // Default to DynamicVal so that ASTs containing invalid nodes can 508 // still be partially-evaluated. 509 return cty.DynamicVal, nil 510 } 511} 512 513func (e *expression) Variables() []hcl.Traversal { 514 var vars []hcl.Traversal 515 516 switch v := e.src.(type) { 517 case *stringVal: 518 templateSrc := v.Value 519 expr, diags := hclsyntax.ParseTemplate( 520 []byte(templateSrc), 521 v.SrcRange.Filename, 522 523 // This won't produce _exactly_ the right result, since 524 // the hclsyntax parser can't "see" any escapes we removed 525 // while parsing JSON, but it's better than nothing. 526 hcl.Pos{ 527 Line: v.SrcRange.Start.Line, 528 529 // skip over the opening quote mark 530 Byte: v.SrcRange.Start.Byte + 1, 531 Column: v.SrcRange.Start.Column + 1, 532 }, 533 ) 534 if diags.HasErrors() { 535 return vars 536 } 537 return expr.Variables() 538 539 case *arrayVal: 540 for _, jsonVal := range v.Values { 541 vars = append(vars, (&expression{src: jsonVal}).Variables()...) 542 } 543 case *objectVal: 544 for _, jsonAttr := range v.Attrs { 545 keyExpr := &stringVal{ // we're going to treat key as an expression in this context 546 Value: jsonAttr.Name, 547 SrcRange: jsonAttr.NameRange, 548 } 549 vars = append(vars, (&expression{src: keyExpr}).Variables()...) 550 vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...) 551 } 552 } 553 554 return vars 555} 556 557func (e *expression) Range() hcl.Range { 558 return e.src.Range() 559} 560 561func (e *expression) StartRange() hcl.Range { 562 return e.src.StartRange() 563} 564 565// Implementation for hcl.AbsTraversalForExpr. 566func (e *expression) AsTraversal() hcl.Traversal { 567 // In JSON-based syntax a traversal is given as a string containing 568 // traversal syntax as defined by hclsyntax.ParseTraversalAbs. 569 570 switch v := e.src.(type) { 571 case *stringVal: 572 traversal, diags := hclsyntax.ParseTraversalAbs([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start) 573 if diags.HasErrors() { 574 return nil 575 } 576 return traversal 577 default: 578 return nil 579 } 580} 581 582// Implementation for hcl.ExprCall. 583func (e *expression) ExprCall() *hcl.StaticCall { 584 // In JSON-based syntax a static call is given as a string containing 585 // an expression in the native syntax that also supports ExprCall. 586 587 switch v := e.src.(type) { 588 case *stringVal: 589 expr, diags := hclsyntax.ParseExpression([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start) 590 if diags.HasErrors() { 591 return nil 592 } 593 594 call, diags := hcl.ExprCall(expr) 595 if diags.HasErrors() { 596 return nil 597 } 598 599 return call 600 default: 601 return nil 602 } 603} 604 605// Implementation for hcl.ExprList. 606func (e *expression) ExprList() []hcl.Expression { 607 switch v := e.src.(type) { 608 case *arrayVal: 609 ret := make([]hcl.Expression, len(v.Values)) 610 for i, node := range v.Values { 611 ret[i] = &expression{src: node} 612 } 613 return ret 614 default: 615 return nil 616 } 617} 618 619// Implementation for hcl.ExprMap. 620func (e *expression) ExprMap() []hcl.KeyValuePair { 621 switch v := e.src.(type) { 622 case *objectVal: 623 ret := make([]hcl.KeyValuePair, len(v.Attrs)) 624 for i, jsonAttr := range v.Attrs { 625 ret[i] = hcl.KeyValuePair{ 626 Key: &expression{src: &stringVal{ 627 Value: jsonAttr.Name, 628 SrcRange: jsonAttr.NameRange, 629 }}, 630 Value: &expression{src: jsonAttr.Value}, 631 } 632 } 633 return ret 634 default: 635 return nil 636 } 637} 638