Skip to content

Commit

Permalink
Use iter.Seq, added in Go 1.23 (#49)
Browse files Browse the repository at this point in the history
First-class support for custom iterators was added in Go 1.23.
Use that as a mechanism for iterating over Pointer tokens.

Also, update the workflows to test on Go 1.23
and bump the go.mod language version to 1.23.
  • Loading branch information
dsnet authored Aug 15, 2024
1 parent 0599f16 commit ebd3a89
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 80 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.22.x
go-version: 1.23.x
- name: Checkout code
uses: actions/checkout@v4
- name: Test
Expand All @@ -23,7 +23,7 @@ jobs:
test-all:
strategy:
matrix:
go-version: [1.22.x]
go-version: [1.23.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
// This package will regularly experience breaking changes.
module github.com/go-json-experiment/json

go 1.22
go 1.23
23 changes: 0 additions & 23 deletions jsontext/pointer.go

This file was deleted.

42 changes: 0 additions & 42 deletions jsontext/pointer_test.go

This file was deleted.

42 changes: 30 additions & 12 deletions jsontext/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package jsontext

import (
"iter"
"math"
"strconv"
"strings"
Expand Down Expand Up @@ -51,20 +52,37 @@ func (s *state) reset() {

// Pointer is a JSON Pointer (RFC 6901) that references a particular JSON value
// relative to the root of the top-level JSON value.
//
// There is exactly one representation of a pointer to a particular value,
// so comparability of Pointer values is equivalent to checking whether
// they both point to the exact same value.
type Pointer string

// nextToken returns the next token in the pointer, reducing the length of p.
func (p *Pointer) nextToken() (token string) {
*p = Pointer(strings.TrimPrefix(string(*p), "/"))
i := min(uint(strings.IndexByte(string(*p), '/')), uint(len(*p)))
token = string(*p)[:i]
*p = (*p)[i:]
if strings.Contains(token, "~") {
// Per RFC 6901, section 3, unescape '~' and '/' characters.
token = strings.ReplaceAll(token, "~1", "/")
token = strings.ReplaceAll(token, "~0", "~")
}
return token
// Tokens returns an iterator over the reference tokens in the JSON pointer,
// starting from the first token until the last token (unless stopped early).
//
// A token is either a JSON object name or an index to a JSON array element
// encoded as a base-10 integer value.
// It is impossible to distinguish between an array index and an object name
// (that happens to be an base-10 encoded integer) without also knowing
// the structure of the top-level JSON value that the pointer refers to.
func (p Pointer) Tokens() iter.Seq[string] {
return func(yield func(string) bool) {
for len(p) > 0 {
p = Pointer(strings.TrimPrefix(string(p), "/"))
i := min(uint(strings.IndexByte(string(p), '/')), uint(len(p)))
token := string(p)[:i]
p = p[i:]
if strings.Contains(token, "~") {
// Per RFC 6901, section 3, unescape '~' and '/' characters.
token = strings.ReplaceAll(token, "~1", "/")
token = strings.ReplaceAll(token, "~0", "~")
}
if !yield(token) {
return
}
}
}
}

// appendStackPointer appends a JSON Pointer (RFC 6901) to the current value.
Expand Down
22 changes: 22 additions & 0 deletions jsontext/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,32 @@ package jsontext
import (
"fmt"
"reflect"
"slices"
"strings"
"testing"
)

func TestPointerTokens(t *testing.T) {
tests := []struct {
in Pointer
want []string
}{
{in: "", want: nil},
{in: "a", want: []string{"a"}},
{in: "~", want: []string{"~"}},
{in: "/a", want: []string{"a"}},
{in: "/foo/bar", want: []string{"foo", "bar"}},
{in: "///", want: []string{"", "", ""}},
{in: "/~0~1", want: []string{"~/"}},
}
for _, tt := range tests {
got := slices.Collect(tt.in.Tokens())
if !slices.Equal(got, tt.want) {
t.Errorf("Pointer(%q).Tokens = %q, want %q", tt.in, got, tt.want)
}
}
}

func TestStateMachine(t *testing.T) {
// To test a state machine, we pass an ordered sequence of operations and
// check whether the current state is as expected.
Expand Down

0 comments on commit ebd3a89

Please sign in to comment.