json: start of low-level decoding
This commit is contained in:
parent
ca6c6b127c
commit
1168f36be5
@ -1,6 +1,9 @@
|
|||||||
package json
|
package json
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-cty/cty"
|
||||||
"github.com/apparentlymart/go-zcl/zcl"
|
"github.com/apparentlymart/go-zcl/zcl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,11 +25,157 @@ type expression struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
|
func (b *body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
|
||||||
// TODO: Implement
|
content, _, diags := b.PartialContent(schema)
|
||||||
return nil, nil
|
|
||||||
|
// TODO: generate errors for the stuff we didn't use in PartialContent
|
||||||
|
|
||||||
|
return content, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Body, zcl.Diagnostics) {
|
func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Body, zcl.Diagnostics) {
|
||||||
// TODO: Implement
|
|
||||||
return nil, nil, nil
|
obj := b.obj
|
||||||
|
jsonAttrs := obj.Attrs
|
||||||
|
usedNames := map[string]struct{}{}
|
||||||
|
if b.hiddenAttrs != nil {
|
||||||
|
for k := range b.hiddenAttrs {
|
||||||
|
usedNames[k] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var diags zcl.Diagnostics
|
||||||
|
|
||||||
|
content := &zcl.BodyContent{
|
||||||
|
Attributes: map[string]*zcl.Attribute{},
|
||||||
|
Blocks: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, attrS := range schema.Attributes {
|
||||||
|
jsonAttr, exists := jsonAttrs[attrS.Name]
|
||||||
|
_, used := usedNames[attrS.Name]
|
||||||
|
if used || !exists {
|
||||||
|
if attrS.Required {
|
||||||
|
diags = diags.Append(&zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Missing required attribute",
|
||||||
|
Detail: fmt.Sprintf("The attribute %q is required, so a JSON object property must be present with this name.", attrS.Name),
|
||||||
|
Subject: &obj.OpenRange,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
usedNames[attrS.Name] = struct{}{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content.Attributes[attrS.Name] = &zcl.Attribute{
|
||||||
|
Name: attrS.Name,
|
||||||
|
Expr: &expression{src: jsonAttr.Value},
|
||||||
|
Range: zcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
|
||||||
|
}
|
||||||
|
usedNames[attrS.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, blockS := range schema.Blocks {
|
||||||
|
jsonAttr, exists := jsonAttrs[blockS.Type]
|
||||||
|
_, used := usedNames[blockS.Type]
|
||||||
|
if used || !exists {
|
||||||
|
usedNames[blockS.Type] = struct{}{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v := jsonAttr.Value
|
||||||
|
diags = append(diags, b.unpackBlock(v, blockS.Type, &jsonAttr.NameRange, blockS.LabelNames, nil, nil, &content.Blocks)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
unusedBody := &body{
|
||||||
|
obj: b.obj,
|
||||||
|
hiddenAttrs: usedNames,
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, unusedBody, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
ov, ok := v.(*objectVal)
|
||||||
|
if !ok {
|
||||||
|
diags = diags.Append(&zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Incorrect JSON value type",
|
||||||
|
Detail: fmt.Sprintf("A JSON object is required, whose keys represent the %q block's %s.", typeName, labelName),
|
||||||
|
Subject: v.StartRange().Ptr(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labelsUsed := append(labelsUsed, "")
|
||||||
|
labelRanges := append(labelRanges, zcl.Range{})
|
||||||
|
for pk, p := range ov.Attrs {
|
||||||
|
labelsUsed[len(labelsUsed)-1] = pk
|
||||||
|
labelRanges[len(labelRanges)-1] = p.NameRange
|
||||||
|
diags = append(diags, b.unpackBlock(p.Value, typeName, typeRange, labelsLeft[1:], labelsUsed, labelRanges, blocks)...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// By the time we get here, we've peeled off all the labels and we're ready
|
||||||
|
// to deal with the block's actual content.
|
||||||
|
|
||||||
|
// need to copy the label slices because their underlying arrays will
|
||||||
|
// continue to be mutated after we return.
|
||||||
|
labels := make([]string, len(labelsUsed))
|
||||||
|
copy(labels, labelsUsed)
|
||||||
|
labelR := make([]zcl.Range, len(labelRanges))
|
||||||
|
copy(labelR, labelRanges)
|
||||||
|
|
||||||
|
switch tv := v.(type) {
|
||||||
|
case *objectVal:
|
||||||
|
// Single instance of the block
|
||||||
|
*blocks = append(*blocks, &zcl.Block{
|
||||||
|
Type: typeName,
|
||||||
|
Labels: labels,
|
||||||
|
Body: &body{
|
||||||
|
obj: tv,
|
||||||
|
},
|
||||||
|
|
||||||
|
DefRange: tv.OpenRange,
|
||||||
|
TypeRange: *typeRange,
|
||||||
|
LabelRanges: labelR,
|
||||||
|
})
|
||||||
|
case *arrayVal:
|
||||||
|
// Multiple instances of the block
|
||||||
|
for _, av := range tv.Values {
|
||||||
|
ov, ok := av.(*objectVal)
|
||||||
|
if !ok {
|
||||||
|
diags = diags.Append(&zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Incorrect JSON value type",
|
||||||
|
Detail: fmt.Sprintf("A JSON object is required, representing the contents of a %q block.", typeName),
|
||||||
|
Subject: v.StartRange().Ptr(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
*blocks = append(*blocks, &zcl.Block{
|
||||||
|
Type: typeName,
|
||||||
|
Labels: labels,
|
||||||
|
Body: &body{
|
||||||
|
obj: ov,
|
||||||
|
},
|
||||||
|
|
||||||
|
DefRange: tv.OpenRange,
|
||||||
|
TypeRange: *typeRange,
|
||||||
|
LabelRanges: labelR,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
diags = diags.Append(&zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Incorrect JSON value type",
|
||||||
|
Detail: fmt.Sprintf("Either a JSON object or a JSON array is required, representing the contents of one or more %q blocks.", typeName),
|
||||||
|
Subject: v.StartRange().Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *expression) LiteralValue() (cty.Value, zcl.Diagnostics) {
|
||||||
|
// TODO: Implement
|
||||||
|
return cty.NilVal, nil
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
package zcl
|
package zcl
|
||||||
|
|
||||||
// ElementHeaderSchema represents the shape of an element header, and is
|
// BlockHeaderSchema represents the shape of a block header, and is
|
||||||
// used for matching elements within bodies.
|
// used for matching blocks within bodies.
|
||||||
type ElementHeaderSchema struct {
|
type BlockHeaderSchema struct {
|
||||||
Name string
|
Type string
|
||||||
LabelNames []string
|
LabelNames []string
|
||||||
Single bool
|
}
|
||||||
|
|
||||||
|
// AttributeSchema represents the requirements for an attribute, and is used
|
||||||
|
// for matching attributes within bodies.
|
||||||
|
type AttributeSchema struct {
|
||||||
|
Name string
|
||||||
|
Required bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// BodySchema represents the desired shallow structure of a body.
|
// BodySchema represents the desired shallow structure of a body.
|
||||||
type BodySchema struct {
|
type BodySchema struct {
|
||||||
Attributes []string
|
Attributes []AttributeSchema
|
||||||
Elements []ElementHeaderSchema
|
Blocks []BlockHeaderSchema
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ type Body interface {
|
|||||||
|
|
||||||
// BodyContent is the result of applying a BodySchema to a Body.
|
// BodyContent is the result of applying a BodySchema to a Body.
|
||||||
type BodyContent struct {
|
type BodyContent struct {
|
||||||
Attributes map[string]Attribute
|
Attributes map[string]*Attribute
|
||||||
Blocks Blocks
|
Blocks Blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ type Attribute struct {
|
|||||||
// Expression is a literal value or an expression provided in the
|
// Expression is a literal value or an expression provided in the
|
||||||
// configuration, which can be evaluated within a scope to produce a value.
|
// configuration, which can be evaluated within a scope to produce a value.
|
||||||
type Expression interface {
|
type Expression interface {
|
||||||
LiteralValue() cty.Value
|
LiteralValue() (cty.Value, Diagnostics)
|
||||||
// TODO: evaluation of non-literal expressions
|
// TODO: evaluation of non-literal expressions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user