1// Copyright 2018 The 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
15package json
16
17import (
18 "bytes"
19 "encoding/json"
20 "fmt"
21 "io"
22 "strings"
23
24 "cuelang.org/go/cue"
25 "cuelang.org/go/cue/ast"
26 "cuelang.org/go/cue/errors"
27 "cuelang.org/go/cue/parser"
28 "cuelang.org/go/cue/token"
29 cuejson "cuelang.org/go/encoding/json"
30 "cuelang.org/go/internal/core/adt"
31 internaljson "cuelang.org/go/internal/encoding/json"
32 "cuelang.org/go/internal/pkg"
33 "cuelang.org/go/internal/value"
34)
35
36// Compact generates the JSON-encoded src with insignificant space characters
37// elided.
38func Compact(src []byte) (string, error) {
39 dst := bytes.Buffer{}
40 if err := json.Compact(&dst, src); err != nil {
41 return "", err
42 }
43 return dst.String(), nil
44}
45
46// Indent creates an indented form of the JSON-encoded src.
47// Each element in a JSON object or array begins on a new,
48// indented line beginning with prefix followed by one or more
49// copies of indent according to the indentation nesting.
50// The data appended to dst does not begin with the prefix nor
51// any indentation, to make it easier to embed inside other formatted JSON data.
52// Although leading space characters (space, tab, carriage return, newline)
53// at the beginning of src are dropped, trailing space characters
54// at the end of src are preserved and copied to dst.
55// For example, if src has no trailing spaces, neither will dst;
56// if src ends in a trailing newline, so will dst.
57func Indent(src []byte, prefix, indent string) (string, error) {
58 dst := bytes.Buffer{}
59 if err := json.Indent(&dst, src, prefix, indent); err != nil {
60 return "", err
61 }
62 return dst.String(), nil
63}
64
65// HTMLEscape returns the JSON-encoded src with <, >, &, U+2028 and
66// U+2029 characters inside string literals changed to \u003c, \u003e, \u0026,
67// \u2028, \u2029 so that the JSON will be safe to embed inside HTML <script>
68// tags. For historical reasons, web browsers don't honor standard HTML escaping
69// within <script> tags, so an alternative JSON encoding must be used.
70func HTMLEscape(src []byte) string {
71 dst := &bytes.Buffer{}
72 json.HTMLEscape(dst, src)
73 return dst.String()
74}
75
76// Marshal returns the JSON encoding of v.
77func Marshal(v cue.Value) (string, error) {
78 b, err := internaljson.Marshal(v)
79 return string(b), err
80}
81
82// MarshalStream turns a list into a stream of JSON objects.
83func MarshalStream(v cue.Value) (string, error) {
84 // TODO: return an io.Reader and allow asynchronous processing.
85 iter, err := v.List()
86 if err != nil {
87 return "", err
88 }
89 var b strings.Builder
90 for iter.Next() {
91 p, err := internaljson.Marshal(iter.Value())
92 if err != nil {
93 return "", err
94 }
95 b.Write(p)
96 b.WriteByte('\n')
97 }
98 return b.String(), nil
99}
100
101// UnmarshalStream parses the JSON to a CUE instance.
102func UnmarshalStream(data []byte) (ast.Expr, error) {
103 d := cuejson.NewDecoder(nil, "", bytes.NewReader(data))
104
105 a := []ast.Expr{}
106 for {
107 x, err := d.Extract()
108 if err == io.EOF {
109 break
110 }
111 if err != nil {
112 return nil, err
113 }
114 a = append(a, x)
115 }
116
117 return ast.NewList(a...), nil
118}
119
120// Unmarshal parses the JSON-encoded data.
121func Unmarshal(b []byte) (ast.Expr, error) {
122 if !json.Valid(b) {
123 return nil, fmt.Errorf("json: invalid JSON")
124 }
125 expr, err := parser.ParseExpr("json", b)
126 if err != nil {
127 // NOTE: should never happen.
128 return nil, errors.Wrapf(err, token.NoPos, "json: could not parse JSON")
129 }
130 return expr, nil
131}
132
133// Validate validates JSON and confirms it matches the constraints
134// specified by v.
135func Validate(b []byte, v pkg.Schema) (bool, error) {
136 c := value.OpContext(v)
137 return validate(c, b, v)
138}
139
140// validate is the actual implementation of Validate.
141func validate(c *adt.OpContext, b []byte, v pkg.Schema) (bool, error) {
142 if !json.Valid(b) {
143 return false, fmt.Errorf("json: invalid JSON")
144 }
145 v2 := v.Context().CompileBytes(b, cue.Filename("json.Validate"))
146 if err := v2.Err(); err != nil {
147 return false, err
148 }
149
150 vx := adt.Unify(c, value.Vertex(v2), value.Vertex(v))
151 v = value.Make(c, vx)
152 if err := v.Err(); err != nil {
153 return false, err
154 }
155
156 if err := v.Validate(cue.Final()); err != nil {
157 return false, err
158 }
159
160 return true, nil
161}