| package json |
| |
| import ( |
| "fmt" |
| "reflect" |
| "strings" |
| "testing" |
| |
| "github.com/davecgh/go-spew/spew" |
| "github.com/go-test/deep" |
| "github.com/hashicorp/hcl/v2" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| func TestBodyPartialContent(t *testing.T) { |
| tests := []struct { |
| src string |
| schema *hcl.BodySchema |
| want *hcl.BodyContent |
| diagCount int |
| }{ |
| { |
| `{}`, |
| &hcl.BodySchema{}, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, |
| End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `[]`, |
| &hcl.BodySchema{}, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, |
| End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `[{}]`, |
| &hcl.BodySchema{}, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, |
| End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `[[]]`, |
| &hcl.BodySchema{}, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, |
| End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, |
| }, |
| }, |
| 1, // elements of root array must be objects |
| }, |
| { |
| `{"//": "comment that should be ignored"}`, |
| &hcl.BodySchema{}, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 40, Byte: 39}, |
| End: hcl.Pos{Line: 1, Column: 41, Byte: 40}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"//": "comment that should be ignored", "//": "another comment"}`, |
| &hcl.BodySchema{}, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 65, Byte: 64}, |
| End: hcl.Pos{Line: 1, Column: 66, Byte: 65}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"name":"Ermintrude"}`, |
| &hcl.BodySchema{ |
| Attributes: []hcl.AttributeSchema{ |
| { |
| Name: "name", |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{ |
| "name": &hcl.Attribute{ |
| Name: "name", |
| Expr: &expression{ |
| src: &stringVal{ |
| Value: "Ermintrude", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 8, |
| Line: 1, |
| Column: 9, |
| }, |
| End: hcl.Pos{ |
| Byte: 20, |
| Line: 1, |
| Column: 21, |
| }, |
| }, |
| }, |
| }, |
| Range: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 20, |
| Line: 1, |
| Column: 21, |
| }, |
| }, |
| NameRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 7, |
| Line: 1, |
| Column: 8, |
| }, |
| }, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 21, Byte: 20}, |
| End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `[{"name":"Ermintrude"}]`, |
| &hcl.BodySchema{ |
| Attributes: []hcl.AttributeSchema{ |
| { |
| Name: "name", |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{ |
| "name": &hcl.Attribute{ |
| Name: "name", |
| Expr: &expression{ |
| src: &stringVal{ |
| Value: "Ermintrude", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 9, |
| Line: 1, |
| Column: 10, |
| }, |
| End: hcl.Pos{ |
| Byte: 21, |
| Line: 1, |
| Column: 22, |
| }, |
| }, |
| }, |
| }, |
| Range: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 2, |
| Line: 1, |
| Column: 3, |
| }, |
| End: hcl.Pos{ |
| Byte: 21, |
| Line: 1, |
| Column: 22, |
| }, |
| }, |
| NameRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 2, |
| Line: 1, |
| Column: 3, |
| }, |
| End: hcl.Pos{ |
| Byte: 8, |
| Line: 1, |
| Column: 9, |
| }, |
| }, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, |
| End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"name":"Ermintrude"}`, |
| &hcl.BodySchema{ |
| Attributes: []hcl.AttributeSchema{ |
| { |
| Name: "name", |
| Required: true, |
| }, |
| { |
| Name: "age", |
| Required: true, |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{ |
| "name": &hcl.Attribute{ |
| Name: "name", |
| Expr: &expression{ |
| src: &stringVal{ |
| Value: "Ermintrude", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 8, |
| Line: 1, |
| Column: 9, |
| }, |
| End: hcl.Pos{ |
| Byte: 20, |
| Line: 1, |
| Column: 21, |
| }, |
| }, |
| }, |
| }, |
| Range: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 20, |
| Line: 1, |
| Column: 21, |
| }, |
| }, |
| NameRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 7, |
| Line: 1, |
| Column: 8, |
| }, |
| }, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 21, Byte: 20}, |
| End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, |
| }, |
| }, |
| 1, |
| }, |
| { |
| `{"resource": null}`, |
| &hcl.BodySchema{ |
| Blocks: []hcl.BlockHeaderSchema{ |
| { |
| Type: "resource", |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| // We don't find any blocks if the value is json null. |
| Blocks: nil, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 18, Byte: 17}, |
| End: hcl.Pos{Line: 1, Column: 19, Byte: 18}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"resource": { "nested": null }}`, |
| &hcl.BodySchema{ |
| Blocks: []hcl.BlockHeaderSchema{ |
| { |
| Type: "resource", |
| LabelNames: []string{"name"}, |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| Blocks: nil, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 32, Byte: 31}, |
| End: hcl.Pos{Line: 1, Column: 33, Byte: 32}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"resource":{}}`, |
| &hcl.BodySchema{ |
| Blocks: []hcl.BlockHeaderSchema{ |
| { |
| Type: "resource", |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| Blocks: hcl.Blocks{ |
| { |
| Type: "resource", |
| Labels: []string{}, |
| Body: &body{ |
| val: &objectVal{ |
| Attrs: []*objectAttr{}, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 12, |
| Line: 1, |
| Column: 13, |
| }, |
| End: hcl.Pos{ |
| Byte: 14, |
| Line: 1, |
| Column: 15, |
| }, |
| }, |
| OpenRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 12, |
| Line: 1, |
| Column: 13, |
| }, |
| End: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| }, |
| CloseRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| End: hcl.Pos{ |
| Byte: 14, |
| Line: 1, |
| Column: 15, |
| }, |
| }, |
| }, |
| }, |
| |
| DefRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 12, |
| Line: 1, |
| Column: 13, |
| }, |
| End: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| }, |
| TypeRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 11, |
| Line: 1, |
| Column: 12, |
| }, |
| }, |
| LabelRanges: []hcl.Range{}, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, |
| End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"resource":[{},{}]}`, |
| &hcl.BodySchema{ |
| Blocks: []hcl.BlockHeaderSchema{ |
| { |
| Type: "resource", |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| Blocks: hcl.Blocks{ |
| { |
| Type: "resource", |
| Labels: []string{}, |
| Body: &body{ |
| val: &objectVal{ |
| Attrs: []*objectAttr{}, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| End: hcl.Pos{ |
| Byte: 15, |
| Line: 1, |
| Column: 16, |
| }, |
| }, |
| OpenRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| End: hcl.Pos{ |
| Byte: 14, |
| Line: 1, |
| Column: 15, |
| }, |
| }, |
| CloseRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 14, |
| Line: 1, |
| Column: 15, |
| }, |
| End: hcl.Pos{ |
| Byte: 15, |
| Line: 1, |
| Column: 16, |
| }, |
| }, |
| }, |
| }, |
| |
| DefRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 12, |
| Line: 1, |
| Column: 13, |
| }, |
| End: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| }, |
| TypeRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 11, |
| Line: 1, |
| Column: 12, |
| }, |
| }, |
| LabelRanges: []hcl.Range{}, |
| }, |
| { |
| Type: "resource", |
| Labels: []string{}, |
| Body: &body{ |
| val: &objectVal{ |
| Attrs: []*objectAttr{}, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 16, |
| Line: 1, |
| Column: 17, |
| }, |
| End: hcl.Pos{ |
| Byte: 18, |
| Line: 1, |
| Column: 19, |
| }, |
| }, |
| OpenRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 16, |
| Line: 1, |
| Column: 17, |
| }, |
| End: hcl.Pos{ |
| Byte: 17, |
| Line: 1, |
| Column: 18, |
| }, |
| }, |
| CloseRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 17, |
| Line: 1, |
| Column: 18, |
| }, |
| End: hcl.Pos{ |
| Byte: 18, |
| Line: 1, |
| Column: 19, |
| }, |
| }, |
| }, |
| }, |
| |
| DefRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 12, |
| Line: 1, |
| Column: 13, |
| }, |
| End: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| }, |
| TypeRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 11, |
| Line: 1, |
| Column: 12, |
| }, |
| }, |
| LabelRanges: []hcl.Range{}, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 20, Byte: 19}, |
| End: hcl.Pos{Line: 1, Column: 21, Byte: 20}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"resource":{"foo_instance":{"bar":{}}}}`, |
| &hcl.BodySchema{ |
| Blocks: []hcl.BlockHeaderSchema{ |
| { |
| Type: "resource", |
| LabelNames: []string{"type", "name"}, |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| Blocks: hcl.Blocks{ |
| { |
| Type: "resource", |
| Labels: []string{"foo_instance", "bar"}, |
| Body: &body{ |
| val: &objectVal{ |
| Attrs: []*objectAttr{}, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 35, |
| Line: 1, |
| Column: 36, |
| }, |
| End: hcl.Pos{ |
| Byte: 37, |
| Line: 1, |
| Column: 38, |
| }, |
| }, |
| OpenRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 35, |
| Line: 1, |
| Column: 36, |
| }, |
| End: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| }, |
| CloseRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| End: hcl.Pos{ |
| Byte: 37, |
| Line: 1, |
| Column: 38, |
| }, |
| }, |
| }, |
| }, |
| |
| DefRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 35, |
| Line: 1, |
| Column: 36, |
| }, |
| End: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| }, |
| TypeRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 11, |
| Line: 1, |
| Column: 12, |
| }, |
| }, |
| LabelRanges: []hcl.Range{ |
| { |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| End: hcl.Pos{ |
| Byte: 27, |
| Line: 1, |
| Column: 28, |
| }, |
| }, |
| { |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 29, |
| Line: 1, |
| Column: 30, |
| }, |
| End: hcl.Pos{ |
| Byte: 34, |
| Line: 1, |
| Column: 35, |
| }, |
| }, |
| }, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 40, Byte: 39}, |
| End: hcl.Pos{Line: 1, Column: 41, Byte: 40}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"resource":{"foo_instance":[{"bar":{}}, {"bar":{}}]}}`, |
| &hcl.BodySchema{ |
| Blocks: []hcl.BlockHeaderSchema{ |
| { |
| Type: "resource", |
| LabelNames: []string{"type", "name"}, |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| Blocks: hcl.Blocks{ |
| { |
| Type: "resource", |
| Labels: []string{"foo_instance", "bar"}, |
| Body: &body{ |
| val: &objectVal{ |
| Attrs: []*objectAttr{}, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| End: hcl.Pos{ |
| Byte: 38, |
| Line: 1, |
| Column: 39, |
| }, |
| }, |
| OpenRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| End: hcl.Pos{ |
| Byte: 37, |
| Line: 1, |
| Column: 38, |
| }, |
| }, |
| CloseRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 37, |
| Line: 1, |
| Column: 38, |
| }, |
| End: hcl.Pos{ |
| Byte: 38, |
| Line: 1, |
| Column: 39, |
| }, |
| }, |
| }, |
| }, |
| |
| DefRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| End: hcl.Pos{ |
| Byte: 37, |
| Line: 1, |
| Column: 38, |
| }, |
| }, |
| TypeRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 11, |
| Line: 1, |
| Column: 12, |
| }, |
| }, |
| LabelRanges: []hcl.Range{ |
| { |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| End: hcl.Pos{ |
| Byte: 27, |
| Line: 1, |
| Column: 28, |
| }, |
| }, |
| { |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 30, |
| Line: 1, |
| Column: 31, |
| }, |
| End: hcl.Pos{ |
| Byte: 35, |
| Line: 1, |
| Column: 36, |
| }, |
| }, |
| }, |
| }, |
| { |
| Type: "resource", |
| Labels: []string{"foo_instance", "bar"}, |
| Body: &body{ |
| val: &objectVal{ |
| Attrs: []*objectAttr{}, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| End: hcl.Pos{ |
| Byte: 38, |
| Line: 1, |
| Column: 39, |
| }, |
| }, |
| OpenRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 36, |
| Line: 1, |
| Column: 37, |
| }, |
| End: hcl.Pos{ |
| Byte: 37, |
| Line: 1, |
| Column: 38, |
| }, |
| }, |
| CloseRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 37, |
| Line: 1, |
| Column: 38, |
| }, |
| End: hcl.Pos{ |
| Byte: 38, |
| Line: 1, |
| Column: 39, |
| }, |
| }, |
| }, |
| }, |
| |
| DefRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 48, |
| Line: 1, |
| Column: 49, |
| }, |
| End: hcl.Pos{ |
| Byte: 49, |
| Line: 1, |
| Column: 50, |
| }, |
| }, |
| TypeRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 1, |
| Line: 1, |
| Column: 2, |
| }, |
| End: hcl.Pos{ |
| Byte: 11, |
| Line: 1, |
| Column: 12, |
| }, |
| }, |
| LabelRanges: []hcl.Range{ |
| { |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 13, |
| Line: 1, |
| Column: 14, |
| }, |
| End: hcl.Pos{ |
| Byte: 27, |
| Line: 1, |
| Column: 28, |
| }, |
| }, |
| { |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 42, |
| Line: 1, |
| Column: 43, |
| }, |
| End: hcl.Pos{ |
| Byte: 47, |
| Line: 1, |
| Column: 48, |
| }, |
| }, |
| }, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 54, Byte: 53}, |
| End: hcl.Pos{Line: 1, Column: 55, Byte: 54}, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"name":"Ermintrude"}`, |
| &hcl.BodySchema{ |
| Blocks: []hcl.BlockHeaderSchema{ |
| { |
| Type: "name", |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{}, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 21, Byte: 20}, |
| End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, |
| }, |
| }, |
| 1, // name is supposed to be a block |
| }, |
| { |
| `[{"name":"Ermintrude"},{"name":"Ermintrude"}]`, |
| &hcl.BodySchema{ |
| Attributes: []hcl.AttributeSchema{ |
| { |
| Name: "name", |
| }, |
| }, |
| }, |
| &hcl.BodyContent{ |
| Attributes: map[string]*hcl.Attribute{ |
| "name": { |
| Name: "name", |
| Expr: &expression{ |
| src: &stringVal{ |
| Value: "Ermintrude", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 8, |
| Line: 1, |
| Column: 9, |
| }, |
| End: hcl.Pos{ |
| Byte: 20, |
| Line: 1, |
| Column: 21, |
| }, |
| }, |
| }, |
| }, |
| Range: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 2, |
| Line: 1, |
| Column: 3, |
| }, |
| End: hcl.Pos{ |
| Byte: 21, |
| Line: 1, |
| Column: 22, |
| }, |
| }, |
| NameRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{ |
| Byte: 2, |
| Line: 1, |
| Column: 3, |
| }, |
| End: hcl.Pos{ |
| Byte: 8, |
| Line: 1, |
| Column: 9, |
| }, |
| }, |
| }, |
| }, |
| MissingItemRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, |
| End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, |
| }, |
| }, |
| 1, // "name" attribute is defined twice |
| }, |
| } |
| |
| for i, test := range tests { |
| t.Run(fmt.Sprintf("%02d-%s", i, test.src), func(t *testing.T) { |
| file, diags := Parse([]byte(test.src), "test.json") |
| if len(diags) != 0 { |
| t.Fatalf("Parse produced diagnostics: %s", diags) |
| } |
| got, _, diags := file.Body.PartialContent(test.schema) |
| if len(diags) != test.diagCount { |
| t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.diagCount) |
| for _, diag := range diags { |
| t.Logf(" - %s", diag) |
| } |
| } |
| |
| for _, problem := range deep.Equal(got, test.want) { |
| t.Error(problem) |
| } |
| }) |
| } |
| } |
| |
| func TestBodyContent(t *testing.T) { |
| // We test most of the functionality already in TestBodyPartialContent, so |
| // this test focuses on the handling of extraneous attributes. |
| tests := []struct { |
| src string |
| schema *hcl.BodySchema |
| diagCount int |
| }{ |
| { |
| `{"unknown": true}`, |
| &hcl.BodySchema{}, |
| 1, |
| }, |
| { |
| `{"//": "comment that should be ignored"}`, |
| &hcl.BodySchema{}, |
| 0, |
| }, |
| { |
| `{"unknow": true}`, |
| &hcl.BodySchema{ |
| Attributes: []hcl.AttributeSchema{ |
| { |
| Name: "unknown", |
| }, |
| }, |
| }, |
| 1, |
| }, |
| { |
| `{"unknow": true, "unnown": true}`, |
| &hcl.BodySchema{ |
| Attributes: []hcl.AttributeSchema{ |
| { |
| Name: "unknown", |
| }, |
| }, |
| }, |
| 2, |
| }, |
| } |
| |
| for i, test := range tests { |
| t.Run(fmt.Sprintf("%02d-%s", i, test.src), func(t *testing.T) { |
| file, diags := Parse([]byte(test.src), "test.json") |
| if len(diags) != 0 { |
| t.Fatalf("Parse produced diagnostics: %s", diags) |
| } |
| _, diags = file.Body.Content(test.schema) |
| if len(diags) != test.diagCount { |
| t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.diagCount) |
| for _, diag := range diags { |
| t.Logf(" - %s", diag) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestJustAttributes(t *testing.T) { |
| // We test most of the functionality already in TestBodyPartialContent, so |
| // this test focuses on the handling of extraneous attributes. |
| tests := []struct { |
| src string |
| want hcl.Attributes |
| diagCount int |
| }{ |
| { |
| `{}`, |
| map[string]*hcl.Attribute{}, |
| 0, |
| }, |
| { |
| `{"foo": true}`, |
| map[string]*hcl.Attribute{ |
| "foo": { |
| Name: "foo", |
| Expr: &expression{ |
| src: &booleanVal{ |
| Value: true, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Byte: 8, Line: 1, Column: 9}, |
| End: hcl.Pos{Byte: 12, Line: 1, Column: 13}, |
| }, |
| }, |
| }, |
| Range: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Byte: 1, Line: 1, Column: 2}, |
| End: hcl.Pos{Byte: 12, Line: 1, Column: 13}, |
| }, |
| NameRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Byte: 1, Line: 1, Column: 2}, |
| End: hcl.Pos{Byte: 6, Line: 1, Column: 7}, |
| }, |
| }, |
| }, |
| 0, |
| }, |
| { |
| `{"//": "comment that should be ignored"}`, |
| map[string]*hcl.Attribute{}, |
| 0, |
| }, |
| { |
| `{"foo": true, "foo": true}`, |
| map[string]*hcl.Attribute{ |
| "foo": { |
| Name: "foo", |
| Expr: &expression{ |
| src: &booleanVal{ |
| Value: true, |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Byte: 8, Line: 1, Column: 9}, |
| End: hcl.Pos{Byte: 12, Line: 1, Column: 13}, |
| }, |
| }, |
| }, |
| Range: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Byte: 1, Line: 1, Column: 2}, |
| End: hcl.Pos{Byte: 12, Line: 1, Column: 13}, |
| }, |
| NameRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Byte: 1, Line: 1, Column: 2}, |
| End: hcl.Pos{Byte: 6, Line: 1, Column: 7}, |
| }, |
| }, |
| }, |
| 1, // attribute foo was already defined |
| }, |
| } |
| |
| for i, test := range tests { |
| t.Run(fmt.Sprintf("%02d-%s", i, test.src), func(t *testing.T) { |
| file, diags := Parse([]byte(test.src), "test.json") |
| if len(diags) != 0 { |
| t.Fatalf("Parse produced diagnostics: %s", diags) |
| } |
| got, diags := file.Body.JustAttributes() |
| if len(diags) != test.diagCount { |
| t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.diagCount) |
| for _, diag := range diags { |
| t.Logf(" - %s", diag) |
| } |
| } |
| if !reflect.DeepEqual(got, test.want) { |
| t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.want)) |
| } |
| }) |
| } |
| } |
| |
| func TestExpressionVariables(t *testing.T) { |
| tests := []struct { |
| Src string |
| Want []hcl.Traversal |
| }{ |
| { |
| `{"a":true}`, |
| nil, |
| }, |
| { |
| `{"a":"${foo}"}`, |
| []hcl.Traversal{ |
| { |
| hcl.TraverseRoot{ |
| Name: "foo", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, |
| End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| `{"a":["${foo}"]}`, |
| []hcl.Traversal{ |
| { |
| hcl.TraverseRoot{ |
| Name: "foo", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 10, Byte: 9}, |
| End: hcl.Pos{Line: 1, Column: 13, Byte: 12}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| `{"a":{"b":"${foo}"}}`, |
| []hcl.Traversal{ |
| { |
| hcl.TraverseRoot{ |
| Name: "foo", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 14, Byte: 13}, |
| End: hcl.Pos{Line: 1, Column: 17, Byte: 16}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| `{"a":{"${foo}":"b"}}`, |
| []hcl.Traversal{ |
| { |
| hcl.TraverseRoot{ |
| Name: "foo", |
| SrcRange: hcl.Range{ |
| Filename: "test.json", |
| Start: hcl.Pos{Line: 1, Column: 10, Byte: 9}, |
| End: hcl.Pos{Line: 1, Column: 13, Byte: 12}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.Src, func(t *testing.T) { |
| file, diags := Parse([]byte(test.Src), "test.json") |
| if len(diags) != 0 { |
| t.Fatalf("Parse produced diagnostics: %s", diags) |
| } |
| attrs, diags := file.Body.JustAttributes() |
| if len(diags) != 0 { |
| t.Fatalf("JustAttributes produced diagnostics: %s", diags) |
| } |
| got := attrs["a"].Expr.Variables() |
| if !reflect.DeepEqual(got, test.Want) { |
| t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want)) |
| } |
| }) |
| } |
| } |
| |
| func TestExpressionAsTraversal(t *testing.T) { |
| e := &expression{ |
| src: &stringVal{ |
| Value: "foo.bar[0]", |
| }, |
| } |
| traversal := e.AsTraversal() |
| if len(traversal) != 3 { |
| t.Fatalf("incorrect traversal %#v; want length 3", traversal) |
| } |
| } |
| |
| func TestStaticExpressionList(t *testing.T) { |
| e := &expression{ |
| src: &arrayVal{ |
| Values: []node{ |
| &stringVal{ |
| Value: "hello", |
| }, |
| }, |
| }, |
| } |
| exprs := e.ExprList() |
| if len(exprs) != 1 { |
| t.Fatalf("incorrect exprs %#v; want length 1", exprs) |
| } |
| if exprs[0].(*expression).src != e.src.(*arrayVal).Values[0] { |
| t.Fatalf("wrong first expression node") |
| } |
| } |
| |
| func TestExpression_Value(t *testing.T) { |
| src := `{ |
| "string": "string_val", |
| "number": 5, |
| "bool_true": true, |
| "bool_false": false, |
| "array": ["a"], |
| "object": {"key": "value"}, |
| "null": null |
| }` |
| expected := map[string]cty.Value{ |
| "string": cty.StringVal("string_val"), |
| "number": cty.NumberIntVal(5), |
| "bool_true": cty.BoolVal(true), |
| "bool_false": cty.BoolVal(false), |
| "array": cty.TupleVal([]cty.Value{cty.StringVal("a")}), |
| "object": cty.ObjectVal(map[string]cty.Value{ |
| "key": cty.StringVal("value"), |
| }), |
| "null": cty.NullVal(cty.DynamicPseudoType), |
| } |
| |
| file, diags := Parse([]byte(src), "") |
| if len(diags) != 0 { |
| t.Errorf("got %d diagnostics on parse; want 0", len(diags)) |
| for _, diag := range diags { |
| t.Logf("- %s", diag.Error()) |
| } |
| } |
| if file == nil { |
| t.Errorf("got nil File; want actual file") |
| } |
| if file.Body == nil { |
| t.Fatalf("got nil Body; want actual body") |
| } |
| attrs, diags := file.Body.JustAttributes() |
| if len(diags) != 0 { |
| t.Errorf("got %d diagnostics on decode; want 0", len(diags)) |
| for _, diag := range diags { |
| t.Logf("- %s", diag.Error()) |
| } |
| } |
| |
| for ek, ev := range expected { |
| val, diags := attrs[ek].Expr.Value(&hcl.EvalContext{}) |
| if len(diags) != 0 { |
| t.Errorf("got %d diagnostics on eval; want 0", len(diags)) |
| for _, diag := range diags { |
| t.Logf("- %s", diag.Error()) |
| } |
| } |
| |
| if !val.RawEquals(ev) { |
| t.Errorf("wrong result %#v; want %#v", val, ev) |
| } |
| } |
| |
| } |
| |
| // TestExpressionValue_Diags asserts that Value() returns diagnostics |
| // from nested evaluations for complex objects (e.g. ObjectVal, ArrayVal) |
| func TestExpressionValue_Diags(t *testing.T) { |
| cases := []struct { |
| name string |
| src string |
| expected cty.Value |
| error string |
| }{ |
| { |
| name: "string: happy", |
| src: `{"v": "happy ${VAR1}"}`, |
| expected: cty.StringVal("happy case"), |
| }, |
| { |
| name: "string: unhappy", |
| src: `{"v": "happy ${UNKNOWN}"}`, |
| expected: cty.UnknownVal(cty.String), |
| error: "Unknown variable", |
| }, |
| { |
| name: "object_val: happy", |
| src: `{"v": {"key": "happy ${VAR1}"}}`, |
| expected: cty.ObjectVal(map[string]cty.Value{ |
| "key": cty.StringVal("happy case"), |
| }), |
| }, |
| { |
| name: "object_val: unhappy", |
| src: `{"v": {"key": "happy ${UNKNOWN}"}}`, |
| expected: cty.ObjectVal(map[string]cty.Value{ |
| "key": cty.UnknownVal(cty.String), |
| }), |
| error: "Unknown variable", |
| }, |
| { |
| name: "object_key: happy", |
| src: `{"v": {"happy ${VAR1}": "val"}}`, |
| expected: cty.ObjectVal(map[string]cty.Value{ |
| "happy case": cty.StringVal("val"), |
| }), |
| }, |
| { |
| name: "object_key: unhappy", |
| src: `{"v": {"happy ${UNKNOWN}": "val"}}`, |
| expected: cty.DynamicVal, |
| error: "Unknown variable", |
| }, |
| { |
| name: "array: happy", |
| src: `{"v": ["happy ${VAR1}"]}`, |
| expected: cty.TupleVal([]cty.Value{cty.StringVal("happy case")}), |
| }, |
| { |
| name: "array: unhappy", |
| src: `{"v": ["happy ${UNKNOWN}"]}`, |
| expected: cty.TupleVal([]cty.Value{cty.UnknownVal(cty.String)}), |
| error: "Unknown variable", |
| }, |
| } |
| |
| ctx := &hcl.EvalContext{ |
| Variables: map[string]cty.Value{ |
| "VAR1": cty.StringVal("case"), |
| }, |
| } |
| |
| for _, c := range cases { |
| t.Run(c.name, func(t *testing.T) { |
| file, diags := Parse([]byte(c.src), "") |
| |
| if len(diags) != 0 { |
| t.Errorf("got %d diagnostics on parse; want 0", len(diags)) |
| for _, diag := range diags { |
| t.Logf("- %s", diag.Error()) |
| } |
| t.FailNow() |
| } |
| if file == nil { |
| t.Errorf("got nil File; want actual file") |
| } |
| if file.Body == nil { |
| t.Fatalf("got nil Body; want actual body") |
| } |
| |
| attrs, diags := file.Body.JustAttributes() |
| if len(diags) != 0 { |
| t.Errorf("got %d diagnostics on decode; want 0", len(diags)) |
| for _, diag := range diags { |
| t.Logf("- %s", diag.Error()) |
| } |
| t.FailNow() |
| } |
| |
| val, diags := attrs["v"].Expr.Value(ctx) |
| if c.error == "" && len(diags) != 0 { |
| t.Errorf("got %d diagnostics on eval; want 0", len(diags)) |
| for _, diag := range diags { |
| t.Logf("- %s", diag.Error()) |
| } |
| t.FailNow() |
| } else if c.error != "" && len(diags) == 0 { |
| t.Fatalf("got 0 diagnostics on eval, want 1 with %s", c.error) |
| } else if c.error != "" && len(diags) != 0 { |
| if !strings.Contains(diags[0].Error(), c.error) { |
| t.Fatalf("found error: %s; want %s", diags[0].Error(), c.error) |
| } |
| } |
| |
| if !val.RawEquals(c.expected) { |
| t.Errorf("wrong result %#v; want %#v", val, c.expected) |
| } |
| }) |
| } |
| |
| } |