package hclpack import ( "encoding/json" "github.com/hashicorp/hcl2/hcl" ) // MarshalJSON is an implementation of Marshaler from encoding/json, allowing // bodies to be included in other types that are JSON-marshalable. // // The result of MarshalJSON is optimized for compactness rather than easy // human consumption/editing. Use UnmarshalJSON to decode it. func (b *Body) MarshalJSON() ([]byte, error) { rngs := make(map[hcl.Range]struct{}) b.addRanges(rngs) fns, posList, posMap := packPositions(rngs) head := jsonHeader{ Body: b.forJSON(posMap), Sources: fns, Pos: posList, } return json.Marshal(&head) } func (b *Body) forJSON(pos map[string]map[hcl.Pos]posOfs) bodyJSON { var ret bodyJSON if len(b.Attributes) > 0 { ret.Attrs = make(map[string]attrJSON, len(b.Attributes)) for name, attr := range b.Attributes { ret.Attrs[name] = attr.forJSON(pos) } } if len(b.ChildBlocks) > 0 { ret.Blocks = make([]blockJSON, len(b.ChildBlocks)) for i, block := range b.ChildBlocks { ret.Blocks[i] = block.forJSON(pos) } } ret.Ranges = make(rangesPacked, 1) ret.Ranges[0] = packRange(b.MissingItemRange_, pos) return ret } func (a *Attribute) forJSON(pos map[string]map[hcl.Pos]posOfs) attrJSON { var ret attrJSON ret.Source = string(a.Expr.Source) switch a.Expr.SourceType { case ExprNative: ret.Syntax = 0 case ExprTemplate: ret.Syntax = 1 case ExprLiteralJSON: ret.Syntax = 2 } ret.Ranges = make(rangesPacked, 4) ret.Ranges[0] = packRange(a.Range, pos) ret.Ranges[1] = packRange(a.NameRange, pos) ret.Ranges[2] = packRange(a.Expr.Range_, pos) ret.Ranges[3] = packRange(a.Expr.StartRange_, pos) return ret } func (b *Block) forJSON(pos map[string]map[hcl.Pos]posOfs) blockJSON { var ret blockJSON ret.Header = make([]string, len(b.Labels)+1) ret.Header[0] = b.Type copy(ret.Header[1:], b.Labels) ret.Body = b.Body.forJSON(pos) ret.Ranges = make(rangesPacked, 2+len(b.LabelRanges)) ret.Ranges[0] = packRange(b.DefRange, pos) ret.Ranges[1] = packRange(b.TypeRange, pos) for i, rng := range b.LabelRanges { ret.Ranges[i+2] = packRange(rng, pos) } return ret } // UnmarshalJSON is an implementation of Unmarshaler from encoding/json, // allowing bodies to be included in other types that are JSON-unmarshalable. func (b *Body) UnmarshalJSON(data []byte) error { var head jsonHeader err := json.Unmarshal(data, &head) if err != nil { return err } fns := head.Sources positions := head.Pos.Unpack() *b = head.Body.decode(fns, positions) return nil } type jsonHeader struct { Body bodyJSON `json:"r"` Sources []string `json:"s,omitempty"` Pos positionsPacked `json:"p,omitempty"` } type bodyJSON struct { // Files are the source filenames that were involved in Attrs map[string]attrJSON `json:"a,omitempty"` Blocks []blockJSON `json:"b,omitempty"` // Ranges contains the MissingItemRange Ranges rangesPacked `json:"r,omitempty"` } func (bj *bodyJSON) decode(fns []string, positions []position) Body { var ret Body if len(bj.Attrs) > 0 { ret.Attributes = make(map[string]Attribute, len(bj.Attrs)) for name, aj := range bj.Attrs { ret.Attributes[name] = aj.decode(fns, positions) } } if len(bj.Blocks) > 0 { ret.ChildBlocks = make([]Block, len(bj.Blocks)) for i, blj := range bj.Blocks { ret.ChildBlocks[i] = blj.decode(fns, positions) } } ret.MissingItemRange_ = bj.Ranges.UnpackIdx(fns, positions, 0) return ret } type attrJSON struct { // To keep things compact, in the JSON encoding we flatten the // expression down into the attribute object, since overhead // for attributes adds up in a complex config. Source string `json:"s"` Syntax int `json:"t,omitempty"` // omitted for 0=native // Ranges contains the Range, NameRange, Expr.Range, Expr.StartRange Ranges rangesPacked `json:"r,omitempty"` } func (aj *attrJSON) decode(fns []string, positions []position) Attribute { var ret Attribute ret.Expr.Source = []byte(aj.Source) switch aj.Syntax { case 0: ret.Expr.SourceType = ExprNative case 1: ret.Expr.SourceType = ExprTemplate case 2: ret.Expr.SourceType = ExprLiteralJSON } ret.Range = aj.Ranges.UnpackIdx(fns, positions, 0) ret.NameRange = aj.Ranges.UnpackIdx(fns, positions, 1) ret.Expr.Range_ = aj.Ranges.UnpackIdx(fns, positions, 2) ret.Expr.StartRange_ = aj.Ranges.UnpackIdx(fns, positions, 3) if ret.Expr.StartRange_ == (hcl.Range{}) { // If the start range wasn't present then we'll just use the Range ret.Expr.StartRange_ = ret.Expr.Range_ } return ret } type blockJSON struct { // Header is the type followed by any labels. We flatten this here // to keep the JSON encoding compact. Header []string `json:"h"` Body bodyJSON `json:"b,omitempty"` // Ranges contains the DefRange followed by the TypeRange and then // each of the label ranges in turn. Ranges rangesPacked `json:"r,omitempty"` } func (blj *blockJSON) decode(fns []string, positions []position) Block { var ret Block if len(blj.Header) > 0 { // If the header is invalid then we'll end up with an empty type ret.Type = blj.Header[0] } if len(blj.Header) > 1 { ret.Labels = blj.Header[1:] } ret.Body = blj.Body.decode(fns, positions) ret.DefRange = blj.Ranges.UnpackIdx(fns, positions, 0) ret.TypeRange = blj.Ranges.UnpackIdx(fns, positions, 1) if len(ret.Labels) > 0 { ret.LabelRanges = make([]hcl.Range, len(ret.Labels)) for i := range ret.Labels { ret.LabelRanges[i] = blj.Ranges.UnpackIdx(fns, positions, i+2) } } return ret }