cuelang.org/go@v0.10.1/internal/encoding/json/encode.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package json
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"math/big"
    21  	"strings"
    22  
    23  	"cuelang.org/go/cue/ast"
    24  	"cuelang.org/go/cue/errors"
    25  	"cuelang.org/go/cue/literal"
    26  	"cuelang.org/go/cue/token"
    27  	"cuelang.org/go/internal"
    28  	"cuelang.org/go/internal/astinternal"
    29  )
    30  
    31  // Marshal is a replacement for [json.Marshal] without HTML escaping.
    32  func Marshal(v any) ([]byte, error) {
    33  	var buf bytes.Buffer
    34  	enc := json.NewEncoder(&buf)
    35  	enc.SetEscapeHTML(false)
    36  	if err := enc.Encode(v); err != nil {
    37  		return nil, err
    38  	}
    39  	p := buf.Bytes()
    40  	// Unlike json.Marshal, json.Encoder.Encode adds a trailing newline.
    41  	p = bytes.TrimSuffix(p, []byte("\n"))
    42  	return p, nil
    43  }
    44  
    45  // Encode converts a CUE AST to unescaped JSON.
    46  //
    47  // The given file must only contain values that can be directly supported by
    48  // JSON:
    49  //
    50  //	Type          Restrictions
    51  //	BasicLit
    52  //	File          no imports, aliases, or definitions
    53  //	StructLit     no embeddings, aliases, or definitions
    54  //	List
    55  //	Field         must be regular; label must be a BasicLit or Ident
    56  //
    57  // Comments and attributes are ignored.
    58  func Encode(n ast.Node) (b []byte, err error) {
    59  	e := encoder{}
    60  	err = e.encode(n)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return e.w.Bytes(), nil
    65  }
    66  
    67  type encoder struct {
    68  	w              bytes.Buffer
    69  	tab            []byte
    70  	indentsAtLevel []int
    71  	indenting      bool
    72  	unIndenting    int
    73  }
    74  
    75  func (e *encoder) writeIndent(b byte) {
    76  	if e.indenting {
    77  		e.indentsAtLevel[len(e.indentsAtLevel)-1]++
    78  	} else {
    79  		e.indentsAtLevel = append(e.indentsAtLevel, 0)
    80  	}
    81  	e.indenting = true
    82  	_ = e.w.WriteByte(b)
    83  }
    84  
    85  func (e *encoder) writeUnindent(b byte, pos, def token.Pos) {
    86  	if e.unIndenting > 0 {
    87  		e.unIndenting--
    88  	} else {
    89  		e.unIndenting = e.indentsAtLevel[len(e.indentsAtLevel)-1]
    90  		e.indentsAtLevel = e.indentsAtLevel[:len(e.indentsAtLevel)-1]
    91  	}
    92  	e.indenting = false
    93  	e.ws(pos, def.RelPos())
    94  	_ = e.w.WriteByte(b)
    95  }
    96  
    97  func (e *encoder) writeString(s string) {
    98  	_, _ = e.w.WriteString(s)
    99  	e.indenting = false
   100  }
   101  
   102  func (e *encoder) writeByte(b byte) {
   103  	_ = e.w.WriteByte(b)
   104  }
   105  
   106  func (e *encoder) write(b []byte) {
   107  	_, _ = e.w.Write(b)
   108  	e.indenting = false
   109  }
   110  
   111  func (e *encoder) indent() {
   112  	for range e.indentsAtLevel {
   113  		e.write(e.tab)
   114  	}
   115  }
   116  
   117  func (e *encoder) ws(pos token.Pos, default_ token.RelPos) {
   118  	rel := pos.RelPos()
   119  	if pos == token.NoPos {
   120  		rel = default_
   121  	}
   122  	switch rel {
   123  	case token.NoSpace:
   124  	case token.Blank:
   125  		e.writeByte(' ')
   126  	case token.Newline:
   127  		e.writeByte('\n')
   128  		e.indent()
   129  	case token.NewSection:
   130  		e.writeString("\n\n")
   131  		e.indent()
   132  	}
   133  }
   134  func (e *encoder) encode(n ast.Node) error {
   135  	if e.tab == nil {
   136  		e.tab = []byte("    ")
   137  	}
   138  	const defPos = token.NoSpace
   139  	switch x := n.(type) {
   140  	case *ast.BasicLit:
   141  		e.ws(x.Pos(), defPos)
   142  		return e.encodeScalar(x, true)
   143  
   144  	case *ast.ListLit:
   145  		e.ws(foldNewline(x.Pos()), token.NoRelPos)
   146  		if len(x.Elts) == 0 {
   147  			e.writeString("[]")
   148  			return nil
   149  		}
   150  		e.writeIndent('[')
   151  		for i, x := range x.Elts {
   152  			if i > 0 {
   153  				e.writeString(",")
   154  			}
   155  			if err := e.encode(x); err != nil {
   156  				return err
   157  			}
   158  		}
   159  		e.writeUnindent(']', x.Rbrack, compactNewline(x.Elts[0].Pos()))
   160  		return nil
   161  
   162  	case *ast.StructLit:
   163  		e.ws(foldNewline(n.Pos()), token.NoRelPos)
   164  		return e.encodeDecls(x.Elts, x.Rbrace)
   165  
   166  	case *ast.File:
   167  		return e.encodeDecls(x.Decls, token.NoPos)
   168  
   169  	case *ast.UnaryExpr:
   170  		e.ws(foldNewline(x.Pos()), defPos)
   171  		l, ok := x.X.(*ast.BasicLit)
   172  		if ok && x.Op == token.SUB && (l.Kind == token.INT || l.Kind == token.FLOAT) {
   173  			e.writeByte('-')
   174  			return e.encodeScalar(l, false)
   175  		}
   176  	}
   177  	return errors.Newf(n.Pos(), "json: unsupported node %s (%T)", astinternal.DebugStr(n), n)
   178  }
   179  
   180  func (e *encoder) encodeScalar(l *ast.BasicLit, allowMinus bool) error {
   181  	switch l.Kind {
   182  	case token.INT:
   183  		var x big.Int
   184  		return e.setNum(l, allowMinus, &x)
   185  
   186  	case token.FLOAT:
   187  		var x big.Float
   188  		return e.setNum(l, allowMinus, &x)
   189  
   190  	case token.TRUE:
   191  		e.writeString("true")
   192  
   193  	case token.FALSE:
   194  		e.writeString("false")
   195  
   196  	case token.NULL:
   197  		e.writeString("null")
   198  
   199  	case token.STRING:
   200  		str, err := literal.Unquote(l.Value)
   201  		if err != nil {
   202  			return err
   203  		}
   204  		b, err := Marshal(str)
   205  		if err != nil {
   206  			return err
   207  		}
   208  		e.write(b)
   209  
   210  	default:
   211  		return errors.Newf(l.Pos(), "unknown literal type %v", l.Kind)
   212  	}
   213  	return nil
   214  }
   215  
   216  func (e *encoder) setNum(l *ast.BasicLit, allowMinus bool, x interface{}) error {
   217  	if !allowMinus && strings.HasPrefix(l.Value, "-") {
   218  		return errors.Newf(l.Pos(), "double minus not allowed")
   219  	}
   220  	var ni literal.NumInfo
   221  	if err := literal.ParseNum(l.Value, &ni); err != nil {
   222  		return err
   223  	}
   224  	e.writeString(ni.String())
   225  	return nil
   226  }
   227  
   228  // encodeDecls converts a sequence of declarations to a value. If it encounters
   229  // an embedded value, it will return this expression. This is more relaxed for
   230  // structs than is currently allowed for CUE, but the expectation is that this
   231  // will be allowed at some point. The input would still be illegal CUE.
   232  func (e *encoder) encodeDecls(decls []ast.Decl, endPos token.Pos) error {
   233  	var embed ast.Expr
   234  	var fields []*ast.Field
   235  
   236  	for _, d := range decls {
   237  		switch x := d.(type) {
   238  		default:
   239  			return errors.Newf(x.Pos(), "json: unsupported node %s (%T)", astinternal.DebugStr(x), x)
   240  
   241  		case *ast.Package:
   242  			if embed != nil || fields != nil {
   243  				return errors.Newf(x.Pos(), "invalid package clause")
   244  			}
   245  			continue
   246  
   247  		case *ast.Field:
   248  			if !internal.IsRegularField(x) {
   249  				return errors.Newf(x.TokenPos, "json: definition or hidden field not allowed")
   250  			}
   251  			if x.Optional != token.NoPos {
   252  				return errors.Newf(x.Optional, "json: optional fields not allowed")
   253  			}
   254  			fields = append(fields, x)
   255  
   256  		case *ast.EmbedDecl:
   257  			if embed != nil {
   258  				return errors.Newf(x.Pos(), "json: multiple embedded values")
   259  			}
   260  			embed = x.Expr
   261  
   262  		case *ast.CommentGroup:
   263  		}
   264  	}
   265  
   266  	if embed != nil {
   267  		if fields != nil {
   268  			return errors.Newf(embed.Pos(), "json: embedding mixed with fields")
   269  		}
   270  		return e.encode(embed)
   271  	}
   272  
   273  	if len(fields) == 0 {
   274  		e.writeString("{}")
   275  		return nil
   276  	}
   277  
   278  	e.writeIndent('{')
   279  	pos := compactNewline(fields[0].Pos())
   280  	if endPos == token.NoPos && pos.RelPos() == token.Blank {
   281  		pos = token.NoPos
   282  	}
   283  	firstPos := pos
   284  	const defPos = token.NoRelPos
   285  	for i, x := range fields {
   286  		if i > 0 {
   287  			e.writeByte(',')
   288  			pos = x.Pos()
   289  		}
   290  		name, _, err := ast.LabelName(x.Label)
   291  		if err != nil {
   292  			return errors.Newf(x.Label.Pos(), "json: only literal labels allowed")
   293  		}
   294  		b, err := Marshal(name)
   295  		if err != nil {
   296  			return err
   297  		}
   298  		e.ws(pos, defPos)
   299  		e.write(b)
   300  		e.writeByte(':')
   301  
   302  		if err := e.encode(x.Value); err != nil {
   303  			return err
   304  		}
   305  	}
   306  	e.writeUnindent('}', endPos, firstPos)
   307  	return nil
   308  }
   309  
   310  func compactNewline(pos token.Pos) token.Pos {
   311  	if pos.RelPos() == token.NewSection {
   312  		pos = token.Newline.Pos()
   313  	}
   314  	return pos
   315  }
   316  
   317  func foldNewline(pos token.Pos) token.Pos {
   318  	if pos.RelPos() >= token.Newline {
   319  		pos = token.Blank.Pos()
   320  	}
   321  	return pos
   322  }