1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
// Copyright 2025 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package json
import (
"fmt"
"iter"
"strconv"
"strings"
"cuelang.org/go/cue"
)
var (
jsonPtrEsc = strings.NewReplacer("~", "~0", "/", "~1")
jsonPtrUnesc = strings.NewReplacer("~0", "~", "~1", "/")
)
// Pointer represents a JSON Pointer as defined by RFC 6901.
// It is a slash-separated list of tokens that reference a specific location
// within a JSON document.
// TODO(go1.26) alias this to [encoding/json/jsontext.Pointer]
type Pointer string
// PointerFromTokens returns a JSON Pointer formed from
// the unquoted tokens in the given sequence. Any
// slash (/) or tilde (~) characters will be escaped appropriately.
func PointerFromTokens(tokens iter.Seq[string]) Pointer {
var buf strings.Builder
for tok := range tokens {
buf.WriteByte('/')
buf.WriteString(jsonPtrEsc.Replace(tok))
}
return Pointer(buf.String())
}
// Tokens returns a sequence of all the
// unquoted path elements (tokens) of the JSON Pointer.
func (p Pointer) Tokens() iter.Seq[string] {
s := string(p)
return func(yield func(string) bool) {
needUnesc := strings.IndexByte(s, '~') >= 0
for len(s) > 0 {
s = strings.TrimPrefix(s, "/")
i := min(uint(strings.IndexByte(s, '/')), uint(len(s)))
tok := s[:i]
if needUnesc {
tok = jsonPtrUnesc.Replace(tok)
}
if !yield(tok) {
return
}
s = s[i:]
}
}
}
// PointerFromCUEPath returns a JSON Pointer equivalent to the
// given CUE path. It returns an error if the path contains an element
// that cannot be represented as a JSON Pointer.
func PointerFromCUEPath(p cue.Path) (Pointer, error) {
var err error
ptr := PointerFromTokens(func(yield func(s string) bool) {
for _, sel := range p.Selectors() {
var token string
switch sel.Type() {
case cue.StringLabel:
token = sel.Unquoted()
case cue.IndexLabel:
token = strconv.Itoa(sel.Index())
default:
if err == nil {
err = fmt.Errorf("cannot convert selector %v to JSON pointer", sel)
continue
}
}
if !yield(token) {
return
}
}
})
if err != nil {
return "", err
}
return ptr, nil
}
|