json: Implement Body.Content

This is a wrapper around Body.PartialContent that generates additional
error diagnostics if any object properties are left over after decoding,
helping the config author to catch typos that would otherwise have caused
a property to be silently ignored.
This commit is contained in:
Martin Atkins 2017-05-20 09:50:48 -07:00
parent 17d372677d
commit 45f97bf427
2 changed files with 88 additions and 4 deletions

View File

@ -25,9 +25,40 @@ type expression struct {
}
func (b *body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
content, _, diags := b.PartialContent(schema)
content, newBody, diags := b.PartialContent(schema)
// TODO: generate errors for the stuff we didn't use in PartialContent
hiddenAttrs := newBody.(*body).hiddenAttrs
var nameSuggestions []string
for _, attrS := range schema.Attributes {
if _, ok := hiddenAttrs[attrS.Name]; !ok {
// Only suggest an attribute name if we didn't use it already.
nameSuggestions = append(nameSuggestions, attrS.Name)
}
}
for _, blockS := range schema.Blocks {
// Blocks can appear multiple times, so we'll suggest their type
// names regardless of whether they've already been used.
nameSuggestions = append(nameSuggestions, blockS.Type)
}
for k, attr := range b.obj.Attrs {
if _, ok := hiddenAttrs[k]; !ok {
var fixItHint string
suggestion := nameSuggestion(k, nameSuggestions)
if suggestion != "" {
fixItHint = fmt.Sprintf(" Did you mean %q?", suggestion)
}
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Extraneous JSON object property",
Detail: fmt.Sprintf("No attribute or block type is named %q.%s", k, fixItHint),
Subject: &attr.NameRange,
Context: attr.Range().Ptr(),
})
}
}
return content, diags
}
@ -61,7 +92,6 @@ func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Bod
Subject: &obj.OpenRange,
})
}
usedNames[attrS.Name] = struct{}{}
continue
}
content.Attributes[attrS.Name] = &zcl.Attribute{

View File

@ -527,7 +527,7 @@ func TestBodyPartialContent(t *testing.T) {
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.Errorf("Parse produced diagnostics: %s", diags)
t.Fatalf("Parse produced diagnostics: %s", diags)
}
got, _, diags := file.Body.PartialContent(test.schema)
if len(diags) != test.diagCount {
@ -543,3 +543,57 @@ func TestBodyPartialContent(t *testing.T) {
})
}
}
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 *zcl.BodySchema
diagCount int
}{
{
`{"unknown": true}`,
&zcl.BodySchema{},
1,
},
{
`{"unknow": true}`,
&zcl.BodySchema{
Attributes: []zcl.AttributeSchema{
{
Name: "unknown",
},
},
},
1,
},
{
`{"unknow": true, "unnown": true}`,
&zcl.BodySchema{
Attributes: []zcl.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)
}
}
})
}
}