Skip to content

Commit

Permalink
feat: rfc9535 compatability (#2)
Browse files Browse the repository at this point in the history
* chore: hackweek

* chore: tinker

* chore: cleanup

* chore: add the jsonpath-compliance-test-suite

* chore: implement more stuff

* chore: parse filter expressions

* chore: add functions into tokenizer

* chore: parse filter expressions

* chore: evaluate

* chore: basic evaluation

* chore: make more private

* chore: work through compliance suite

* continuing to tinker

* chore: support basic unions

* chore: basic eval

* chore: standard filters

* chore: filter matches

* chore: more conformance

* chore: more conformance

* chore: more conformance

* chore: slice sanitization

* chore: conformance

* chore: fix slices

* chore: type validation

* chore: fixup functions

* chore: pass conformance test

* chore: clean up tests after conformance test

* chore: remove unnecessary comment

* chore: bridge, service workers

* chore: commentary

* chore: allow superceding messages

* chore: setup defaults

* chore: add wasm to git
  • Loading branch information
ThomasRooney authored Jan 7, 2025
1 parent 69a55f2 commit 2869aa7
Show file tree
Hide file tree
Showing 41 changed files with 7,337 additions and 663 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
dist
dist
*.iml
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "jsonpath-compliance-test-suite"]
path = jsonpath-compliance-test-suite
url = [email protected]:jsonpath-standard/jsonpath-compliance-test-suite
16 changes: 15 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,18 @@ web/src/assets/wasm/lib.wasm: $(SOURCE)
mkdir -p dist
rm -f dist/*
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" web/src/assets/wasm/wasm_exec.js
GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o ./web/src/assets/wasm/lib.wasm cmd/wasm/main.go
GOOS=js GOARCH=wasm go build -o ./web/src/assets/wasm/lib.wasm cmd/wasm/functions.go

.PHONY: tinygo web/src/assets/wasm/lib.tinygo.wasm web/src/assets/wasm/lib.wasm
tinygo:
brew tap tinygo-org/tools
brew install tinygo


web/src/assets/wasm/lib.tinygo.wasm: $(SOURCE)
echo "Not working: requires regex which is not supported by tinygo"
exit 1
mkdir -p dist
rm -f dist/*
cp "${shell brew --prefix tinygo}/targets/wasm_exec.js" web/src/assets/wasm/wasm_exec.js
GOOS=js GOARCH=wasm tinygo build -target=wasm -o ./web/src/assets/wasm/lib.tinygo.wasm cmd/wasm/functions.go
115 changes: 115 additions & 0 deletions cmd/wasm/functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//go:build js && wasm

package main

import (
"fmt"
"github.com/speakeasy-api/jsonpath/pkg/overlay"
"gopkg.in/yaml.v3"
"syscall/js"
)

func CalculateOverlay(originalYAML, targetYAML string) (string, error) {
var orig yaml.Node
err := yaml.Unmarshal([]byte(originalYAML), &orig)
if err != nil {
return "", fmt.Errorf("failed to parse source schema: %w", err)
}
var target yaml.Node
err = yaml.Unmarshal([]byte(targetYAML), &target)
if err != nil {
return "", fmt.Errorf("failed to parse target schema: %w", err)
}

overlay, err := overlay.Compare("example overlay", &orig, target)
if err != nil {
return "", fmt.Errorf("failed to compare schemas: %w", err)
}
out, err := yaml.Marshal(overlay)
if err != nil {
return "", fmt.Errorf("failed to marshal schema: %w", err)
}

return string(out), nil
}

func ApplyOverlay(originalYAML, overlayYAML string) (string, error) {
var orig yaml.Node
err := yaml.Unmarshal([]byte(originalYAML), &orig)
if err != nil {
return "", fmt.Errorf("failed to parse original schema: %w", err)
}

var overlay overlay.Overlay
err = yaml.Unmarshal([]byte(overlayYAML), &overlay)
if err != nil {
return "", fmt.Errorf("failed to parse overlay schema: %w", err)
}

err = overlay.ApplyTo(&orig)
if err != nil {
return "", fmt.Errorf("failed to apply overlay: %w", err)
}

// Unwrap the document node if it exists and has only one content node
if orig.Kind == yaml.DocumentNode && len(orig.Content) == 1 {
orig = *orig.Content[0]
}

out, err := yaml.Marshal(&orig)
if err != nil {
return "", fmt.Errorf("failed to marshal result: %w", err)
}

return string(out), nil
}

func promisify(fn func(args []js.Value) (string, error)) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
// Handler for the Promise
handler := js.FuncOf(func(this js.Value, promiseArgs []js.Value) interface{} {
resolve := promiseArgs[0]
reject := promiseArgs[1]

// Run this code asynchronously
go func() {
result, err := fn(args)
if err != nil {
errorConstructor := js.Global().Get("Error")
errorObject := errorConstructor.New(err.Error())
reject.Invoke(errorObject)
return
}

resolve.Invoke(result)
}()

// The handler of a Promise doesn't return any value
return nil
})

// Create and return the Promise object
promiseConstructor := js.Global().Get("Promise")
return promiseConstructor.New(handler)
})
}

func main() {
js.Global().Set("CalculateOverlay", promisify(func(args []js.Value) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("CalculateOverlay: expected 2 args, got %v", len(args))
}

return CalculateOverlay(args[0].String(), args[1].String())
}))

js.Global().Set("ApplyOverlay", promisify(func(args []js.Value) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("ApplyOverlay: expected 2 args, got %v", len(args))
}

return ApplyOverlay(args[0].String(), args[1].String())
}))

<-make(chan bool)
}
45 changes: 0 additions & 45 deletions cmd/wasm/main.go

This file was deleted.

3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ go 1.21.0
toolchain go1.22.2

require (
github.com/pmezard/go-difflib v1.0.0
github.com/speakeasy-api/openapi-overlay v0.6.0
github.com/stretchr/testify v1.9.0
github.com/vmware-labs/yaml-jsonpath v0.3.2
gopkg.in/yaml.v3 v3.0.1
sigs.k8s.io/yaml v1.4.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
)
5 changes: 4 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
Expand Down Expand Up @@ -132,3 +133,5 @@ gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
144 changes: 144 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package jsonpath_test

import (
"encoding/json"
"github.com/pmezard/go-difflib/difflib"
"github.com/speakeasy-api/jsonpath/pkg/jsonpath"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"os"
"slices"
"strings"
"testing"
)

type FullTestSuite struct {
Description string `json:"description"`
Tests []Test `json:"tests"`
}
type Test struct {
Name string `json:"name"`
Selector string `json:"selector"`
Document interface{} `json:"document"`
Result []interface{} `json:"result"`
Results [][]interface{} `json:"results"`
InvalidSelector bool `json:"invalid_selector"`
Tags []string `json:"tags"`
}

func TestJSONPathComplianceTestSuite(t *testing.T) {
// Read the test suite JSON file
file, err := os.ReadFile("./jsonpath-compliance-test-suite/cts.json")
require.NoError(t, err, "Failed to read test suite file")
// alter the file to delete any unicode tests: these break the yaml library we use..
var testSuite FullTestSuite
json.Unmarshal(file, &testSuite)
for i := 0; i < len(testSuite.Tests); i++ {
// if Tags contains "unicode", delete it
shouldDelete := slices.Contains(testSuite.Tests[i].Tags, "unicode")
// delete new line / some unicode tests -- these break the yaml parser
shouldDelete = shouldDelete || strings.Contains(testSuite.Tests[i].Name, "line feed")
shouldDelete = shouldDelete || strings.Contains(testSuite.Tests[i].Name, "carriage return")
shouldDelete = shouldDelete || strings.Contains(testSuite.Tests[i].Name, "u2028")
shouldDelete = shouldDelete || strings.Contains(testSuite.Tests[i].Name, "u2029")
if shouldDelete {
testSuite.Tests = append(testSuite.Tests[:i], testSuite.Tests[i+1:]...)
i--
}
}

// Run each test case as a subtest
for _, test := range testSuite.Tests {
t.Run(test.Name, func(t *testing.T) {
// Test case for a valid selector
jp, err := jsonpath.NewPath(test.Selector)
if test.InvalidSelector {
require.Error(t, err, "Expected an error for invalid selector, but got none")
return
} else {
require.NoError(t, err, "Failed to parse JSONPath selector")
}
// interface{} to yaml.Node
toYAML := func(i interface{}) *yaml.Node {
o, err := yaml.Marshal(i)
require.NoError(t, err, "Failed to marshal interface to yaml")
n := new(yaml.Node)
err = yaml.Unmarshal(o, n)
require.NoError(t, err, "Failed to unmarshal yaml to yaml.Node")
// unwrap the document node
if n.Kind == yaml.DocumentNode && len(n.Content) == 1 {
n = n.Content[0]
}
return n
}

result := jp.Query(toYAML(test.Document))

if test.Results != nil {
expectedResults := make([][]*yaml.Node, 0)
for _, expectedResult := range test.Results {
expected := make([]*yaml.Node, 0)
for _, expectedResult := range expectedResult {
expected = append(expected, toYAML(expectedResult))
}
expectedResults = append(expectedResults, expected)
}

// Test case with multiple possible results
var found bool
for i, _ := range test.Results {
if match, msg := compareResults(result, expectedResults[i]); match {
found = true
break
} else {
t.Log(msg)
}
}
if !found {
t.Errorf("Unexpected result. Got: %v, Want one of: %v", result, test.Results)
}
} else {
expectedResult := make([]*yaml.Node, 0)
for _, res := range test.Result {
expectedResult = append(expectedResult, toYAML(res))
}
// Test case with a single expected result
if match, msg := compareResults(result, expectedResult); !match {
t.Error(msg)
}
}
})
}
}

func compareResults(actual, expected []*yaml.Node) (bool, string) {
actualStr, err := yaml.Marshal(actual)
if err != nil {
return false, "Failed to serialize actual result: " + err.Error()
}

expectedStr, err := yaml.Marshal(expected)
if err != nil {
return false, "Failed to serialize expected result: " + err.Error()
}

if string(actualStr) == string(expectedStr) {
return true, ""
}

// Use a differ library to generate a nice diff string
// You can use a package like github.com/pmezard/go-difflib/difflib
diff := difflib.UnifiedDiff{
A: difflib.SplitLines(string(expectedStr)),
B: difflib.SplitLines(string(actualStr)),
FromFile: "Expected",
ToFile: "Actual",
Context: 3,
}
diffStr, err := difflib.GetUnifiedDiffString(diff)
if err != nil {
return false, "Failed to generate diff: " + err.Error()
}

return false, diffStr
}
1 change: 1 addition & 0 deletions jsonpath-compliance-test-suite
Loading

0 comments on commit 2869aa7

Please sign in to comment.