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

     1  // Copyright 2019 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 converts JSON to and from CUE.
    16  package json
    17  
    18  import (
    19  	"bytes"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"strings"
    24  
    25  	"cuelang.org/go/cue"
    26  	"cuelang.org/go/cue/ast"
    27  	"cuelang.org/go/cue/ast/astutil"
    28  	"cuelang.org/go/cue/errors"
    29  	"cuelang.org/go/cue/literal"
    30  	"cuelang.org/go/cue/parser"
    31  	"cuelang.org/go/cue/token"
    32  	"cuelang.org/go/internal/source"
    33  )
    34  
    35  // Valid reports whether data is a valid JSON encoding.
    36  func Valid(b []byte) bool {
    37  	return json.Valid(b)
    38  }
    39  
    40  // Validate validates JSON and confirms it matches the constraints
    41  // specified by v.
    42  func Validate(b []byte, v cue.Value) error {
    43  	if !json.Valid(b) {
    44  		return fmt.Errorf("json: invalid JSON")
    45  	}
    46  	v2 := v.Context().CompileBytes(b, cue.Filename("json.Validate"))
    47  	if err := v2.Err(); err != nil {
    48  		return err
    49  	}
    50  
    51  	v = v.Unify(v2)
    52  	if err := v.Err(); err != nil {
    53  		return err
    54  	}
    55  	return v.Validate(cue.Final())
    56  }
    57  
    58  // Extract parses JSON-encoded data to a CUE expression, using path for
    59  // position information.
    60  func Extract(path string, data []byte) (ast.Expr, error) {
    61  	expr, err := extract(path, data)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	patchExpr(expr, nil)
    66  	return expr, nil
    67  }
    68  
    69  // Decode parses JSON-encoded data to a CUE value, using path for position
    70  // information.
    71  //
    72  // Deprecated: use Extract and build using cue.Context.BuildExpr.
    73  func Decode(r *cue.Runtime, path string, data []byte) (*cue.Instance, error) {
    74  	expr, err := extract(path, data)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	return r.CompileExpr(expr)
    79  }
    80  
    81  func extract(path string, b []byte) (ast.Expr, error) {
    82  	expr, err := parser.ParseExpr(path, b)
    83  	if err != nil || !json.Valid(b) {
    84  		p := token.NoPos
    85  		if pos := errors.Positions(err); len(pos) > 0 {
    86  			p = pos[0]
    87  		}
    88  		var x interface{}
    89  		err := json.Unmarshal(b, &x)
    90  
    91  		// If encoding/json has a position, prefer that, as it relates to json.Unmarshal's error message.
    92  		if synErr, ok := err.(*json.SyntaxError); ok && len(b) > 0 {
    93  			tokFile := token.NewFile(path, 0, len(b))
    94  			tokFile.SetLinesForContent(b)
    95  			p = tokFile.Pos(int(synErr.Offset-1), token.NoRelPos)
    96  		}
    97  
    98  		return nil, errors.Wrapf(err, p, "invalid JSON for file %q", path)
    99  	}
   100  	return expr, nil
   101  }
   102  
   103  // NewDecoder configures a JSON decoder. The path is used to associate position
   104  // information with each node. The runtime may be nil if the decoder
   105  // is only used to extract to CUE ast objects.
   106  //
   107  // The runtime may be nil if Decode isn't used.
   108  func NewDecoder(r *cue.Runtime, path string, src io.Reader) *Decoder {
   109  	b, err := source.ReadAll(path, src)
   110  	tokFile := token.NewFile(path, 0, len(b))
   111  	tokFile.SetLinesForContent(b)
   112  	return &Decoder{
   113  		r:          r,
   114  		path:       path,
   115  		dec:        json.NewDecoder(bytes.NewReader(b)),
   116  		tokFile:    tokFile,
   117  		readAllErr: err,
   118  	}
   119  }
   120  
   121  // A Decoder converts JSON values to CUE.
   122  type Decoder struct {
   123  	r    *cue.Runtime
   124  	path string
   125  	dec  *json.Decoder
   126  
   127  	startOffset int
   128  	tokFile     *token.File
   129  	readAllErr  error
   130  }
   131  
   132  // Extract converts the current JSON value to a CUE ast. It returns io.EOF
   133  // if the input has been exhausted.
   134  func (d *Decoder) Extract() (ast.Expr, error) {
   135  	if d.readAllErr != nil {
   136  		return nil, d.readAllErr
   137  	}
   138  
   139  	expr, err := d.extract()
   140  	if err != nil {
   141  		return expr, err
   142  	}
   143  	patchExpr(expr, d.patchPos)
   144  	return expr, nil
   145  }
   146  
   147  func (d *Decoder) extract() (ast.Expr, error) {
   148  	var raw json.RawMessage
   149  	err := d.dec.Decode(&raw)
   150  	if err == io.EOF {
   151  		return nil, err
   152  	}
   153  	if err != nil {
   154  		pos := token.NoPos
   155  		// When decoding into a RawMessage, encoding/json should only error due to syntax errors.
   156  		if synErr, ok := err.(*json.SyntaxError); ok {
   157  			pos = d.tokFile.Pos(int(synErr.Offset-1), token.NoRelPos)
   158  		}
   159  		return nil, errors.Wrapf(err, pos, "invalid JSON for file %q", d.path)
   160  	}
   161  	expr, err := parser.ParseExpr(d.path, []byte(raw))
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	d.startOffset = int(d.dec.InputOffset()) - len(raw)
   167  	return expr, nil
   168  }
   169  
   170  func (d *Decoder) patchPos(n ast.Node) {
   171  	pos := n.Pos()
   172  	realPos := d.tokFile.Pos(pos.Offset()+d.startOffset, pos.RelPos())
   173  	ast.SetPos(n, realPos)
   174  }
   175  
   176  // Decode converts the current JSON value to a CUE instance. It returns io.EOF
   177  // if the input has been exhausted.
   178  //
   179  // Deprecated: use Extract and build with cue.Context.BuildExpr.
   180  func (d *Decoder) Decode() (*cue.Instance, error) {
   181  	expr, err := d.Extract()
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	return d.r.CompileExpr(expr)
   186  }
   187  
   188  // patchExpr simplifies the AST parsed from JSON.
   189  // TODO: some of the modifications are already done in format, but are
   190  // a package deal of a more aggressive simplify. Other pieces of modification
   191  // should probably be moved to format.
   192  func patchExpr(n ast.Node, patchPos func(n ast.Node)) {
   193  	type info struct {
   194  		reflow bool
   195  	}
   196  	stack := []info{{true}}
   197  
   198  	afterFn := func(n ast.Node) {
   199  		switch n.(type) {
   200  		case *ast.ListLit, *ast.StructLit:
   201  			stack = stack[:len(stack)-1]
   202  		}
   203  	}
   204  
   205  	var beforeFn func(n ast.Node) bool
   206  
   207  	beforeFn = func(n ast.Node) bool {
   208  		if patchPos != nil {
   209  			patchPos(n)
   210  		}
   211  
   212  		isLarge := n.End().Offset()-n.Pos().Offset() > 50
   213  		descent := true
   214  
   215  		switch x := n.(type) {
   216  		case *ast.ListLit:
   217  			reflow := true
   218  			if !isLarge {
   219  				for _, e := range x.Elts {
   220  					if hasSpaces(e) {
   221  						reflow = false
   222  						break
   223  					}
   224  				}
   225  			}
   226  			stack = append(stack, info{reflow})
   227  			if reflow {
   228  				x.Lbrack = x.Lbrack.WithRel(token.NoRelPos)
   229  				x.Rbrack = x.Rbrack.WithRel(token.NoRelPos)
   230  			}
   231  			return true
   232  
   233  		case *ast.StructLit:
   234  			reflow := true
   235  			if !isLarge {
   236  				for _, e := range x.Elts {
   237  					if f, ok := e.(*ast.Field); !ok || hasSpaces(f) || hasSpaces(f.Value) {
   238  						reflow = false
   239  						break
   240  					}
   241  				}
   242  			}
   243  			stack = append(stack, info{reflow})
   244  			if reflow {
   245  				x.Lbrace = x.Lbrace.WithRel(token.NoRelPos)
   246  				x.Rbrace = x.Rbrace.WithRel(token.NoRelPos)
   247  			}
   248  			return true
   249  
   250  		case *ast.Field:
   251  			// label is always a string for JSON.
   252  			switch {
   253  			case true:
   254  				s, ok := x.Label.(*ast.BasicLit)
   255  				if !ok || s.Kind != token.STRING {
   256  					break // should not happen: implies invalid JSON
   257  				}
   258  
   259  				u, err := literal.Unquote(s.Value)
   260  				if err != nil {
   261  					break // should not happen: implies invalid JSON
   262  				}
   263  
   264  				// TODO(legacy): remove checking for '_' prefix once hidden
   265  				// fields are removed.
   266  				if !ast.IsValidIdent(u) || strings.HasPrefix(u, "_") {
   267  					break // keep string
   268  				}
   269  
   270  				x.Label = ast.NewIdent(u)
   271  				astutil.CopyMeta(x.Label, s)
   272  			}
   273  			ast.Walk(x.Value, beforeFn, afterFn)
   274  			descent = false
   275  
   276  		case *ast.BasicLit:
   277  			if x.Kind == token.STRING && len(x.Value) > 10 {
   278  				s, err := literal.Unquote(x.Value)
   279  				if err != nil {
   280  					break // should not happen: implies invalid JSON
   281  				}
   282  
   283  				x.Value = literal.String.WithOptionalTabIndent(len(stack)).Quote(s)
   284  			}
   285  		}
   286  
   287  		if stack[len(stack)-1].reflow {
   288  			ast.SetRelPos(n, token.NoRelPos)
   289  		}
   290  		return descent
   291  	}
   292  
   293  	ast.Walk(n, beforeFn, afterFn)
   294  }
   295  
   296  func hasSpaces(n ast.Node) bool {
   297  	return n.Pos().RelPos() > token.NoSpace
   298  }