1// Copyright 2025 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 "fmt"
19 "iter"
20 "strconv"
21 "strings"
22
23 "cuelang.org/go/cue"
24)
25
26var (
27 jsonPtrEsc = strings.NewReplacer("~", "~0", "/", "~1")
28 jsonPtrUnesc = strings.NewReplacer("~0", "~", "~1", "/")
29)
30
31// Pointer represents a JSON Pointer as defined by RFC 6901.
32// It is a slash-separated list of tokens that reference a specific location
33// within a JSON document.
34// TODO(go1.26) alias this to [encoding/json/jsontext.Pointer]
35type Pointer string
36
37// PointerFromTokens returns a JSON Pointer formed from
38// the unquoted tokens in the given sequence. Any
39// slash (/) or tilde (~) characters will be escaped appropriately.
40func PointerFromTokens(tokens iter.Seq[string]) Pointer {
41 var buf strings.Builder
42 for tok := range tokens {
43 buf.WriteByte('/')
44 buf.WriteString(jsonPtrEsc.Replace(tok))
45 }
46 return Pointer(buf.String())
47}
48
49// Tokens returns a sequence of all the
50// unquoted path elements (tokens) of the JSON Pointer.
51func (p Pointer) Tokens() iter.Seq[string] {
52 s := string(p)
53 return func(yield func(string) bool) {
54 needUnesc := strings.IndexByte(s, '~') >= 0
55 for len(s) > 0 {
56 s = strings.TrimPrefix(s, "/")
57 i := min(uint(strings.IndexByte(s, '/')), uint(len(s)))
58 tok := s[:i]
59 if needUnesc {
60 tok = jsonPtrUnesc.Replace(tok)
61 }
62 if !yield(tok) {
63 return
64 }
65 s = s[i:]
66 }
67 }
68}
69
70// PointerFromCUEPath returns a JSON Pointer equivalent to the
71// given CUE path. It returns an error if the path contains an element
72// that cannot be represented as a JSON Pointer.
73func PointerFromCUEPath(p cue.Path) (Pointer, error) {
74 var err error
75 ptr := PointerFromTokens(func(yield func(s string) bool) {
76 for _, sel := range p.Selectors() {
77 var token string
78 switch sel.Type() {
79 case cue.StringLabel:
80 token = sel.Unquoted()
81 case cue.IndexLabel:
82 token = strconv.Itoa(sel.Index())
83 default:
84 if err == nil {
85 err = fmt.Errorf("cannot convert selector %v to JSON pointer", sel)
86 continue
87 }
88 }
89 if !yield(token) {
90 return
91 }
92 }
93 })
94 if err != nil {
95 return "", err
96 }
97 return ptr, nil
98}