269 lines
7.1 KiB
Go
269 lines
7.1 KiB
Go
package yaml
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
)
|
|
|
|
func (c *Converter) impliedType(src []byte) (cty.Type, error) {
|
|
p := &yaml_parser_t{}
|
|
if !yaml_parser_initialize(p) {
|
|
return cty.NilType, errors.New("failed to initialize YAML parser")
|
|
}
|
|
if len(src) == 0 {
|
|
src = []byte{'\n'}
|
|
}
|
|
|
|
an := &typeAnalysis{
|
|
anchorsPending: map[string]int{},
|
|
anchorTypes: map[string]cty.Type{},
|
|
}
|
|
|
|
yaml_parser_set_input_string(p, src)
|
|
|
|
var evt yaml_event_t
|
|
if !yaml_parser_parse(p, &evt) {
|
|
return cty.NilType, parserError(p)
|
|
}
|
|
if evt.typ != yaml_STREAM_START_EVENT {
|
|
return cty.NilType, parseEventErrorf(&evt, "missing stream start token")
|
|
}
|
|
if !yaml_parser_parse(p, &evt) {
|
|
return cty.NilType, parserError(p)
|
|
}
|
|
if evt.typ != yaml_DOCUMENT_START_EVENT {
|
|
return cty.NilType, parseEventErrorf(&evt, "missing start of document")
|
|
}
|
|
|
|
ty, err := c.impliedTypeParse(an, p)
|
|
if err != nil {
|
|
return cty.NilType, err
|
|
}
|
|
|
|
if !yaml_parser_parse(p, &evt) {
|
|
return cty.NilType, parserError(p)
|
|
}
|
|
if evt.typ == yaml_DOCUMENT_START_EVENT {
|
|
return cty.NilType, parseEventErrorf(&evt, "only a single document is allowed")
|
|
}
|
|
if evt.typ != yaml_DOCUMENT_END_EVENT {
|
|
return cty.NilType, parseEventErrorf(&evt, "unexpected extra content (%s) after value", evt.typ.String())
|
|
}
|
|
if !yaml_parser_parse(p, &evt) {
|
|
return cty.NilType, parserError(p)
|
|
}
|
|
if evt.typ != yaml_STREAM_END_EVENT {
|
|
return cty.NilType, parseEventErrorf(&evt, "unexpected extra content after value")
|
|
}
|
|
|
|
return ty, err
|
|
}
|
|
|
|
func (c *Converter) impliedTypeParse(an *typeAnalysis, p *yaml_parser_t) (cty.Type, error) {
|
|
var evt yaml_event_t
|
|
if !yaml_parser_parse(p, &evt) {
|
|
return cty.NilType, parserError(p)
|
|
}
|
|
return c.impliedTypeParseRemainder(an, &evt, p)
|
|
}
|
|
|
|
func (c *Converter) impliedTypeParseRemainder(an *typeAnalysis, evt *yaml_event_t, p *yaml_parser_t) (cty.Type, error) {
|
|
switch evt.typ {
|
|
case yaml_SCALAR_EVENT:
|
|
return c.impliedTypeScalar(an, evt, p)
|
|
case yaml_ALIAS_EVENT:
|
|
return c.impliedTypeAlias(an, evt, p)
|
|
case yaml_MAPPING_START_EVENT:
|
|
return c.impliedTypeMapping(an, evt, p)
|
|
case yaml_SEQUENCE_START_EVENT:
|
|
return c.impliedTypeSequence(an, evt, p)
|
|
case yaml_DOCUMENT_START_EVENT:
|
|
return cty.NilType, parseEventErrorf(evt, "only a single document is allowed")
|
|
case yaml_STREAM_END_EVENT:
|
|
// Decoding an empty buffer, probably
|
|
return cty.NilType, parseEventErrorf(evt, "expecting value but found end of stream")
|
|
default:
|
|
// Should never happen; the above should be comprehensive
|
|
return cty.NilType, parseEventErrorf(evt, "unexpected parser event %s", evt.typ.String())
|
|
}
|
|
}
|
|
|
|
func (c *Converter) impliedTypeScalar(an *typeAnalysis, evt *yaml_event_t, p *yaml_parser_t) (cty.Type, error) {
|
|
src := evt.value
|
|
tag := string(evt.tag)
|
|
anchor := string(evt.anchor)
|
|
implicit := evt.implicit
|
|
|
|
if len(anchor) > 0 {
|
|
an.beginAnchor(anchor)
|
|
}
|
|
|
|
var ty cty.Type
|
|
switch {
|
|
case tag == "" && !implicit:
|
|
// Untagged explicit string
|
|
ty = cty.String
|
|
default:
|
|
v, err := c.resolveScalar(tag, string(src), yaml_scalar_style_t(evt.style))
|
|
if err != nil {
|
|
return cty.NilType, parseEventErrorWrap(evt, err)
|
|
}
|
|
if v.RawEquals(mergeMappingVal) {
|
|
// In any context other than a mapping key, this is just a plain string
|
|
ty = cty.String
|
|
} else {
|
|
ty = v.Type()
|
|
}
|
|
}
|
|
|
|
if len(anchor) > 0 {
|
|
an.completeAnchor(anchor, ty)
|
|
}
|
|
return ty, nil
|
|
}
|
|
|
|
func (c *Converter) impliedTypeMapping(an *typeAnalysis, evt *yaml_event_t, p *yaml_parser_t) (cty.Type, error) {
|
|
tag := string(evt.tag)
|
|
anchor := string(evt.anchor)
|
|
|
|
if tag != "" && tag != yaml_MAP_TAG {
|
|
return cty.NilType, parseEventErrorf(evt, "can't interpret mapping as %s", tag)
|
|
}
|
|
|
|
if anchor != "" {
|
|
an.beginAnchor(anchor)
|
|
}
|
|
|
|
atys := make(map[string]cty.Type)
|
|
for {
|
|
var nextEvt yaml_event_t
|
|
if !yaml_parser_parse(p, &nextEvt) {
|
|
return cty.NilType, parserError(p)
|
|
}
|
|
if nextEvt.typ == yaml_MAPPING_END_EVENT {
|
|
ty := cty.Object(atys)
|
|
if anchor != "" {
|
|
an.completeAnchor(anchor, ty)
|
|
}
|
|
return ty, nil
|
|
}
|
|
|
|
if nextEvt.typ != yaml_SCALAR_EVENT {
|
|
return cty.NilType, parseEventErrorf(&nextEvt, "only strings are allowed as mapping keys")
|
|
}
|
|
keyVal, err := c.resolveScalar(string(nextEvt.tag), string(nextEvt.value), yaml_scalar_style_t(nextEvt.style))
|
|
if err != nil {
|
|
return cty.NilType, err
|
|
}
|
|
if keyVal.RawEquals(mergeMappingVal) {
|
|
// Merging the value (which must be a mapping) into our mapping,
|
|
// then.
|
|
ty, err := c.impliedTypeParse(an, p)
|
|
if err != nil {
|
|
return cty.NilType, err
|
|
}
|
|
if !ty.IsObjectType() {
|
|
return cty.NilType, parseEventErrorf(&nextEvt, "cannot merge %s into mapping", ty.FriendlyName())
|
|
}
|
|
for name, aty := range ty.AttributeTypes() {
|
|
atys[name] = aty
|
|
}
|
|
continue
|
|
}
|
|
if keyValStr, err := convert.Convert(keyVal, cty.String); err == nil {
|
|
keyVal = keyValStr
|
|
} else {
|
|
return cty.NilType, parseEventErrorf(&nextEvt, "only strings are allowed as mapping keys")
|
|
}
|
|
if keyVal.IsNull() {
|
|
return cty.NilType, parseEventErrorf(&nextEvt, "mapping key cannot be null")
|
|
}
|
|
if !keyVal.IsKnown() {
|
|
return cty.NilType, parseEventErrorf(&nextEvt, "mapping key must be known")
|
|
}
|
|
valTy, err := c.impliedTypeParse(an, p)
|
|
if err != nil {
|
|
return cty.NilType, err
|
|
}
|
|
|
|
atys[keyVal.AsString()] = valTy
|
|
}
|
|
}
|
|
|
|
func (c *Converter) impliedTypeSequence(an *typeAnalysis, evt *yaml_event_t, p *yaml_parser_t) (cty.Type, error) {
|
|
tag := string(evt.tag)
|
|
anchor := string(evt.anchor)
|
|
|
|
if tag != "" && tag != yaml_SEQ_TAG {
|
|
return cty.NilType, parseEventErrorf(evt, "can't interpret sequence as %s", tag)
|
|
}
|
|
|
|
if anchor != "" {
|
|
an.beginAnchor(anchor)
|
|
}
|
|
|
|
var atys []cty.Type
|
|
for {
|
|
var nextEvt yaml_event_t
|
|
if !yaml_parser_parse(p, &nextEvt) {
|
|
return cty.NilType, parserError(p)
|
|
}
|
|
if nextEvt.typ == yaml_SEQUENCE_END_EVENT {
|
|
ty := cty.Tuple(atys)
|
|
if anchor != "" {
|
|
an.completeAnchor(anchor, ty)
|
|
}
|
|
return ty, nil
|
|
}
|
|
|
|
valTy, err := c.impliedTypeParseRemainder(an, &nextEvt, p)
|
|
if err != nil {
|
|
return cty.NilType, err
|
|
}
|
|
|
|
atys = append(atys, valTy)
|
|
}
|
|
}
|
|
|
|
func (c *Converter) impliedTypeAlias(an *typeAnalysis, evt *yaml_event_t, p *yaml_parser_t) (cty.Type, error) {
|
|
ty, err := an.anchorType(string(evt.anchor))
|
|
if err != nil {
|
|
err = parseEventErrorWrap(evt, err)
|
|
}
|
|
return ty, err
|
|
}
|
|
|
|
type typeAnalysis struct {
|
|
anchorsPending map[string]int
|
|
anchorTypes map[string]cty.Type
|
|
}
|
|
|
|
func (an *typeAnalysis) beginAnchor(name string) {
|
|
an.anchorsPending[name]++
|
|
}
|
|
|
|
func (an *typeAnalysis) completeAnchor(name string, ty cty.Type) {
|
|
an.anchorsPending[name]--
|
|
if an.anchorsPending[name] == 0 {
|
|
delete(an.anchorsPending, name)
|
|
}
|
|
an.anchorTypes[name] = ty
|
|
}
|
|
|
|
func (an *typeAnalysis) anchorType(name string) (cty.Type, error) {
|
|
if _, pending := an.anchorsPending[name]; pending {
|
|
// YAML normally allows self-referencing structures, but cty cannot
|
|
// represent them (it requires all structures to be finite) so we
|
|
// must fail here.
|
|
return cty.NilType, fmt.Errorf("cannot refer to anchor %q from inside its own definition", name)
|
|
}
|
|
ty, ok := an.anchorTypes[name]
|
|
if !ok {
|
|
return cty.NilType, fmt.Errorf("reference to undefined anchor %q", name)
|
|
}
|
|
return ty, nil
|
|
}
|