diff --git a/Makefile b/Makefile index 8f94235..da51065 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,6 @@ SOURCE=$(shell find . -iname "*.go") -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 -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 +web/src/assets/wasm/lib.wasm: $(SOURCE) + ./build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..bc28c5c --- /dev/null +++ b/build.sh @@ -0,0 +1,53 @@ +#!/bin/bash +readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +readonly REPO_ROOT="$(git rev-parse --show-toplevel)" + +# ANSI color codes +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Validate Go version higher or equal to than this. +REQUIRED_GO_VERSION="1.24.1" +CURRENT_GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//') + +if ! echo "$CURRENT_GO_VERSION $REQUIRED_GO_VERSION" | awk '{ + split($1, current, ".") + split($2, required, ".") + if ((current[1] > required[1]) || \ + (current[1] == required[1] && current[2] > required[2]) || \ + (current[1] == required[1] && current[2] == required[2] && current[3] >= required[3])) { + exit 0; + } + exit 1 +}'; then + echo -e "${RED}Error: Go version must be $REQUIRED_GO_VERSION or higher, but found $CURRENT_GO_VERSION${NC}" + exit 1 +fi + +get_file_size() { + if [[ "$OSTYPE" == "darwin"* ]]; then + BYTES=$(stat -f%z "$1") + else + BYTES=$(stat -c%s "$1") + fi + MB=$((BYTES / 1024 / 1024)) + echo "$MB MB" +} + +# Function to build the WASM binary +build_wasm() { + printf "Installing dependencies... " + (cd "$SCRIPT_DIR" && go mod tidy) > /dev/null 2>&1 + printf "Done\n" + + printf "Building WASM binary..." + GOOS=js GOARCH=wasm go build -o ./web/src/assets/wasm/lib.wasm cmd/wasm/functions.go + SIZE="$(get_file_size "./web/src/assets/wasm/lib.wasm")" + printf " Done (%s)\n" "$SIZE" + cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" "./web/src/assets/wasm/wasm_exec.js" + +} + +# Initial build +build_wasm "$VERSION" diff --git a/pkg/overlay/apply.go b/pkg/overlay/apply.go index 7a26f3a..005ee95 100644 --- a/pkg/overlay/apply.go +++ b/pkg/overlay/apply.go @@ -92,7 +92,7 @@ func applyUpdateAction(root *yaml.Node, action Action) error { nodes := p.Query(root) for _, node := range nodes { - if err := updateNode(node, action.Update); err != nil { + if err := updateNode(node, &action.Update); err != nil { return err } } @@ -100,14 +100,14 @@ func applyUpdateAction(root *yaml.Node, action Action) error { return nil } -func updateNode(node *yaml.Node, updateNode yaml.Node) error { +func updateNode(node *yaml.Node, updateNode *yaml.Node) error { mergeNode(node, updateNode) return nil } -func mergeNode(node *yaml.Node, merge yaml.Node) { +func mergeNode(node *yaml.Node, merge *yaml.Node) { if node.Kind != merge.Kind { - *node = merge + *node = *clone(merge) return } switch node.Kind { @@ -122,7 +122,7 @@ func mergeNode(node *yaml.Node, merge yaml.Node) { // mergeMappingNode will perform a shallow merge of the merge node into the main // node. -func mergeMappingNode(node *yaml.Node, merge yaml.Node) { +func mergeMappingNode(node *yaml.Node, merge *yaml.Node) { NextKey: for i := 0; i < len(merge.Content); i += 2 { mergeKey := merge.Content[i].Value @@ -131,16 +131,39 @@ NextKey: for j := 0; j < len(node.Content); j += 2 { nodeKey := node.Content[j].Value if nodeKey == mergeKey { - mergeNode(node.Content[j+1], *mergeValue) + mergeNode(node.Content[j+1], mergeValue) continue NextKey } } - node.Content = append(node.Content, merge.Content[i], mergeValue) + 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, merge.Content...) +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/compare_test.go b/pkg/overlay/compare_test.go index d7732d2..fbf6a39 100644 --- a/pkg/overlay/compare_test.go +++ b/pkg/overlay/compare_test.go @@ -54,7 +54,7 @@ func TestCompare(t *testing.T) { assert.NoError(t, err) // Uncomment this if we've improved the output - os.WriteFile("testdata/overlay-generated.yaml", []byte(o2s), 0644) + //os.WriteFile("testdata/overlay-generated.yaml", []byte(o2s), 0644) assert.Equal(t, o1s, o2s) // round trip it diff --git a/pkg/overlay/testdata/openapi-overlayed.yaml b/pkg/overlay/testdata/openapi-overlayed.yaml index 42c1782..de439ec 100644 --- a/pkg/overlay/testdata/openapi-overlayed.yaml +++ b/pkg/overlay/testdata/openapi-overlayed.yaml @@ -69,12 +69,14 @@ paths: 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 @@ -103,6 +105,7 @@ paths: $ref: "#/components/responses/APIError" default: $ref: "#/components/responses/UnknownError" + x-drop: false /drinks: x-speakeasy-note: "$ref": "./removeNote.yaml" @@ -138,6 +141,7 @@ paths: $ref: "#/components/responses/APIError" default: $ref: "#/components/responses/UnknownError" + x-drop: true /order: post: operationId: createOrder @@ -195,6 +199,7 @@ paths: $ref: "#/components/responses/APIError" default: $ref: "#/components/responses/UnknownError" + x-drop: true /webhooks/subscribe: post: operationId: subscribeToWebhooks @@ -224,6 +229,7 @@ paths: $ref: "#/components/responses/APIError" default: $ref: "#/components/responses/UnknownError" + x-drop: true webhooks: stockUpdate: post: diff --git a/pkg/overlay/testdata/openapi.yaml b/pkg/overlay/testdata/openapi.yaml index 294b95b..961aa0c 100644 --- a/pkg/overlay/testdata/openapi.yaml +++ b/pkg/overlay/testdata/openapi.yaml @@ -71,6 +71,7 @@ paths: summary: Authenticate with the API by providing a username and password. security: [] tags: + - dont-add-x-drop-false - authentication requestBody: required: true diff --git a/pkg/overlay/testdata/overlay-generated.yaml b/pkg/overlay/testdata/overlay-generated.yaml index 4b5861c..7140b58 100644 --- a/pkg/overlay/testdata/overlay-generated.yaml +++ b/pkg/overlay/testdata/overlay-generated.yaml @@ -12,6 +12,12 @@ actions: 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: @@ -20,3 +26,12 @@ actions: 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 index 7279363..ae9c13b 100644 --- a/pkg/overlay/testdata/overlay.yaml +++ b/pkg/overlay/testdata/overlay.yaml @@ -42,4 +42,11 @@ actions: servers: - url: http://localhost:35123 description: The default server. -x-top-level-extension: true + - 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/web/src/assets/wasm/lib.wasm b/web/src/assets/wasm/lib.wasm index 2eca688..78635ab 100755 Binary files a/web/src/assets/wasm/lib.wasm and b/web/src/assets/wasm/lib.wasm differ diff --git a/web/src/assets/wasm/wasm_exec.js b/web/src/assets/wasm/wasm_exec.js index bc6f210..d71af9e 100644 --- a/web/src/assets/wasm/wasm_exec.js +++ b/web/src/assets/wasm/wasm_exec.js @@ -14,7 +14,7 @@ if (!globalThis.fs) { let outputBuf = ""; globalThis.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused writeSync(fd, buf) { outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf("\n"); @@ -73,6 +73,14 @@ } } + if (!globalThis.path) { + globalThis.path = { + resolve(...pathSegments) { + return pathSegments.join("/"); + } + } + } + if (!globalThis.crypto) { throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); } @@ -208,10 +216,16 @@ return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); } + const testCallExport = (a, b) => { + this._inst.exports.testExport0(); + return this._inst.exports.testExport(a, b); + } + const timeOrigin = Date.now() - performance.now(); this.importObject = { _gotest: { add: (a, b) => a + b, + callExport: testCallExport, }, gojs: { // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)