diff --git a/cmd/wasm/functions.go b/cmd/wasm/functions.go index 81cf9de..c165f45 100644 --- a/cmd/wasm/functions.go +++ b/cmd/wasm/functions.go @@ -5,13 +5,14 @@ package main import ( "encoding/json" "fmt" + "reflect" + "syscall/js" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath" "github.com/speakeasy-api/jsonpath/pkg/jsonpath/config" "github.com/speakeasy-api/jsonpath/pkg/jsonpath/token" - "github.com/speakeasy-api/jsonpath/pkg/overlay" + "github.com/speakeasy-api/openapi/overlay" "gopkg.in/yaml.v3" - "reflect" - "syscall/js" ) func CalculateOverlay(originalYAML, targetYAML, existingOverlay string) (string, error) { diff --git a/go.mod b/go.mod index 8ffd79e..662f435 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,17 @@ module github.com/speakeasy-api/jsonpath -go 1.22 +go 1.24.3 require ( - github.com/pmezard/go-difflib v1.0.0 - github.com/stretchr/testify v1.9.0 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 + github.com/speakeasy-api/openapi v1.16.2 + github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.1.0 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect ) diff --git a/go.sum b/go.sum index d85da3a..5f39949 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,65 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 h1:aRd8M7HJVZOqn/vhOzrGcQH0lNAMkqMn+pXUYkatmcA= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/openapi v1.16.2 h1:ntN57Z4isk3jfckYf7L957/2JtZazpfm8qg76pPlpR0= +github.com/speakeasy-api/openapi v1.16.2/go.mod h1:aiVj+JnirrwZDtKegt0hQrj/ixl3v17EkN2YGnTuSro= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= diff --git a/pkg/overlay/apply.go b/pkg/overlay/apply.go deleted file mode 100644 index 005ee95..0000000 --- a/pkg/overlay/apply.go +++ /dev/null @@ -1,169 +0,0 @@ -package overlay - -import ( - "github.com/speakeasy-api/jsonpath/pkg/jsonpath" - "github.com/speakeasy-api/jsonpath/pkg/jsonpath/config" - "gopkg.in/yaml.v3" -) - -// ApplyTo will take an overlay and apply its changes to the given YAML -// document. -func (o *Overlay) ApplyTo(root *yaml.Node) error { - for _, action := range o.Actions { - var err error - if action.Remove { - err = applyRemoveAction(root, action) - } else { - err = applyUpdateAction(root, action) - } - - if err != nil { - return err - } - } - - return nil -} - -func applyRemoveAction(root *yaml.Node, action Action) error { - if action.Target == "" { - return nil - } - - idx := newParentIndex(root) - - p, err := jsonpath.NewPath(action.Target, config.WithPropertyNameExtension()) - if err != nil { - return err - } - - nodes := p.Query(root) - if err != nil { - return err - } - - for _, node := range nodes { - removeNode(idx, node) - } - - return nil -} - -func removeNode(idx parentIndex, node *yaml.Node) { - parent := idx.getParent(node) - if parent == nil { - return - } - - for i, child := range parent.Content { - if child == node { - switch parent.Kind { - case yaml.MappingNode: - if i%2 == 1 { - // if we select a value, we should delete the key too - parent.Content = append(parent.Content[:i-1], parent.Content[i+1:]...) - } else { - // if we select a key, we should delete the value - parent.Content = append(parent.Content[:i], parent.Content[i+2:]...) - } - return - case yaml.SequenceNode: - parent.Content = append(parent.Content[:i], parent.Content[i+1:]...) - return - } - } - } -} - -func applyUpdateAction(root *yaml.Node, action Action) error { - if action.Target == "" { - return nil - } - - if action.Update.IsZero() { - return nil - } - - p, err := jsonpath.NewPath(action.Target, config.WithPropertyNameExtension()) - if err != nil { - return err - } - - nodes := p.Query(root) - - for _, node := range nodes { - if err := updateNode(node, &action.Update); err != nil { - return err - } - } - - return nil -} - -func updateNode(node *yaml.Node, updateNode *yaml.Node) error { - mergeNode(node, updateNode) - return nil -} - -func mergeNode(node *yaml.Node, merge *yaml.Node) { - if node.Kind != merge.Kind { - *node = *clone(merge) - return - } - switch node.Kind { - default: - node.Value = merge.Value - case yaml.MappingNode: - mergeMappingNode(node, merge) - case yaml.SequenceNode: - mergeSequenceNode(node, merge) - } -} - -// mergeMappingNode will perform a shallow merge of the merge node into the main -// node. -func mergeMappingNode(node *yaml.Node, merge *yaml.Node) { -NextKey: - for i := 0; i < len(merge.Content); i += 2 { - mergeKey := merge.Content[i].Value - mergeValue := merge.Content[i+1] - - for j := 0; j < len(node.Content); j += 2 { - nodeKey := node.Content[j].Value - if nodeKey == mergeKey { - mergeNode(node.Content[j+1], mergeValue) - continue NextKey - } - } - - node.Content = append(node.Content, merge.Content[i], clone(mergeValue)) - } -} - -// mergeSequenceNode will append the merge node's content to the original node. -func mergeSequenceNode(node *yaml.Node, merge *yaml.Node) { - node.Content = append(node.Content, clone(merge).Content...) -} - -func clone(node *yaml.Node) *yaml.Node { - newNode := &yaml.Node{ - Kind: node.Kind, - Style: node.Style, - Tag: node.Tag, - Value: node.Value, - Anchor: node.Anchor, - HeadComment: node.HeadComment, - LineComment: node.LineComment, - FootComment: node.FootComment, - } - if node.Alias != nil { - newNode.Alias = clone(node.Alias) - } - if node.Content != nil { - newNode.Content = make([]*yaml.Node, len(node.Content)) - for i, child := range node.Content { - newNode.Content[i] = clone(child) - } - } - return newNode -} diff --git a/pkg/overlay/apply_test.go b/pkg/overlay/apply_test.go deleted file mode 100644 index 7f6fcf8..0000000 --- a/pkg/overlay/apply_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package overlay_test - -import ( - "bytes" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" - "os" - "testing" -) - -// NodeMatchesFile is a test that marshals the YAML file from the given node, -// then compares those bytes to those found in the expected file. -func NodeMatchesFile( - t *testing.T, - actual *yaml.Node, - expectedFile string, - msgAndArgs ...any, -) { - variadoc := func(pre ...any) []any { return append(msgAndArgs, pre...) } - - var actualBuf bytes.Buffer - enc := yaml.NewEncoder(&actualBuf) - enc.SetIndent(2) - err := enc.Encode(actual) - require.NoError(t, err, variadoc("failed to marshal node: ")...) - - expectedBytes, err := os.ReadFile(expectedFile) - require.NoError(t, err, variadoc("failed to read expected file: ")...) - - // lazy redo snapshot - //os.WriteFile(expectedFile, actualBuf.Bytes(), 0644) - - //t.Log("### EXPECT START ###\n" + string(expectedBytes) + "\n### EXPECT END ###\n") - //t.Log("### ACTUAL START ###\n" + actualBuf.string() + "\n### ACTUAL END ###\n") - - assert.Equal(t, string(expectedBytes), actualBuf.String(), variadoc("node does not match expected file: ")...) -} - -func TestApplyTo(t *testing.T) { - t.Parallel() - - node, err := LoadSpecification("testdata/openapi.yaml") - require.NoError(t, err) - - o, err := LoadOverlay("testdata/overlay.yaml") - require.NoError(t, err) - - err = o.ApplyTo(node) - assert.NoError(t, err) - - NodeMatchesFile(t, node, "testdata/openapi-overlayed.yaml") -} diff --git a/pkg/overlay/compare.go b/pkg/overlay/compare.go deleted file mode 100644 index 1aa8d2f..0000000 --- a/pkg/overlay/compare.go +++ /dev/null @@ -1,260 +0,0 @@ -package overlay - -import ( - "bytes" - "fmt" - "log" - "strings" - - "gopkg.in/yaml.v3" -) - -// Compare compares input specifications from two files and returns an overlay -// that will convert the first into the second. -func Compare(title string, y1 *yaml.Node, y2 yaml.Node) (*Overlay, error) { - actions, err := walkTreesAndCollectActions(simplePath{}, y1, y2) - if err != nil { - return nil, err - } - - return &Overlay{ - Version: "1.0.0", - Info: Info{ - Title: title, - Version: "0.0.0", - }, - Actions: actions, - }, nil -} - -type simplePart struct { - isKey bool - key string - index int -} - -func intPart(index int) simplePart { - return simplePart{ - index: index, - } -} - -func keyPart(key string) simplePart { - return simplePart{ - isKey: true, - key: key, - } -} - -func (p simplePart) String() string { - if p.isKey { - return fmt.Sprintf("[%q]", p.key) - } - return fmt.Sprintf("[%d]", p.index) -} - -func (p simplePart) KeyString() string { - if p.isKey { - return p.key - } - panic("FIXME: Bug detected in overlay comparison algorithm: attempt to use non key part as key") -} - -type simplePath []simplePart - -func (p simplePath) WithIndex(index int) simplePath { - return append(p, intPart(index)) -} - -func (p simplePath) WithKey(key string) simplePath { - return append(p, keyPart(key)) -} - -func (p simplePath) ToJSONPath() string { - out := &strings.Builder{} - out.WriteString("$") - for _, part := range p { - out.WriteString(part.String()) - } - return out.String() -} - -func (p simplePath) Dir() simplePath { - return p[:len(p)-1] -} - -func (p simplePath) Base() simplePart { - return p[len(p)-1] -} - -func walkTreesAndCollectActions(path simplePath, y1 *yaml.Node, y2 yaml.Node) ([]Action, error) { - if y1 == nil { - return []Action{{ - Target: path.Dir().ToJSONPath(), - Update: y2, - }}, nil - } - - if y2.IsZero() { - return []Action{{ - Target: path.ToJSONPath(), - Remove: true, - }}, nil - } - if y1.Kind != y2.Kind { - return []Action{{ - Target: path.ToJSONPath(), - Update: y2, - }}, nil - } - - switch y1.Kind { - case yaml.DocumentNode: - return walkTreesAndCollectActions(path, y1.Content[0], *y2.Content[0]) - case yaml.SequenceNode: - if len(y2.Content) == len(y1.Content) { - return walkSequenceNode(path, y1, y2) - } - - if len(y2.Content) == len(y1.Content)+1 && - yamlEquals(y2.Content[:len(y1.Content)], y1.Content) { - return []Action{{ - Target: path.ToJSONPath(), - Update: yaml.Node{ - Kind: y1.Kind, - Content: []*yaml.Node{y2.Content[len(y1.Content)]}, - }, - }}, nil - } - - return []Action{{ - Target: path.ToJSONPath() + "[*]", // target all elements - Remove: true, - }, { - Target: path.ToJSONPath(), - Update: yaml.Node{ - Kind: y1.Kind, - Content: y2.Content, - }, - }}, nil - case yaml.MappingNode: - return walkMappingNode(path, y1, y2) - case yaml.ScalarNode: - if y1.Value != y2.Value { - return []Action{{ - Target: path.ToJSONPath(), - Update: y2, - }}, nil - } - case yaml.AliasNode: - log.Println("YAML alias nodes are not yet supported for compare.") - } - return nil, nil -} - -func yamlEquals(nodes []*yaml.Node, content []*yaml.Node) bool { - for i := range nodes { - bufA := &bytes.Buffer{} - bufB := &bytes.Buffer{} - decodeA := yaml.NewEncoder(bufA) - decodeB := yaml.NewEncoder(bufB) - err := decodeA.Encode(nodes[i]) - if err != nil { - return false - } - err = decodeB.Encode(content[i]) - if err != nil { - return false - } - - if bufA.String() != bufB.String() { - return false - } - } - return true -} - -func walkSequenceNode(path simplePath, y1 *yaml.Node, y2 yaml.Node) ([]Action, error) { - nodeLen := max(len(y1.Content), len(y2.Content)) - var actions []Action - for i := 0; i < nodeLen; i++ { - var c1, c2 *yaml.Node - if i < len(y1.Content) { - c1 = y1.Content[i] - } - if i < len(y2.Content) { - c2 = y2.Content[i] - } - - newActions, err := walkTreesAndCollectActions( - path.WithIndex(i), - c1, *c2) - if err != nil { - return nil, err - } - - actions = append(actions, newActions...) - } - - return actions, nil -} - -func walkMappingNode(path simplePath, y1 *yaml.Node, y2 yaml.Node) ([]Action, error) { - var actions []Action - foundKeys := map[string]struct{}{} - - // Add or update keys in y2 that differ/missing from y1 -Outer: - for i := 0; i < len(y2.Content); i += 2 { - k2 := y2.Content[i] - v2 := y2.Content[i+1] - - foundKeys[k2.Value] = struct{}{} - - // find keys in y1 to update - for j := 0; j < len(y1.Content); j += 2 { - k1 := y1.Content[j] - v1 := y1.Content[j+1] - - if k1.Value == k2.Value { - newActions, err := walkTreesAndCollectActions( - path.WithKey(k2.Value), - v1, *v2) - if err != nil { - return nil, err - } - actions = append(actions, newActions...) - continue Outer - } - } - - // key not found in y1, so add it - newActions, err := walkTreesAndCollectActions( - path.WithKey(k2.Value), - nil, yaml.Node{ - Kind: y1.Kind, - Content: []*yaml.Node{k2, v2}, - }) - if err != nil { - return nil, err - } - - actions = append(actions, newActions...) - } - - // look for keys in y1 that are not in y2: remove them - for i := 0; i < len(y1.Content); i += 2 { - k1 := y1.Content[i] - - if _, alreadySeen := foundKeys[k1.Value]; alreadySeen { - continue - } - - actions = append(actions, Action{ - Target: path.WithKey(k1.Value).ToJSONPath(), - Remove: true, - }) - } - - return actions, nil -} diff --git a/pkg/overlay/compare_test.go b/pkg/overlay/compare_test.go deleted file mode 100644 index fbf6a39..0000000 --- a/pkg/overlay/compare_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package overlay_test - -import ( - "fmt" - "github.com/speakeasy-api/jsonpath/pkg/overlay" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" - "os" - "testing" -) - -func LoadSpecification(path string) (*yaml.Node, error) { - rs, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("failed to open schema from path %q: %w", path, err) - } - - var ys yaml.Node - err = yaml.NewDecoder(rs).Decode(&ys) - if err != nil { - return nil, fmt.Errorf("failed to parse schema at path %q: %w", path, err) - } - - return &ys, nil -} - -func LoadOverlay(path string) (*overlay.Overlay, error) { - o, err := overlay.Parse(path) - if err != nil { - return nil, fmt.Errorf("failed to parse overlay from path %q: %w", path, err) - } - - return o, nil -} - -func TestCompare(t *testing.T) { - t.Parallel() - - node, err := LoadSpecification("testdata/openapi.yaml") - require.NoError(t, err) - node2, err := LoadSpecification("testdata/openapi-overlayed.yaml") - require.NoError(t, err) - - o, err := LoadOverlay("testdata/overlay-generated.yaml") - require.NoError(t, err) - - o2, err := overlay.Compare("Drinks Overlay", node, *node2) - assert.NoError(t, err) - - o1s, err := o.ToString() - assert.NoError(t, err) - o2s, err := o2.ToString() - assert.NoError(t, err) - - // Uncomment this if we've improved the output - //os.WriteFile("testdata/overlay-generated.yaml", []byte(o2s), 0644) - assert.Equal(t, o1s, o2s) - - // round trip it - err = o.ApplyTo(node) - assert.NoError(t, err) - NodeMatchesFile(t, node, "testdata/openapi-overlayed.yaml") - -} diff --git a/pkg/overlay/parents.go b/pkg/overlay/parents.go deleted file mode 100644 index a2b4942..0000000 --- a/pkg/overlay/parents.go +++ /dev/null @@ -1,23 +0,0 @@ -package overlay - -import "gopkg.in/yaml.v3" - -type parentIndex map[*yaml.Node]*yaml.Node - -// newParentIndex returns a new parentIndex, populated for the given root node. -func newParentIndex(root *yaml.Node) parentIndex { - index := parentIndex{} - index.indexNodeRecursively(root) - return index -} - -func (index parentIndex) indexNodeRecursively(parent *yaml.Node) { - for _, child := range parent.Content { - index[child] = parent - index.indexNodeRecursively(child) - } -} - -func (index parentIndex) getParent(child *yaml.Node) *yaml.Node { - return index[child] -} diff --git a/pkg/overlay/parse.go b/pkg/overlay/parse.go deleted file mode 100644 index 0708846..0000000 --- a/pkg/overlay/parse.go +++ /dev/null @@ -1,58 +0,0 @@ -package overlay - -import ( - "fmt" - "gopkg.in/yaml.v3" - "io" - "os" - "path/filepath" -) - -// Parse will parse the given reader as an overlay file. -func Parse(path string) (*Overlay, error) { - filePath, err := filepath.Abs(path) - if err != nil { - return nil, fmt.Errorf("failed to get absolute path for %q: %w", path, err) - } - - ro, err := os.Open(filePath) - if err != nil { - return nil, fmt.Errorf("failed to open overlay file at path %q: %w", path, err) - } - defer ro.Close() - - var overlay Overlay - dec := yaml.NewDecoder(ro) - - err = dec.Decode(&overlay) - if err != nil { - return nil, err - } - - return &overlay, err -} - -// Format will validate reformat the given file -func Format(path string) error { - overlay, err := Parse(path) - if err != nil { - return err - } - filePath, err := filepath.Abs(path) - if err != nil { - return fmt.Errorf("failed to open overlay file at path %q: %w", path, err) - } - formatted, err := overlay.ToString() - if err != nil { - return err - } - - return os.WriteFile(filePath, []byte(formatted), 0644) -} - -// Format writes the file back out as YAML. -func (o *Overlay) Format(w io.Writer) error { - enc := yaml.NewEncoder(w) - enc.SetIndent(2) - return enc.Encode(o) -} diff --git a/pkg/overlay/parse_test.go b/pkg/overlay/parse_test.go deleted file mode 100644 index 077157e..0000000 --- a/pkg/overlay/parse_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package overlay_test - -import ( - "github.com/speakeasy-api/jsonpath/pkg/overlay" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "os" - "testing" -) - -var expectOverlay = &overlay.Overlay{ - Extensions: map[string]any{ - "x-top-level-extension": true, - }, - Version: "1.0.0", - Info: overlay.Info{ - Extensions: map[string]any{ - "x-info-extension": 42, - }, - Title: "Drinks Overlay", - Version: "1.2.3", - }, - Extends: "https://raw.githubusercontent.com/speakeasy-sdks/template-sdk/main/openapi.yaml", - Actions: []overlay.Action{ - { - Extensions: map[string]any{ - "x-action-extension": "foo", - }, - Target: `$.paths["/drink/{name}"].get`, - Description: "Test update", - }, - { - Extensions: map[string]any{ - "x-action-extension": "bar", - }, - Target: `$.paths["/drinks"].get`, - Description: "Test remove", - Remove: true, - }, - { - Target: "$.paths[\"/drinks\"]", - }, - { - Target: "$.tags", - }, - }, -} - -func TestParse(t *testing.T) { - err := overlay.Format("testdata/overlay.yaml") - require.NoError(t, err) - o, err := overlay.Parse("testdata/overlay.yaml") - assert.NoError(t, err) - assert.NotNil(t, o) - expect, err := os.ReadFile("testdata/overlay.yaml") - assert.NoError(t, err) - - actual, err := o.ToString() - assert.NoError(t, err) - assert.Equal(t, string(expect), actual) - -} diff --git a/pkg/overlay/schema.go b/pkg/overlay/schema.go deleted file mode 100644 index ae1ea3e..0000000 --- a/pkg/overlay/schema.go +++ /dev/null @@ -1,66 +0,0 @@ -package overlay - -import ( - "bytes" - "gopkg.in/yaml.v3" -) - -// Extensible provides a place for extensions to be added to components of the -// Overlay configuration. These are a map from x-* extension fields to their values. -type Extensions map[string]any - -// Overlay is the top-level configuration for an OpenAPI overlay. -type Overlay struct { - Extensions Extensions `yaml:",inline"` - - // Version is the version of the overlay configuration. - Version string `yaml:"overlay"` - - // JSONPathVersion should be set to rfc9535, and is used for backwards compatability purposes - JSONPathVersion string `yaml:"x-speakeasy-jsonpath,omitempty"` - - // Info describes the metadata for the overlay. - Info Info `yaml:"info"` - - // Extends is a URL to the OpenAPI specification this overlay applies to. - Extends string `yaml:"extends,omitempty"` - - // Actions is the list of actions to perform to apply the overlay. - Actions []Action `yaml:"actions"` -} - -func (o *Overlay) ToString() (string, error) { - buf := bytes.NewBuffer([]byte{}) - decoder := yaml.NewEncoder(buf) - decoder.SetIndent(2) - err := decoder.Encode(o) - return buf.String(), err -} - -// Info describes the metadata for the overlay. -type Info struct { - Extensions `yaml:"-,inline"` - - // Title is the title of the overlay. - Title string `yaml:"title"` - - // Version is the version of the overlay. - Version string `yaml:"version"` -} - -type Action struct { - Extensions `yaml:"-,inline"` - - // Target is the JSONPath to the target of the action. - Target string `yaml:"target"` - - // Description is a description of the action. - Description string `yaml:"description,omitempty"` - - // Update is the sub-document to use to merge or replace in the target. This is - // ignored if Remove is set. - Update yaml.Node `yaml:"update,omitempty"` - - // Remove marks the target node for removal rather than update. - Remove bool `yaml:"remove,omitempty"` -} diff --git a/pkg/overlay/testdata/openapi-overlayed.yaml b/pkg/overlay/testdata/openapi-overlayed.yaml deleted file mode 100644 index de439ec..0000000 --- a/pkg/overlay/testdata/openapi-overlayed.yaml +++ /dev/null @@ -1,411 +0,0 @@ -openapi: 3.1.0 -info: - title: The Speakeasy Bar - version: 1.0.0 - summary: A bar that serves drinks. - description: A secret underground bar that serves drinks to those in the know. - contact: - name: Speakeasy Support - url: https://support.speakeasy.bar - email: support@speakeasy.bar - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0.html - termsOfService: https://speakeasy.bar/terms -externalDocs: - url: https://docs.speakeasy.bar - description: The Speakeasy Bar Documentation. -servers: - - url: https://speakeasy.bar - description: The production server. - x-speakeasy-server-id: prod - - url: https://staging.speakeasy.bar - description: The staging server. - x-speakeasy-server-id: staging - - url: https://{organization}.{environment}.speakeasy.bar - description: A per-organization and per-environment API. - x-speakeasy-server-id: customer - variables: - organization: - description: The organization name. Defaults to a generic organization. - default: api - environment: - description: The environment name. Defaults to the production environment. - default: prod - enum: - - prod - - staging - - dev -security: - - apiKey: [] -tags: - - name: drinks - description: The drinks endpoints. - - name: ingredients - description: The ingredients endpoints. - - name: orders - description: The orders endpoints. - - name: authentication - description: The authentication endpoints. - - name: config - - name: Testing - description: just a description -paths: - x-speakeasy-errors: - statusCodes: # Defines status codes to handle as errors for all operations - - 4XX # Wildcard to handle all status codes in the 400-499 range - - 5XX - /anything/selectGlobalServer: - x-my-ignore: - servers: - - url: http://localhost:35123 - description: The default server. - get: - operationId: selectGlobalServer - responses: - "200": - description: OK - headers: - X-Optional-Header: - schema: - type: string - x-drop: true - /authenticate: - post: - operationId: authenticate - summary: Authenticate with the API by providing a username and password. - security: [] - tags: - - dont-add-x-drop-false - - authentication - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - username: - type: string - password: - type: string - responses: - "200": - description: The api key to use for authenticated endpoints. - content: - application/json: - schema: - type: object - properties: - token: - type: string - "401": - description: Invalid credentials provided. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - x-drop: false - /drinks: - x-speakeasy-note: - "$ref": "./removeNote.yaml" - /drink/{name}: {} - /ingredients: - get: - operationId: listIngredients - summary: Get a list of ingredients. - description: Get a list of ingredients, if authenticated this will include stock levels and product codes otherwise it will only include public information. - tags: - - ingredients - parameters: - - name: ingredients - in: query - description: A list of ingredients to filter by. If not provided all ingredients will be returned. - required: false - style: form - explode: false - schema: - type: array - items: - type: string - responses: - "200": - description: A list of ingredients. - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Ingredient" - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - x-drop: true - /order: - post: - operationId: createOrder - summary: Create an order. - description: Create an order for a drink. - tags: - - orders - parameters: - - name: callback_url - in: query - description: The url to call when the order is updated. - required: false - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Order" - responses: - "200": - description: The order was created successfully. - content: - application/json: - schema: - $ref: "#/components/schemas/Order" - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - callbacks: - orderUpdate: - "{$request.query.callback_url}": - post: - summary: Receive order updates. - description: Receive order updates from the supplier, this will be called whenever the status of an order changes. - tags: - - orders - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - order: - $ref: "#/components/schemas/Order" - responses: - "200": - description: The order update was received successfully. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - x-drop: true - /webhooks/subscribe: - post: - operationId: subscribeToWebhooks - summary: Subscribe to webhooks. - description: Subscribe to webhooks. - tags: - - config - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - type: object - properties: - url: - type: string - webhook: - type: string - enum: - - stockUpdate - responses: - "200": - description: The webhook was subscribed to successfully. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - x-drop: true -webhooks: - stockUpdate: - post: - summary: Receive stock updates. - description: Receive stock updates from the bar, this will be called whenever the stock levels of a drink or ingredient changes. - tags: - - drinks - - ingredients - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - drink: - $ref: "#/components/schemas/Drink" - ingredient: - $ref: "#/components/schemas/Ingredient" - responses: - "200": - description: The stock update was received successfully. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" -components: - schemas: - APIError: - type: object - properties: - code: - type: string - message: - type: string - details: - type: object - additionalProperties: true - Error: - type: object - properties: - code: - type: string - message: - type: string - Drink: - type: object - properties: - name: - description: The name of the drink. - type: string - examples: - - Old Fashioned - - Manhattan - - Negroni - type: - $ref: "#/components/schemas/DrinkType" - price: - description: The price of one unit of the drink in US cents. - type: number - examples: - - 1000 # $10.00 - - 1200 # $12.00 - - 1500 # $15.00 - stock: - description: The number of units of the drink in stock, only available when authenticated. - type: integer - readOnly: true - productCode: - description: The product code of the drink, only available when authenticated. - type: string - examples: - - "AC-A2DF3" - - "NAC-3F2D1" - - "APM-1F2D3" - required: - - name - - price - DrinkType: - description: The type of drink. - type: string - enum: - - cocktail - - non-alcoholic - - beer - - wine - - spirit - - other - Ingredient: - type: object - properties: - name: - description: The name of the ingredient. - type: string - examples: - - Sugar Syrup - - Angostura Bitters - - Orange Peel - type: - $ref: "#/components/schemas/IngredientType" - stock: - description: The number of units of the ingredient in stock, only available when authenticated. - type: integer - examples: - - 10 - - 5 - - 0 - readOnly: true - productCode: - description: The product code of the ingredient, only available when authenticated. - type: string - examples: - - "AC-A2DF3" - - "NAC-3F2D1" - - "APM-1F2D3" - required: - - name - - type - IngredientType: - description: The type of ingredient. - type: string - enum: - - fresh - - long-life - - packaged - Order: - description: An order for a drink or ingredient. - type: object - properties: - type: - $ref: "#/components/schemas/OrderType" - productCode: - description: The product code of the drink or ingredient. - type: string - examples: - - "AC-A2DF3" - - "NAC-3F2D1" - - "APM-1F2D3" - quantity: - description: The number of units of the drink or ingredient to order. - type: integer - minimum: 1 - status: - description: The status of the order. - type: string - enum: - - pending - - processing - - complete - readOnly: true - required: - - type - - productCode - - quantity - - status - OrderType: - description: The type of order. - type: string - enum: - - drink - - ingredient - securitySchemes: - apiKey: - type: apiKey - name: Authorization - in: header - responses: - APIError: - description: An error occurred interacting with the API. - content: - application/json: - schema: - $ref: "#/components/schemas/APIError" - UnknownError: - description: An unknown error occurred interacting with the API. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" diff --git a/pkg/overlay/testdata/openapi.yaml b/pkg/overlay/testdata/openapi.yaml deleted file mode 100644 index 961aa0c..0000000 --- a/pkg/overlay/testdata/openapi.yaml +++ /dev/null @@ -1,456 +0,0 @@ -openapi: 3.1.0 -info: - title: The Speakeasy Bar - version: 1.0.0 - summary: A bar that serves drinks. - description: A secret underground bar that serves drinks to those in the know. - contact: - name: Speakeasy Support - url: https://support.speakeasy.bar - email: support@speakeasy.bar - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0.html - termsOfService: https://speakeasy.bar/terms -externalDocs: - url: https://docs.speakeasy.bar - description: The Speakeasy Bar Documentation. -servers: - - url: https://speakeasy.bar - description: The production server. - x-speakeasy-server-id: prod - - url: https://staging.speakeasy.bar - description: The staging server. - x-speakeasy-server-id: staging - - url: https://{organization}.{environment}.speakeasy.bar - description: A per-organization and per-environment API. - x-speakeasy-server-id: customer - variables: - organization: - description: The organization name. Defaults to a generic organization. - default: api - environment: - description: The environment name. Defaults to the production environment. - default: prod - enum: - - prod - - staging - - dev -security: - - apiKey: [] -tags: - - name: drinks - description: The drinks endpoints. - - name: ingredients - description: The ingredients endpoints. - - name: orders - description: The orders endpoints. - - name: authentication - description: The authentication endpoints. - - name: config - -paths: - x-speakeasy-errors: - statusCodes: # Defines status codes to handle as errors for all operations - - 4XX # Wildcard to handle all status codes in the 400-499 range - - 5XX - /anything/selectGlobalServer: - x-my-ignore: true - get: - operationId: selectGlobalServer - responses: - "200": - description: OK - headers: - X-Optional-Header: - schema: - type: string - /authenticate: - post: - operationId: authenticate - summary: Authenticate with the API by providing a username and password. - security: [] - tags: - - dont-add-x-drop-false - - authentication - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - username: - type: string - password: - type: string - responses: - "200": - description: The api key to use for authenticated endpoints. - content: - application/json: - schema: - type: object - properties: - token: - type: string - "401": - description: Invalid credentials provided. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - - /drinks: - get: - x-speakeasy-usage-example: true - operationId: listDrinks - summary: Get a list of drinks. - description: Get a list of drinks, if authenticated this will include stock levels and product codes otherwise it will only include public information. - security: - - {} - tags: - - drinks - parameters: - - name: drinkType - in: query - description: The type of drink to filter by. If not provided all drinks will be returned. - required: false - schema: - $ref: "#/components/schemas/DrinkType" - responses: - "200": - description: A list of drinks. - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Drink" - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - - /drink/{name}: - get: - operationId: getDrink - summary: Get a drink. - description: Get a drink by name, if authenticated this will include stock levels and product codes otherwise it will only include public information. - tags: - - drinks - parameters: - - name: name - in: path - required: true - schema: - type: string - responses: - "200": - description: A drink. - content: - application/json: - schema: - $ref: "#/components/schemas/Drink" - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - - /ingredients: - get: - operationId: listIngredients - summary: Get a list of ingredients. - description: Get a list of ingredients, if authenticated this will include stock levels and product codes otherwise it will only include public information. - tags: - - ingredients - parameters: - - name: ingredients - in: query - description: A list of ingredients to filter by. If not provided all ingredients will be returned. - required: false - style: form - explode: false - schema: - type: array - items: - type: string - responses: - "200": - description: A list of ingredients. - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Ingredient" - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - - /order: - post: - operationId: createOrder - summary: Create an order. - description: Create an order for a drink. - tags: - - orders - parameters: - - name: callback_url - in: query - description: The url to call when the order is updated. - required: false - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Order" - responses: - "200": - description: The order was created successfully. - content: - application/json: - schema: - $ref: "#/components/schemas/Order" - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - callbacks: - orderUpdate: - "{$request.query.callback_url}": - post: - summary: Receive order updates. - description: Receive order updates from the supplier, this will be called whenever the status of an order changes. - tags: - - orders - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - order: - $ref: "#/components/schemas/Order" - responses: - "200": - description: The order update was received successfully. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" - /webhooks/subscribe: - post: - operationId: subscribeToWebhooks - summary: Subscribe to webhooks. - description: Subscribe to webhooks. - tags: - - config - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - type: object - properties: - url: - type: string - webhook: - type: string - enum: - - stockUpdate - responses: - "200": - description: The webhook was subscribed to successfully. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" -webhooks: - stockUpdate: - post: - summary: Receive stock updates. - description: Receive stock updates from the bar, this will be called whenever the stock levels of a drink or ingredient changes. - tags: - - drinks - - ingredients - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - drink: - $ref: "#/components/schemas/Drink" - ingredient: - $ref: "#/components/schemas/Ingredient" - responses: - "200": - description: The stock update was received successfully. - "5XX": - $ref: "#/components/responses/APIError" - default: - $ref: "#/components/responses/UnknownError" -components: - schemas: - APIError: - type: object - properties: - code: - type: string - message: - type: string - details: - type: object - additionalProperties: true - Error: - type: object - properties: - code: - type: string - message: - type: string - Drink: - type: object - properties: - name: - description: The name of the drink. - type: string - examples: - - Old Fashioned - - Manhattan - - Negroni - type: - $ref: "#/components/schemas/DrinkType" - price: - description: The price of one unit of the drink in US cents. - type: number - examples: - - 1000 # $10.00 - - 1200 # $12.00 - - 1500 # $15.00 - stock: - description: The number of units of the drink in stock, only available when authenticated. - type: integer - readOnly: true - productCode: - description: The product code of the drink, only available when authenticated. - type: string - examples: - - "AC-A2DF3" - - "NAC-3F2D1" - - "APM-1F2D3" - required: - - name - - price - DrinkType: - description: The type of drink. - type: string - enum: - - cocktail - - non-alcoholic - - beer - - wine - - spirit - - other - Ingredient: - type: object - properties: - name: - description: The name of the ingredient. - type: string - examples: - - Sugar Syrup - - Angostura Bitters - - Orange Peel - type: - $ref: "#/components/schemas/IngredientType" - stock: - description: The number of units of the ingredient in stock, only available when authenticated. - type: integer - examples: - - 10 - - 5 - - 0 - readOnly: true - productCode: - description: The product code of the ingredient, only available when authenticated. - type: string - examples: - - "AC-A2DF3" - - "NAC-3F2D1" - - "APM-1F2D3" - required: - - name - - type - IngredientType: - description: The type of ingredient. - type: string - enum: - - fresh - - long-life - - packaged - Order: - description: An order for a drink or ingredient. - type: object - properties: - type: - $ref: "#/components/schemas/OrderType" - productCode: - description: The product code of the drink or ingredient. - type: string - examples: - - "AC-A2DF3" - - "NAC-3F2D1" - - "APM-1F2D3" - quantity: - description: The number of units of the drink or ingredient to order. - type: integer - minimum: 1 - status: - description: The status of the order. - type: string - enum: - - pending - - processing - - complete - readOnly: true - required: - - type - - productCode - - quantity - - status - OrderType: - description: The type of order. - type: string - enum: - - drink - - ingredient - securitySchemes: - apiKey: - type: apiKey - name: Authorization - in: header - responses: - APIError: - description: An error occurred interacting with the API. - content: - application/json: - schema: - $ref: "#/components/schemas/APIError" - UnknownError: - description: An unknown error occurred interacting with the API. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" diff --git a/pkg/overlay/testdata/overlay-generated.yaml b/pkg/overlay/testdata/overlay-generated.yaml deleted file mode 100644 index 7140b58..0000000 --- a/pkg/overlay/testdata/overlay-generated.yaml +++ /dev/null @@ -1,37 +0,0 @@ -overlay: 1.0.0 -info: - title: Drinks Overlay - version: 0.0.0 -actions: - - target: $["tags"] - update: - - name: Testing - description: just a description - - target: $["paths"]["/anything/selectGlobalServer"]["x-my-ignore"] - update: - servers: - - url: http://localhost:35123 - description: The default server. - - target: $["paths"]["/anything/selectGlobalServer"]["get"] - update: - x-drop: true - - target: $["paths"]["/authenticate"]["post"] - update: - x-drop: false - - target: $["paths"]["/drinks"] - update: - x-speakeasy-note: - "$ref": "./removeNote.yaml" - - target: $["paths"]["/drinks"]["get"] - remove: true - - target: $["paths"]["/drink/{name}"]["get"] - remove: true - - target: $["paths"]["/ingredients"]["get"] - update: - x-drop: true - - target: $["paths"]["/order"]["post"] - update: - x-drop: true - - target: $["paths"]["/webhooks/subscribe"]["post"] - update: - x-drop: true diff --git a/pkg/overlay/testdata/overlay.yaml b/pkg/overlay/testdata/overlay.yaml deleted file mode 100644 index ae9c13b..0000000 --- a/pkg/overlay/testdata/overlay.yaml +++ /dev/null @@ -1,52 +0,0 @@ -overlay: 1.0.0 -info: - title: Drinks Overlay - version: 1.2.3 - x-info-extension: 42 -actions: - - target: $.paths["/drink/{name}"].get - description: Test update - update: - parameters: - - x-parameter-extension: foo - name: test - description: Test parameter - in: query - schema: - type: string - responses: - '200': - x-response-extension: foo - description: Test response - content: - application/json: - schema: - type: string - x-action-extension: foo - - target: $.paths["/drinks"].get - description: Test remove - remove: true - - target: $.paths["/drink/{name}"].get~ - description: Test removing a key -- should delete the node too - remove: true - - target: $.paths["/drinks"] - update: - x-speakeasy-note: - "$ref": "./removeNote.yaml" - - target: $.tags - update: - - name: Testing - description: just a description - - target: $.paths["/anything/selectGlobalServer"]["x-my-ignore"] - update: - servers: - - url: http://localhost:35123 - description: The default server. - - target: $.paths.*[?@.operationId] - description: 'add x-drop: true to all paths' - update: - x-drop: true - - target: $.paths.*[?length(@.tags[?(@ == "dont-add-x-drop-false")]) > 0] - description: 'add x-drop: false to any operation which has the dont-add-x-drop-false tag' - update: - x-drop: false diff --git a/pkg/overlay/testdata/removeNote.yaml b/pkg/overlay/testdata/removeNote.yaml deleted file mode 100644 index e19fe55..0000000 --- a/pkg/overlay/testdata/removeNote.yaml +++ /dev/null @@ -1 +0,0 @@ -this got removed.. \ No newline at end of file diff --git a/pkg/overlay/validate.go b/pkg/overlay/validate.go deleted file mode 100644 index 5521e2a..0000000 --- a/pkg/overlay/validate.go +++ /dev/null @@ -1,57 +0,0 @@ -package overlay - -import ( - "fmt" - "net/url" - "strings" -) - -type ValidationErrors []error - -func (v ValidationErrors) Error() string { - msgs := make([]string, len(v)) - for i, err := range v { - msgs[i] = err.Error() - } - return strings.Join(msgs, "\n") -} - -func (v ValidationErrors) Return() error { - if len(v) > 0 { - return v - } - return nil -} - -func (o *Overlay) Validate() error { - errs := make(ValidationErrors, 0) - if o.Version != "1.0.0" { - errs = append(errs, fmt.Errorf("overlay version must be 1.0.0")) - } - - if o.Info.Title == "" { - errs = append(errs, fmt.Errorf("overlay info title must be defined")) - } - if o.Info.Version == "" { - errs = append(errs, fmt.Errorf("overlay info version must be defined")) - } - - if o.Extends != "" { - _, err := url.Parse(o.Extends) - if err != nil { - errs = append(errs, fmt.Errorf("overlay extends must be a valid URL")) - } - } - - for i, action := range o.Actions { - if action.Target == "" { - errs = append(errs, fmt.Errorf("overlay action at index %d target must be defined", i)) - } - - if action.Remove && !action.Update.IsZero() { - errs = append(errs, fmt.Errorf("overlay action at index %d should not both set remove and define update", i)) - } - } - - return errs.Return() -} diff --git a/web/src/assets/wasm/lib.wasm b/web/src/assets/wasm/lib.wasm index 78635ab..ea407a5 100755 Binary files a/web/src/assets/wasm/lib.wasm and b/web/src/assets/wasm/lib.wasm differ diff --git a/web/src/defaults.ts b/web/src/defaults.ts index 67f28af..45a7445 100644 --- a/web/src/defaults.ts +++ b/web/src/defaults.ts @@ -816,7 +816,7 @@ components: in: header `; -export const blankOverlay = `overlay: 1.0.0 +export const blankOverlay = `overlay: 1.1.0 x-speakeasy-jsonpath: rfc9535 info: title: example overlay @@ -825,7 +825,7 @@ actions: - target: $.info.description update: Hello World`; -export const emptyOverlay = `overlay: 1.0.0 +export const emptyOverlay = `overlay: 1.1.0 x-speakeasy-jsonpath: rfc9535 info: title: example overlay