Body.JustAttributes method

The Content and PartialContent methods deal with the case where the caller
knows what structure is expected within the body, but sometimes the
structure of a body is just a free-form set of attributes that the caller
needs to enumerate.

The idea here is that the block in question must contain only attributes,
and no child blocks. For JSON this just entails interpreting every
property as an attribute. For native syntax later this will mean
producing an error diagnostic if any blocks appear within the body.
This commit is contained in:
Martin Atkins 2017-05-20 14:35:19 -07:00
parent 4bbfa6d6ab
commit 92e407e672
5 changed files with 160 additions and 1 deletions

View File

@ -123,6 +123,25 @@ func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Bod
return content, unusedBody, diags
}
// JustAttributes for JSON bodies interprets all properties of the wrapped
// JSON object as attributes and returns them.
func (b *body) JustAttributes() (map[string]*zcl.Attribute, zcl.Diagnostics) {
attrs := make(map[string]*zcl.Attribute)
for name, jsonAttr := range b.obj.Attrs {
attrs[name] = &zcl.Attribute{
Name: name,
Expr: &expression{src: jsonAttr.Value},
Range: zcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
NameRange: jsonAttr.NameRange,
ExprRange: jsonAttr.Value.Range(),
}
}
// No diagnostics possible here, since the parser already took care of
// finding duplicates and every JSON value can be a valid attribute value.
return attrs, nil
}
func (b *body) unpackBlock(v node, typeName string, typeRange *zcl.Range, labelsLeft []string, labelsUsed []string, labelRanges []zcl.Range, blocks *zcl.Blocks) (diags zcl.Diagnostics) {
if len(labelsLeft) > 0 {
labelName := labelsLeft[0]

View File

@ -597,3 +597,69 @@ func TestBodyContent(t *testing.T) {
})
}
}
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 map[string]*zcl.Attribute
}{
{
`{}`,
map[string]*zcl.Attribute{},
},
{
`{"foo": true}`,
map[string]*zcl.Attribute{
"foo": {
Name: "foo",
Expr: &expression{
src: &booleanVal{
Value: true,
SrcRange: zcl.Range{
Filename: "test.json",
Start: zcl.Pos{Byte: 8, Line: 1, Column: 9},
End: zcl.Pos{Byte: 12, Line: 1, Column: 13},
},
},
},
Range: zcl.Range{
Filename: "test.json",
Start: zcl.Pos{Byte: 1, Line: 1, Column: 2},
End: zcl.Pos{Byte: 12, Line: 1, Column: 13},
},
NameRange: zcl.Range{
Filename: "test.json",
Start: zcl.Pos{Byte: 1, Line: 1, Column: 2},
End: zcl.Pos{Byte: 6, Line: 1, Column: 7},
},
ExprRange: zcl.Range{
Filename: "test.json",
Start: zcl.Pos{Byte: 8, Line: 1, Column: 9},
End: zcl.Pos{Byte: 12, Line: 1, Column: 13},
},
},
},
},
}
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) != 0 {
t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), 0)
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))
}
})
}
}

View File

@ -93,6 +93,40 @@ func (mb mergedBodies) PartialContent(schema *BodySchema) (*BodyContent, Body, D
return mb.mergedContent(schema, true)
}
func (mb mergedBodies) JustAttributes() (map[string]*Attribute, Diagnostics) {
attrs := make(map[string]*Attribute)
var diags Diagnostics
for _, body := range mb {
thisAttrs, thisDiags := body.JustAttributes()
if len(thisDiags) != 0 {
diags = append(diags, thisDiags...)
}
if thisAttrs != nil {
for name, attr := range thisAttrs {
if existing := attrs[name]; existing != nil {
diags = diags.Append(&Diagnostic{
Severity: DiagError,
Summary: "Duplicate attribute",
Detail: fmt.Sprintf(
"Attribute %q was already assigned at %s",
name, existing.NameRange.String(),
),
Subject: &attr.NameRange,
})
continue
}
attrs[name] = attr
}
}
}
return attrs, diags
}
func (mb mergedBodies) mergedContent(schema *BodySchema, partial bool) (*BodyContent, Body, Diagnostics) {
// We need to produce a new schema with none of the attributes marked as
// required, since _any one_ of our bodies can contribute an attribute value.

View File

@ -8,7 +8,7 @@ import (
"github.com/davecgh/go-spew/spew"
)
func TestMergedBodies(t *testing.T) {
func TestMergedBodiesContent(t *testing.T) {
tests := []struct {
Bodies []Body
Schema *BodySchema
@ -411,3 +411,30 @@ func (v *testMergedBodiesVictim) PartialContent(schema *BodySchema) (*BodyConten
return content, emptyBody, diags
}
func (v *testMergedBodiesVictim) JustAttributes() (map[string]*Attribute, Diagnostics) {
attrs := make(map[string]*Attribute)
rng := Range{
Filename: v.Name,
}
for _, name := range v.HasAttributes {
attrs[name] = &Attribute{
Name: name,
NameRange: rng,
}
}
diags := make(Diagnostics, v.DiagCount)
for i := range diags {
diags[i] = &Diagnostic{
Severity: DiagError,
Summary: fmt.Sprintf("Fake diagnostic %d", i),
Detail: "For testing only.",
Context: &rng,
}
}
return attrs, diags
}

View File

@ -48,6 +48,19 @@ type Body interface {
// schema. If any are present, the returned Body is non-nil and contains
// the remaining items from the body that were not selected by the schema.
PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics)
// JustAttributes attempts to interpret all of the contents of the body
// as attributes, allowing for the contents to be accessed without a priori
// knowledge of the structure.
//
// The behavior of this method depends on the body's source language.
// Some languages, like JSON, can't distinguish between attributes and
// blocks without schema hints, but for languages that _can_ error
// diagnostics will be generated if any blocks are present in the body.
//
// Diagnostics may be produced for other reasons too, such as duplicate
// declarations of the same attribute.
JustAttributes() (map[string]*Attribute, Diagnostics)
}
// BodyContent is the result of applying a BodySchema to a Body.