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:
parent
4bbfa6d6ab
commit
92e407e672
@ -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]
|
||||
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user