From 6710bf4d8685b929f16bf98581becac9b59ea223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Fern=C3=A1ndez=20L=C3=B3pez?= Date: Tue, 3 Oct 2023 12:39:37 +0200 Subject: [PATCH] feat: add docker+wasm examples (#222) Co-authored-by: Jorge Prendes --- examples/containers/.gitignore | 1 + examples/containers/Dockerfile | 67 + examples/containers/Makefile | 56 + examples/containers/README.md | 61 + examples/containers/apps-src/tmp/file.txt | 1 + .../apps-src/user-generation-go/.gitignore | 1 + .../apps-src/user-generation-go/go.mod | 12 + .../apps-src/user-generation-go/go.sum | 12 + .../apps-src/user-generation-go/main.go | 82 + .../user-generation-go.toml | 14 + .../vendor/github.com/tidwall/gjson/LICENSE | 20 + .../vendor/github.com/tidwall/gjson/README.md | 497 +++ .../vendor/github.com/tidwall/gjson/SYNTAX.md | 342 ++ .../vendor/github.com/tidwall/gjson/gjson.go | 3359 +++++++++++++++++ .../vendor/github.com/tidwall/gjson/logo.png | Bin 0 -> 15936 bytes .../vendor/github.com/tidwall/match/LICENSE | 20 + .../vendor/github.com/tidwall/match/README.md | 29 + .../vendor/github.com/tidwall/match/match.go | 237 ++ .../vendor/github.com/tidwall/pretty/LICENSE | 20 + .../github.com/tidwall/pretty/README.md | 122 + .../github.com/tidwall/pretty/pretty.go | 682 ++++ .../vendor/github.com/tidwall/sjson/LICENSE | 21 + .../vendor/github.com/tidwall/sjson/README.md | 278 ++ .../vendor/github.com/tidwall/sjson/logo.png | Bin 0 -> 16874 bytes .../vendor/github.com/tidwall/sjson/sjson.go | 737 ++++ .../vmware-labs/wasm-workers-server/LICENSE | 199 + .../vmware-labs/wasm-workers-server/NOTICE | 5 + .../kits/go/worker/README.md | 61 + .../kits/go/worker/bindings/bindings.c | 223 ++ .../kits/go/worker/bindings/bindings.go | 337 ++ .../kits/go/worker/bindings/bindings.h | 131 + .../kits/go/worker/bindings/bindings_types.go | 108 + .../wasm-workers-server/kits/go/worker/doc.go | 13 + .../kits/go/worker/worker.go | 267 ++ .../user-generation-go/vendor/modules.txt | 16 + .../user-generation-js/user-generation-js.js | 41 + .../user-generation-js.toml | 14 + .../apps-src/user-generation-python/index.py | 29 + .../user-generation-python/index.toml | 6 + .../apps-src/user-generation-python/tmp | 1 + .../apps-src/user-generation-ruby/index.rb | 31 + .../apps-src/user-generation-ruby/index.toml | 6 + .../apps-src/user-generation-ruby/tmp | 1 + .../user-generation-rust/.cargo/config.toml | 3 + .../apps-src/user-generation-rust/.gitignore | 1 + .../apps-src/user-generation-rust/Cargo.lock | 332 ++ .../apps-src/user-generation-rust/Cargo.toml | 10 + .../user-generation-rust/rust-toolchain.toml | 2 + .../apps-src/user-generation-rust/src/main.rs | 61 + .../user-generation-rust.toml | 14 + examples/containers/apps/root/.wws.toml | 83 + .../apps/root/user-generation-go.toml | 14 + .../apps/root/user-generation-js.js | 41 + .../apps/root/user-generation-js.toml | 14 + .../apps/root/user-generation-python/index.py | 29 + .../root/user-generation-python/index.toml | 6 + .../root/user-generation-python/tmp/file.txt | 1 + .../apps/root/user-generation-ruby/index.rb | 31 + .../apps/root/user-generation-ruby/index.toml | 6 + .../root/user-generation-ruby/tmp/file.txt | 1 + .../apps/root/user-generation-rust.toml | 14 + examples/containers/tmp/file.txt | 1 + 62 files changed, 8824 insertions(+) create mode 100644 examples/containers/.gitignore create mode 100644 examples/containers/Dockerfile create mode 100644 examples/containers/Makefile create mode 100644 examples/containers/README.md create mode 100644 examples/containers/apps-src/tmp/file.txt create mode 100644 examples/containers/apps-src/user-generation-go/.gitignore create mode 100644 examples/containers/apps-src/user-generation-go/go.mod create mode 100644 examples/containers/apps-src/user-generation-go/go.sum create mode 100644 examples/containers/apps-src/user-generation-go/main.go create mode 100644 examples/containers/apps-src/user-generation-go/user-generation-go.toml create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/LICENSE create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/README.md create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/SYNTAX.md create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/gjson.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/logo.png create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/match/LICENSE create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/match/README.md create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/match/match.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/LICENSE create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/README.md create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/pretty.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/LICENSE create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/README.md create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/logo.png create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/sjson.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/LICENSE create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/NOTICE create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/README.md create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.c create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.h create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings_types.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/doc.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/worker.go create mode 100644 examples/containers/apps-src/user-generation-go/vendor/modules.txt create mode 100644 examples/containers/apps-src/user-generation-js/user-generation-js.js create mode 100644 examples/containers/apps-src/user-generation-js/user-generation-js.toml create mode 100644 examples/containers/apps-src/user-generation-python/index.py create mode 100644 examples/containers/apps-src/user-generation-python/index.toml create mode 120000 examples/containers/apps-src/user-generation-python/tmp create mode 100644 examples/containers/apps-src/user-generation-ruby/index.rb create mode 100644 examples/containers/apps-src/user-generation-ruby/index.toml create mode 120000 examples/containers/apps-src/user-generation-ruby/tmp create mode 100644 examples/containers/apps-src/user-generation-rust/.cargo/config.toml create mode 100644 examples/containers/apps-src/user-generation-rust/.gitignore create mode 100644 examples/containers/apps-src/user-generation-rust/Cargo.lock create mode 100644 examples/containers/apps-src/user-generation-rust/Cargo.toml create mode 100644 examples/containers/apps-src/user-generation-rust/rust-toolchain.toml create mode 100644 examples/containers/apps-src/user-generation-rust/src/main.rs create mode 100644 examples/containers/apps-src/user-generation-rust/user-generation-rust.toml create mode 100644 examples/containers/apps/root/.wws.toml create mode 100644 examples/containers/apps/root/user-generation-go.toml create mode 100644 examples/containers/apps/root/user-generation-js.js create mode 100644 examples/containers/apps/root/user-generation-js.toml create mode 100644 examples/containers/apps/root/user-generation-python/index.py create mode 100644 examples/containers/apps/root/user-generation-python/index.toml create mode 100644 examples/containers/apps/root/user-generation-python/tmp/file.txt create mode 100644 examples/containers/apps/root/user-generation-ruby/index.rb create mode 100644 examples/containers/apps/root/user-generation-ruby/index.toml create mode 100644 examples/containers/apps/root/user-generation-ruby/tmp/file.txt create mode 100644 examples/containers/apps/root/user-generation-rust.toml create mode 100644 examples/containers/tmp/file.txt diff --git a/examples/containers/.gitignore b/examples/containers/.gitignore new file mode 100644 index 00000000..3e221292 --- /dev/null +++ b/examples/containers/.gitignore @@ -0,0 +1 @@ +/dist \ No newline at end of file diff --git a/examples/containers/Dockerfile b/examples/containers/Dockerfile new file mode 100644 index 00000000..ccbb087d --- /dev/null +++ b/examples/containers/Dockerfile @@ -0,0 +1,67 @@ +# syntax=docker/dockerfile:labs + +# Prepare a base layer for building the rust endpoint +FROM --platform=$BUILDPLATFORM rust:1.72.0-alpine AS base +RUN apk add sudo curl musl-dev ca-certificates && \ + curl -fsSL https://workers.wasmlabs.dev/install | sh + +# Rust endpoint: first build, then generate the release layer +FROM base AS build-rust +WORKDIR /src +RUN --mount=type=bind,target=/src,source=./apps-src/user-generation-rust \ + cargo build --release --target-dir /output + +FROM scratch AS release-rust +COPY --from=build-rust /output/wasm32-wasi/release/user-generation-rust.wasm / +COPY ./apps-src/user-generation-rust/user-generation-rust.toml / + +# JS endpoint: no build needed, just generate the release layer +FROM scratch AS release-js +COPY ./apps-src/user-generation-js/ / + +# Ruby endpoint: no build needed, just generate the release layer +FROM scratch AS release-ruby +COPY ./apps-src/user-generation-ruby /user-generation-ruby + +# Python endpoint: no build needed, just generate the release layer +FROM scratch AS release-python +COPY ./apps-src/user-generation-python/ /user-generation-python + +# Go endpoint: first build, then generate the release layer +FROM --platform=$BUILDPLATFORM tinygo/tinygo:0.28.1 AS build-go +WORKDIR /src +RUN --mount=type=bind,target=/src,source=./apps-src/user-generation-go \ + tinygo build \ + -o /home/tinygo/user-generation-go.wasm \ + -no-debug -panic=trap -scheduler=none -gc=leaking \ + -target=wasi . + +FROM scratch AS release-go +COPY --from=build-go /home/tinygo/user-generation-go.wasm / +COPY ./apps-src/user-generation-go/user-generation-go.toml / + +# Wws root: install the required runtimes, then generate the release layer +FROM base AS build-root +WORKDIR /output +RUN wws runtimes install ruby latest +RUN wws runtimes install python latest +COPY ./apps-src/tmp /output/tmp +RUN mkdir /public + +FROM scratch AS release-root +COPY --from=build-root /public /public +COPY --from=build-root /output / + +# Merge all the release layers into one +FROM scratch AS release +COPY --from=release-root / / +COPY --from=release-rust / / +COPY --from=release-js / / +COPY --from=release-ruby / / +COPY --from=release-python / / +COPY --from=release-go / / + +# Copy over te SSL certificates +COPY --from=base /etc/ssl /etc/ssl + +ENTRYPOINT ["/"] diff --git a/examples/containers/Makefile b/examples/containers/Makefile new file mode 100644 index 00000000..3d0b2373 --- /dev/null +++ b/examples/containers/Makefile @@ -0,0 +1,56 @@ +# Workaround for https://github.com/moby/buildkit/issues/3891 +export BUILDX_NO_DEFAULT_ATTESTATIONS = 1 + +# Build a container image for the demo +.PHONY: image +image: + docker build --platform wasi/wasm --tag=ghcr.io/vmware-labs/wws-apps:latest . + +# Export the content of the demo image into the ./dist folder +.PHONY: dist +dist: clean + docker build --platform wasi/wasm --output=dist . + +# Run the demo container +.PHONY: run +run: stop image + docker run --rm -d --name docker-wws \ + -p 3000:3000 \ + --runtime=io.containerd.wws.v1 \ + --platform=wasi/wasm \ + ghcr.io/vmware-labs/wws-apps:latest + @echo "Now you can reach the Wasm Workers Server functions, such as:" + @echo " - curl http://localhost:3000/user-generation-rust" + @echo " - curl http://localhost:3000/user-generation-go" + @echo " - curl http://localhost:3000/user-generation-js" + @echo " - curl http://localhost:3000/user-generation-python" + @echo " - curl http://localhost:3000/user-generation-ruby" + +# Run the demo container using a host mount +.PHONY: run-with-mount +run-with-mount: stop image + docker run --rm -d --name docker-wws \ + -p 3000:3000 \ + --runtime=io.containerd.wws.v1 \ + --platform=wasi/wasm \ + -v $(PWD)/tmp:/tmp \ + ghcr.io/vmware-labs/wws-apps:latest + @echo "Now you can reach the Wasm Workers Server functions, such as:" + @echo " - curl http://localhost:3000/user-generation-rust" + @echo " - curl http://localhost:3000/user-generation-go" + @echo " - curl http://localhost:3000/user-generation-js" + @echo " - curl http://localhost:3000/user-generation-python" + @echo " - curl http://localhost:3000/user-generation-ruby" + +# Stop the demo contianer +.PHONY: stop +stop: + docker rm -f docker-wws + +# Same as dist +.PHONY: build +build: dist; + +.PHONY: clean +clean: + rm -Rf ./dist diff --git a/examples/containers/README.md b/examples/containers/README.md new file mode 100644 index 00000000..05fa39bb --- /dev/null +++ b/examples/containers/README.md @@ -0,0 +1,61 @@ +# Docker + Wasm + Wasm Workers Server (wws) + +This repo showcases some functions you can write, taking advantage of +Wasm Workers Server, on top of Docker Desktop, thanks to the +[`containerd-wasm-shims`](https://github.com/deislabs/containerd-wasm-shims) project. + +## Build + +Prerequisites for building this project: + +- Docker, with [Docker + Wasm support](https://docs.docker.com/desktop/wasm/) + +In order to build this example, you just have to run on the root of +this project: + +```shell-session +$ make build +``` + +## Running + +Prerequisites for running this project: Docker Desktop 4.23.0 or later. + +You can run the example: + +```shell-session +$ make run +``` + +After that, you can target the different endpoints exposed by the Wasm +Workers Server: + +```shell-session +$ curl -s http://localhost:3000/user-generation-rust | jq +$ curl -s http://localhost:3000/user-generation-go | jq +$ curl -s http://localhost:3000/user-generation-js | jq +$ curl -s http://localhost:3000/user-generation-python | jq +$ curl -s http://localhost:3000/user-generation-ruby | jq +``` + +This example also showcases exposing a directory in the host to the WebAssembly guest. This example can be executed with: + +```shell-session +$ make run-with-mount +``` + +You can reach the same endpoints, but you will notice that the +attribute `.some_file_contents` of the produced JSON in all examples +now is the content of +[tmp/file.txt](tmp/file.txt) +from the host. + +The only worker that is not able to read contents from the filesystem +is the JS one, so you can only check it with the rest: + +```shell-session +$ curl -s http://localhost:3000/user-generation-rust | jq +$ curl -s http://localhost:3000/user-generation-go | jq +$ curl -s http://localhost:3000/user-generation-python | jq +$ curl -s http://localhost:3000/user-generation-ruby | jq +``` diff --git a/examples/containers/apps-src/tmp/file.txt b/examples/containers/apps-src/tmp/file.txt new file mode 100644 index 00000000..1ed65434 --- /dev/null +++ b/examples/containers/apps-src/tmp/file.txt @@ -0,0 +1 @@ +Some contents diff --git a/examples/containers/apps-src/user-generation-go/.gitignore b/examples/containers/apps-src/user-generation-go/.gitignore new file mode 100644 index 00000000..917660a3 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/.gitignore @@ -0,0 +1 @@ +*.wasm \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-go/go.mod b/examples/containers/apps-src/user-generation-go/go.mod new file mode 100644 index 00000000..ef8b51f6 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/go.mod @@ -0,0 +1,12 @@ +module github.com/vmware-labs/docker-wasm + +go 1.20 + +require github.com/vmware-labs/wasm-workers-server v1.4.0 + +require ( + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect +) diff --git a/examples/containers/apps-src/user-generation-go/go.sum b/examples/containers/apps-src/user-generation-go/go.sum new file mode 100644 index 00000000..44d4ad10 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/go.sum @@ -0,0 +1,12 @@ +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/vmware-labs/wasm-workers-server v1.4.0 h1:dlc0eWc3TdijQ6RxX3Mt5OLPazO/Trmk4mqDt68eDkI= +github.com/vmware-labs/wasm-workers-server v1.4.0/go.mod h1:cigUhoitjUTLsUzR4+q0cz2FymdvJtfrfIS2hYAj69c= diff --git a/examples/containers/apps-src/user-generation-go/main.go b/examples/containers/apps-src/user-generation-go/main.go new file mode 100644 index 00000000..4f357a30 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "strconv" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" +) + +type User struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Username string `json:"username"` + Email string `json:"email"` +} + +type ResponseData struct { + User User `json:"user"` + SomeFileContents string `json:"some_file_contents"` + GeneratedUsers uint32 `json:"generated_users"` +} + +func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + cache, _ := r.Context().Value(worker.CacheKey).(map[string]string) + + // Create the request + req, err := http.NewRequest(http.MethodGet, "https://random-data-api.com/api/v2/users", nil) + if err != nil { + panic(err) + } + + res, err := worker.SendHttpRequest(req) + if err != nil { + panic(err) + } + + // Read the response + resBody, err := io.ReadAll(res.Body) + if err != nil { + panic(err) + } + res.Body.Close() + + user := User{} + err = json.Unmarshal([]byte(resBody), &user) + if err != nil { + panic(err) + } + + fileContents_, err := ioutil.ReadFile("/tmp/file.txt") + if err != nil { + panic(err) + } + fileContents := string(fileContents_) + + generatedUserCount := uint32(1) + if count, ok := cache["generated_users_counter"]; ok { + n, _ := strconv.ParseUint(count, 10, 32) + generatedUserCount = uint32(n) + 1 + } + cache["generated_users_counter"] = fmt.Sprintf("%d", generatedUserCount) + + responseData := ResponseData{ + User: user, + SomeFileContents: fileContents, + GeneratedUsers: generatedUserCount, + } + + marshaledResponseData, err := json.Marshal(responseData) + if err != nil { + panic(err) + } + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(marshaledResponseData)) + }) +} diff --git a/examples/containers/apps-src/user-generation-go/user-generation-go.toml b/examples/containers/apps-src/user-generation-go/user-generation-go.toml new file mode 100644 index 00000000..d8c05b5b --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/user-generation-go.toml @@ -0,0 +1,14 @@ +name = "user-generation-go" +version = "1" + +[data] +[data.kv] +namespace = "generated_users_counter" + +[[folders]] +from = "./tmp" +to = "/tmp" + +[features] +[features.http_requests] +allowed_hosts = ["random-data-api.com"] \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/LICENSE b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/LICENSE new file mode 100644 index 00000000..58f5819a --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/README.md b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/README.md new file mode 100644 index 00000000..c8db11f1 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/README.md @@ -0,0 +1,497 @@ +

+GJSON +
+GoDoc +GJSON Playground +GJSON Syntax + +

+ +

get json values quickly

+ +GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document. +It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array), and [parsing json lines](#json-lines). + +Also check out [SJSON](https://github.com/tidwall/sjson) for modifying json, and the [JJ](https://github.com/tidwall/jj) command line tool. + +This README is a quick overview of how to use GJSON, for more information check out [GJSON Syntax](SYNTAX.md). + +GJSON is also available for [Python](https://github.com/volans-/gjson-py) and [Rust](https://github.com/tidwall/gjson.rs) + +Getting Started +=============== + +## Installing + +To start using GJSON, install Go and run `go get`: + +```sh +$ go get -u github.com/tidwall/gjson +``` + +This will retrieve the library. + +## Get a value +Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". When the value is found it's returned immediately. + +```go +package main + +import "github.com/tidwall/gjson" + +const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}` + +func main() { + value := gjson.Get(json, "name.last") + println(value.String()) +} +``` + +This will print: + +``` +Prichard +``` +*There's also the [GetMany](#get-multiple-values-at-once) function to get multiple values at once, and [GetBytes](#working-with-bytes) for working with JSON byte slices.* + +## Path Syntax + +Below is a quick overview of the path syntax, for more complete information please +check out [GJSON Syntax](SYNTAX.md). + +A path is a series of keys separated by a dot. +A key may contain special wildcard characters '\*' and '?'. +To access an array value use the index as the key. +To get the number of elements in an array or to access a child path, use the '#' character. +The dot and wildcard characters can be escaped with '\\'. + +```json +{ + "name": {"first": "Tom", "last": "Anderson"}, + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]}, + {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]}, + {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]} + ] +} +``` +``` +"name.last" >> "Anderson" +"age" >> 37 +"children" >> ["Sara","Alex","Jack"] +"children.#" >> 3 +"children.1" >> "Alex" +"child*.2" >> "Jack" +"c?ildren.0" >> "Sara" +"fav\.movie" >> "Deer Hunter" +"friends.#.first" >> ["Dale","Roger","Jane"] +"friends.1.last" >> "Craig" +``` + +You can also query an array for the first match by using `#(...)`, or find all +matches with `#(...)#`. Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` +comparison operators and the simple pattern matching `%` (like) and `!%` +(not like) operators. + +``` +friends.#(last=="Murphy").first >> "Dale" +friends.#(last=="Murphy")#.first >> ["Dale","Jane"] +friends.#(age>45)#.last >> ["Craig","Murphy"] +friends.#(first%"D*").last >> "Murphy" +friends.#(first!%"D*").last >> "Craig" +friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"] +``` + +*Please note that prior to v1.3.0, queries used the `#[...]` brackets. This was +changed in v1.3.0 as to avoid confusion with the new +[multipath](SYNTAX.md#multipaths) syntax. For backwards compatibility, +`#[...]` will continue to work until the next major release.* + +## Result Type + +GJSON supports the json types `string`, `number`, `bool`, and `null`. +Arrays and Objects are returned as their raw json types. + +The `Result` type holds one of these: + +``` +bool, for JSON booleans +float64, for JSON numbers +string, for JSON string literals +nil, for JSON null +``` + +To directly access the value: + +```go +result.Type // can be String, Number, True, False, Null, or JSON +result.Str // holds the string +result.Num // holds the float64 number +result.Raw // holds the raw json +result.Index // index of raw value in original json, zero means index unknown +result.Indexes // indexes of all the elements that match on a path containing the '#' query character. +``` + +There are a variety of handy functions that work on a result: + +```go +result.Exists() bool +result.Value() interface{} +result.Int() int64 +result.Uint() uint64 +result.Float() float64 +result.String() string +result.Bool() bool +result.Time() time.Time +result.Array() []gjson.Result +result.Map() map[string]gjson.Result +result.Get(path string) Result +result.ForEach(iterator func(key, value Result) bool) +result.Less(token Result, caseSensitive bool) bool +``` + +The `result.Value()` function returns an `interface{}` which requires type assertion and is one of the following Go types: + +```go +boolean >> bool +number >> float64 +string >> string +null >> nil +array >> []interface{} +object >> map[string]interface{} +``` + +The `result.Array()` function returns back an array of values. +If the result represents a non-existent value, then an empty array will be returned. +If the result is not a JSON array, the return value will be an array containing one result. + +### 64-bit integers + +The `result.Int()` and `result.Uint()` calls are capable of reading all 64 bits, allowing for large JSON integers. + +```go +result.Int() int64 // -9223372036854775808 to 9223372036854775807 +result.Uint() uint64 // 0 to 18446744073709551615 +``` + +## Modifiers and path chaining + +New in version 1.2 is support for modifier functions and path chaining. + +A modifier is a path component that performs custom processing on the +json. + +Multiple paths can be "chained" together using the pipe character. +This is useful for getting results from a modified query. + +For example, using the built-in `@reverse` modifier on the above json document, +we'll get `children` array and reverse the order: + +``` +"children|@reverse" >> ["Jack","Alex","Sara"] +"children|@reverse|0" >> "Jack" +``` + +There are currently the following built-in modifiers: + +- `@reverse`: Reverse an array or the members of an object. +- `@ugly`: Remove all whitespace from a json document. +- `@pretty`: Make the json document more human readable. +- `@this`: Returns the current element. It can be used to retrieve the root element. +- `@valid`: Ensure the json document is valid. +- `@flatten`: Flattens an array. +- `@join`: Joins multiple objects into a single object. +- `@keys`: Returns an array of keys for an object. +- `@values`: Returns an array of values for an object. +- `@tostr`: Converts json to a string. Wraps a json string. +- `@fromstr`: Converts a string from json. Unwraps a json string. +- `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db). + +### Modifier arguments + +A modifier may accept an optional argument. The argument can be a valid JSON +document or just characters. + +For example, the `@pretty` modifier takes a json object as its argument. + +``` +@pretty:{"sortKeys":true} +``` + +Which makes the json pretty and orders all of its keys. + +```json +{ + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"age": 44, "first": "Dale", "last": "Murphy"}, + {"age": 68, "first": "Roger", "last": "Craig"}, + {"age": 47, "first": "Jane", "last": "Murphy"} + ], + "name": {"first": "Tom", "last": "Anderson"} +} +``` + +*The full list of `@pretty` options are `sortKeys`, `indent`, `prefix`, and `width`. +Please see [Pretty Options](https://github.com/tidwall/pretty#customized-output) for more information.* + +### Custom modifiers + +You can also add custom modifiers. + +For example, here we create a modifier that makes the entire json document upper +or lower case. + +```go +gjson.AddModifier("case", func(json, arg string) string { + if arg == "upper" { + return strings.ToUpper(json) + } + if arg == "lower" { + return strings.ToLower(json) + } + return json +}) +``` + +``` +"children|@case:upper" >> ["SARA","ALEX","JACK"] +"children|@case:lower|@reverse" >> ["jack","alex","sara"] +``` + +## JSON Lines + +There's support for [JSON Lines](http://jsonlines.org/) using the `..` prefix, which treats a multilined document as an array. + +For example: + +``` +{"name": "Gilbert", "age": 61} +{"name": "Alexa", "age": 34} +{"name": "May", "age": 57} +{"name": "Deloise", "age": 44} +``` + +``` +..# >> 4 +..1 >> {"name": "Alexa", "age": 34} +..3 >> {"name": "Deloise", "age": 44} +..#.name >> ["Gilbert","Alexa","May","Deloise"] +..#(name="May").age >> 57 +``` + +The `ForEachLines` function will iterate through JSON lines. + +```go +gjson.ForEachLine(json, func(line gjson.Result) bool{ + println(line.String()) + return true +}) +``` + +## Get nested array values + +Suppose you want all the last names from the following json: + +```json +{ + "programmers": [ + { + "firstName": "Janet", + "lastName": "McLaughlin", + }, { + "firstName": "Elliotte", + "lastName": "Hunter", + }, { + "firstName": "Jason", + "lastName": "Harold", + } + ] +} +``` + +You would use the path "programmers.#.lastName" like such: + +```go +result := gjson.Get(json, "programmers.#.lastName") +for _, name := range result.Array() { + println(name.String()) +} +``` + +You can also query an object inside an array: + +```go +name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`) +println(name.String()) // prints "Elliotte" +``` + +## Iterate through an object or array + +The `ForEach` function allows for quickly iterating through an object or array. +The key and value are passed to the iterator function for objects. +Only the value is passed for arrays. +Returning `false` from an iterator will stop iteration. + +```go +result := gjson.Get(json, "programmers") +result.ForEach(func(key, value gjson.Result) bool { + println(value.String()) + return true // keep iterating +}) +``` + +## Simple Parse and Get + +There's a `Parse(json)` function that will do a simple parse, and `result.Get(path)` that will search a result. + +For example, all of these will return the same result: + +```go +gjson.Parse(json).Get("name").Get("last") +gjson.Get(json, "name").Get("last") +gjson.Get(json, "name.last") +``` + +## Check for the existence of a value + +Sometimes you just want to know if a value exists. + +```go +value := gjson.Get(json, "name.last") +if !value.Exists() { + println("no last name") +} else { + println(value.String()) +} + +// Or as one step +if gjson.Get(json, "name.last").Exists() { + println("has a last name") +} +``` + +## Validate JSON + +The `Get*` and `Parse*` functions expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results. + +If you are consuming JSON from an unpredictable source then you may want to validate prior to using GJSON. + +```go +if !gjson.Valid(json) { + return errors.New("invalid json") +} +value := gjson.Get(json, "name.last") +``` + +## Unmarshal to a map + +To unmarshal to a `map[string]interface{}`: + +```go +m, ok := gjson.Parse(json).Value().(map[string]interface{}) +if !ok { + // not a map +} +``` + +## Working with Bytes + +If your JSON is contained in a `[]byte` slice, there's the [GetBytes](https://godoc.org/github.com/tidwall/gjson#GetBytes) function. This is preferred over `Get(string(data), path)`. + +```go +var json []byte = ... +result := gjson.GetBytes(json, path) +``` + +If you are using the `gjson.GetBytes(json, path)` function and you want to avoid converting `result.Raw` to a `[]byte`, then you can use this pattern: + +```go +var json []byte = ... +result := gjson.GetBytes(json, path) +var raw []byte +if result.Index > 0 { + raw = json[result.Index:result.Index+len(result.Raw)] +} else { + raw = []byte(result.Raw) +} +``` + +This is a best-effort no allocation sub slice of the original json. This method utilizes the `result.Index` field, which is the position of the raw data in the original json. It's possible that the value of `result.Index` equals zero, in which case the `result.Raw` is converted to a `[]byte`. + +## Get multiple values at once + +The `GetMany` function can be used to get multiple values at the same time. + +```go +results := gjson.GetMany(json, "name.first", "name.last", "age") +``` + +The return value is a `[]Result`, which will always contain exactly the same number of items as the input paths. + +## Performance + +Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), +[ffjson](https://github.com/pquerna/ffjson), +[EasyJSON](https://github.com/mailru/easyjson), +[jsonparser](https://github.com/buger/jsonparser), +and [json-iterator](https://github.com/json-iterator/go) + +``` +BenchmarkGJSONGet-16 11644512 311 ns/op 0 B/op 0 allocs/op +BenchmarkGJSONUnmarshalMap-16 1122678 3094 ns/op 1920 B/op 26 allocs/op +BenchmarkJSONUnmarshalMap-16 516681 6810 ns/op 2944 B/op 69 allocs/op +BenchmarkJSONUnmarshalStruct-16 697053 5400 ns/op 928 B/op 13 allocs/op +BenchmarkJSONDecoder-16 330450 10217 ns/op 3845 B/op 160 allocs/op +BenchmarkFFJSONLexer-16 1424979 2585 ns/op 880 B/op 8 allocs/op +BenchmarkEasyJSONLexer-16 3000000 729 ns/op 501 B/op 5 allocs/op +BenchmarkJSONParserGet-16 3000000 366 ns/op 21 B/op 0 allocs/op +BenchmarkJSONIterator-16 3000000 869 ns/op 693 B/op 14 allocs/op +``` + +JSON document used: + +```json +{ + "widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } + } +} +``` + +Each operation was rotated through one of the following search paths: + +``` +widget.window.name +widget.image.hOffset +widget.text.onMouseUp +``` + +*These benchmarks were run on a MacBook Pro 16" 2.4 GHz Intel Core i9 using Go 1.17 and can be found [here](https://github.com/tidwall/gjson-benchmarks).* diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/SYNTAX.md b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/SYNTAX.md new file mode 100644 index 00000000..7a9b6a2d --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/SYNTAX.md @@ -0,0 +1,342 @@ +# GJSON Path Syntax + +A GJSON Path is a text string syntax that describes a search pattern for quickly retreiving values from a JSON payload. + +This document is designed to explain the structure of a GJSON Path through examples. + +- [Path structure](#path-structure) +- [Basic](#basic) +- [Wildcards](#wildcards) +- [Escape Character](#escape-character) +- [Arrays](#arrays) +- [Queries](#queries) +- [Dot vs Pipe](#dot-vs-pipe) +- [Modifiers](#modifiers) +- [Multipaths](#multipaths) +- [Literals](#literals) + +The definitive implemenation is [github.com/tidwall/gjson](https://github.com/tidwall/gjson). +Use the [GJSON Playground](https://gjson.dev) to experiment with the syntax online. + +## Path structure + +A GJSON Path is intended to be easily expressed as a series of components seperated by a `.` character. + +Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, `!`, and `?`. + +## Example + +Given this JSON + +```json +{ + "name": {"first": "Tom", "last": "Anderson"}, + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]}, + {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]}, + {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]} + ] +} +``` + +The following GJSON Paths evaluate to the accompanying values. + +### Basic + +In many cases you'll just want to retreive values by object name or array index. + +```go +name.last "Anderson" +name.first "Tom" +age 37 +children ["Sara","Alex","Jack"] +children.0 "Sara" +children.1 "Alex" +friends.1 {"first": "Roger", "last": "Craig", "age": 68} +friends.1.first "Roger" +``` + +### Wildcards + +A key may contain the special wildcard characters `*` and `?`. +The `*` will match on any zero+ characters, and `?` matches on any one character. + +```go +child*.2 "Jack" +c?ildren.0 "Sara" +``` + +### Escape character + +Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`. + +```go +fav\.movie "Deer Hunter" +``` + +You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in your source code. + +```go +// Go +val := gjson.Get(json, "fav\\.movie") // must escape the slash +val := gjson.Get(json, `fav\.movie`) // no need to escape the slash +``` + +```rust +// Rust +let val = gjson::get(json, "fav\\.movie") // must escape the slash +let val = gjson::get(json, r#"fav\.movie"#) // no need to escape the slash +``` + + +### Arrays + +The `#` character allows for digging into JSON Arrays. + +To get the length of an array you'll just use the `#` all by itself. + +```go +friends.# 3 +friends.#.age [44,68,47] +``` + +### Queries + +You can also query an array for the first match by using `#(...)`, or find all matches with `#(...)#`. +Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators, +and the simple pattern matching `%` (like) and `!%` (not like) operators. + +```go +friends.#(last=="Murphy").first "Dale" +friends.#(last=="Murphy")#.first ["Dale","Jane"] +friends.#(age>45)#.last ["Craig","Murphy"] +friends.#(first%"D*").last "Murphy" +friends.#(first!%"D*").last "Craig" +``` + +To query for a non-object value in an array, you can forgo the string to the right of the operator. + +```go +children.#(!%"*a*") "Alex" +children.#(%"*a*")# ["Sara","Jack"] +``` + +Nested queries are allowed. + +```go +friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"] +``` + +*Please note that prior to v1.3.0, queries used the `#[...]` brackets. This was +changed in v1.3.0 as to avoid confusion with the new [multipath](#multipaths) +syntax. For backwards compatibility, `#[...]` will continue to work until the +next major release.* + +The `~` (tilde) operator will convert a value to a boolean before comparison. + +For example, using the following JSON: + +```json +{ + "vals": [ + { "a": 1, "b": true }, + { "a": 2, "b": true }, + { "a": 3, "b": false }, + { "a": 4, "b": "0" }, + { "a": 5, "b": 0 }, + { "a": 6, "b": "1" }, + { "a": 7, "b": 1 }, + { "a": 8, "b": "true" }, + { "a": 9, "b": false }, + { "a": 10, "b": null }, + { "a": 11 } + ] +} +``` + +You can now query for all true(ish) or false(ish) values: + +``` +vals.#(b==~true)#.a >> [1,2,6,7,8] +vals.#(b==~false)#.a >> [3,4,5,9,10,11] +``` + +The last value which was non-existent is treated as `false` + +### Dot vs Pipe + +The `.` is standard separator, but it's also possible to use a `|`. +In most cases they both end up returning the same results. +The cases where`|` differs from `.` is when it's used after the `#` for [Arrays](#arrays) and [Queries](#queries). + +Here are some examples + +```go +friends.0.first "Dale" +friends|0.first "Dale" +friends.0|first "Dale" +friends|0|first "Dale" +friends|# 3 +friends.# 3 +friends.#(last="Murphy")# [{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}] +friends.#(last="Murphy")#.first ["Dale","Jane"] +friends.#(last="Murphy")#|first +friends.#(last="Murphy")#.0 [] +friends.#(last="Murphy")#|0 {"first": "Dale", "last": "Murphy", "age": 44} +friends.#(last="Murphy")#.# [] +friends.#(last="Murphy")#|# 2 +``` + +Let's break down a few of these. + +The path `friends.#(last="Murphy")#` all by itself results in + +```json +[{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}] +``` + +The `.first` suffix will process the `first` path on each array element *before* returning the results. Which becomes + +```json +["Dale","Jane"] +``` + +But the `|first` suffix actually processes the `first` path *after* the previous result. +Since the previous result is an array, not an object, it's not possible to process +because `first` does not exist. + +Yet, `|0` suffix returns + +```json +{"first": "Dale", "last": "Murphy", "age": 44} +``` + +Because `0` is the first index of the previous result. + +### Modifiers + +A modifier is a path component that performs custom processing on the JSON. + +For example, using the built-in `@reverse` modifier on the above JSON payload will reverse the `children` array: + +```go +children.@reverse ["Jack","Alex","Sara"] +children.@reverse.0 "Jack" +``` + +There are currently the following built-in modifiers: + +- `@reverse`: Reverse an array or the members of an object. +- `@ugly`: Remove all whitespace from JSON. +- `@pretty`: Make the JSON more human readable. +- `@this`: Returns the current element. It can be used to retrieve the root element. +- `@valid`: Ensure the json document is valid. +- `@flatten`: Flattens an array. +- `@join`: Joins multiple objects into a single object. +- `@keys`: Returns an array of keys for an object. +- `@values`: Returns an array of values for an object. +- `@tostr`: Converts json to a string. Wraps a json string. +- `@fromstr`: Converts a string from json. Unwraps a json string. +- `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db). + +#### Modifier arguments + +A modifier may accept an optional argument. The argument can be a valid JSON payload or just characters. + +For example, the `@pretty` modifier takes a json object as its argument. + +``` +@pretty:{"sortKeys":true} +``` + +Which makes the json pretty and orders all of its keys. + +```json +{ + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"age": 44, "first": "Dale", "last": "Murphy"}, + {"age": 68, "first": "Roger", "last": "Craig"}, + {"age": 47, "first": "Jane", "last": "Murphy"} + ], + "name": {"first": "Tom", "last": "Anderson"} +} +``` + +*The full list of `@pretty` options are `sortKeys`, `indent`, `prefix`, and `width`. +Please see [Pretty Options](https://github.com/tidwall/pretty#customized-output) for more information.* + +#### Custom modifiers + +You can also add custom modifiers. + +For example, here we create a modifier which makes the entire JSON payload upper or lower case. + +```go +gjson.AddModifier("case", func(json, arg string) string { + if arg == "upper" { + return strings.ToUpper(json) + } + if arg == "lower" { + return strings.ToLower(json) + } + return json +}) +"children.@case:upper" ["SARA","ALEX","JACK"] +"children.@case:lower.@reverse" ["jack","alex","sara"] +``` + +*Note: Custom modifiers are not yet available in the Rust version* + +### Multipaths + +Starting with v1.3.0, GJSON added the ability to join multiple paths together +to form new documents. Wrapping comma-separated paths between `[...]` or +`{...}` will result in a new array or object, respectively. + +For example, using the given multipath: + +``` +{name.first,age,"the_murphys":friends.#(last="Murphy")#.first} +``` + +Here we selected the first name, age, and the first name for friends with the +last name "Murphy". + +You'll notice that an optional key can be provided, in this case +"the_murphys", to force assign a key to a value. Otherwise, the name of the +actual field will be used, in this case "first". If a name cannot be +determined, then "_" is used. + +This results in + +```json +{"first":"Tom","age":37,"the_murphys":["Dale","Jane"]} +``` + +### Literals + +Starting with v1.12.0, GJSON added support of json literals, which provides a way for constructing static blocks of json. This is can be particularly useful when constructing a new json document using [multipaths](#multipaths). + +A json literal begins with the '!' declaration character. + +For example, using the given multipath: + +``` +{name.first,age,"company":!"Happysoft","employed":!true} +``` + +Here we selected the first name and age. Then add two new fields, "company" and "employed". + +This results in + +```json +{"first":"Tom","age":37,"company":"Happysoft","employed":true} +``` + +*See issue [#249](https://github.com/tidwall/gjson/issues/249) for additional context on JSON Literals.* diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/gjson.go b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/gjson.go new file mode 100644 index 00000000..53cbd236 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/gjson.go @@ -0,0 +1,3359 @@ +// Package gjson provides searching for json strings. +package gjson + +import ( + "strconv" + "strings" + "time" + "unicode/utf16" + "unicode/utf8" + "unsafe" + + "github.com/tidwall/match" + "github.com/tidwall/pretty" +) + +// Type is Result type +type Type int + +const ( + // Null is a null json value + Null Type = iota + // False is a json false boolean + False + // Number is json number + Number + // String is a json string + String + // True is a json true boolean + True + // JSON is a raw block of JSON + JSON +) + +// String returns a string representation of the type. +func (t Type) String() string { + switch t { + default: + return "" + case Null: + return "Null" + case False: + return "False" + case Number: + return "Number" + case String: + return "String" + case True: + return "True" + case JSON: + return "JSON" + } +} + +// Result represents a json value that is returned from Get(). +type Result struct { + // Type is the json type + Type Type + // Raw is the raw json + Raw string + // Str is the json string + Str string + // Num is the json number + Num float64 + // Index of raw value in original json, zero means index unknown + Index int + // Indexes of all the elements that match on a path containing the '#' + // query character. + Indexes []int +} + +// String returns a string representation of the value. +func (t Result) String() string { + switch t.Type { + default: + return "" + case False: + return "false" + case Number: + if len(t.Raw) == 0 { + // calculated result + return strconv.FormatFloat(t.Num, 'f', -1, 64) + } + var i int + if t.Raw[0] == '-' { + i++ + } + for ; i < len(t.Raw); i++ { + if t.Raw[i] < '0' || t.Raw[i] > '9' { + return strconv.FormatFloat(t.Num, 'f', -1, 64) + } + } + return t.Raw + case String: + return t.Str + case JSON: + return t.Raw + case True: + return "true" + } +} + +// Bool returns an boolean representation. +func (t Result) Bool() bool { + switch t.Type { + default: + return false + case True: + return true + case String: + b, _ := strconv.ParseBool(strings.ToLower(t.Str)) + return b + case Number: + return t.Num != 0 + } +} + +// Int returns an integer representation. +func (t Result) Int() int64 { + switch t.Type { + default: + return 0 + case True: + return 1 + case String: + n, _ := parseInt(t.Str) + return n + case Number: + // try to directly convert the float64 to int64 + i, ok := safeInt(t.Num) + if ok { + return i + } + // now try to parse the raw string + i, ok = parseInt(t.Raw) + if ok { + return i + } + // fallback to a standard conversion + return int64(t.Num) + } +} + +// Uint returns an unsigned integer representation. +func (t Result) Uint() uint64 { + switch t.Type { + default: + return 0 + case True: + return 1 + case String: + n, _ := parseUint(t.Str) + return n + case Number: + // try to directly convert the float64 to uint64 + i, ok := safeInt(t.Num) + if ok && i >= 0 { + return uint64(i) + } + // now try to parse the raw string + u, ok := parseUint(t.Raw) + if ok { + return u + } + // fallback to a standard conversion + return uint64(t.Num) + } +} + +// Float returns an float64 representation. +func (t Result) Float() float64 { + switch t.Type { + default: + return 0 + case True: + return 1 + case String: + n, _ := strconv.ParseFloat(t.Str, 64) + return n + case Number: + return t.Num + } +} + +// Time returns a time.Time representation. +func (t Result) Time() time.Time { + res, _ := time.Parse(time.RFC3339, t.String()) + return res +} + +// Array returns back an array of values. +// If the result represents a null value or is non-existent, then an empty +// array will be returned. +// If the result is not a JSON array, the return value will be an +// array containing one result. +func (t Result) Array() []Result { + if t.Type == Null { + return []Result{} + } + if !t.IsArray() { + return []Result{t} + } + r := t.arrayOrMap('[', false) + return r.a +} + +// IsObject returns true if the result value is a JSON object. +func (t Result) IsObject() bool { + return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '{' +} + +// IsArray returns true if the result value is a JSON array. +func (t Result) IsArray() bool { + return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '[' +} + +// IsBool returns true if the result value is a JSON boolean. +func (t Result) IsBool() bool { + return t.Type == True || t.Type == False +} + +// ForEach iterates through values. +// If the result represents a non-existent value, then no values will be +// iterated. If the result is an Object, the iterator will pass the key and +// value of each item. If the result is an Array, the iterator will only pass +// the value of each item. If the result is not a JSON array or object, the +// iterator will pass back one value equal to the result. +func (t Result) ForEach(iterator func(key, value Result) bool) { + if !t.Exists() { + return + } + if t.Type != JSON { + iterator(Result{}, t) + return + } + json := t.Raw + var obj bool + var i int + var key, value Result + for ; i < len(json); i++ { + if json[i] == '{' { + i++ + key.Type = String + obj = true + break + } else if json[i] == '[' { + i++ + key.Type = Number + key.Num = -1 + break + } + if json[i] > ' ' { + return + } + } + var str string + var vesc bool + var ok bool + var idx int + for ; i < len(json); i++ { + if obj { + if json[i] != '"' { + continue + } + s := i + i, str, vesc, ok = parseString(json, i+1) + if !ok { + return + } + if vesc { + key.Str = unescape(str[1 : len(str)-1]) + } else { + key.Str = str[1 : len(str)-1] + } + key.Raw = str + key.Index = s + t.Index + } else { + key.Num += 1 + } + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ',' || json[i] == ':' { + continue + } + break + } + s := i + i, value, ok = parseAny(json, i, true) + if !ok { + return + } + if t.Indexes != nil { + if idx < len(t.Indexes) { + value.Index = t.Indexes[idx] + } + } else { + value.Index = s + t.Index + } + if !iterator(key, value) { + return + } + idx++ + } +} + +// Map returns back a map of values. The result should be a JSON object. +// If the result is not a JSON object, the return value will be an empty map. +func (t Result) Map() map[string]Result { + if t.Type != JSON { + return map[string]Result{} + } + r := t.arrayOrMap('{', false) + return r.o +} + +// Get searches result for the specified path. +// The result should be a JSON array or object. +func (t Result) Get(path string) Result { + r := Get(t.Raw, path) + if r.Indexes != nil { + for i := 0; i < len(r.Indexes); i++ { + r.Indexes[i] += t.Index + } + } else { + r.Index += t.Index + } + return r +} + +type arrayOrMapResult struct { + a []Result + ai []interface{} + o map[string]Result + oi map[string]interface{} + vc byte +} + +func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) { + var json = t.Raw + var i int + var value Result + var count int + var key Result + if vc == 0 { + for ; i < len(json); i++ { + if json[i] == '{' || json[i] == '[' { + r.vc = json[i] + i++ + break + } + if json[i] > ' ' { + goto end + } + } + } else { + for ; i < len(json); i++ { + if json[i] == vc { + i++ + break + } + if json[i] > ' ' { + goto end + } + } + r.vc = vc + } + if r.vc == '{' { + if valueize { + r.oi = make(map[string]interface{}) + } else { + r.o = make(map[string]Result) + } + } else { + if valueize { + r.ai = make([]interface{}, 0) + } else { + r.a = make([]Result, 0) + } + } + for ; i < len(json); i++ { + if json[i] <= ' ' { + continue + } + // get next value + if json[i] == ']' || json[i] == '}' { + break + } + switch json[i] { + default: + if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' { + value.Type = Number + value.Raw, value.Num = tonum(json[i:]) + value.Str = "" + } else { + continue + } + case '{', '[': + value.Type = JSON + value.Raw = squash(json[i:]) + value.Str, value.Num = "", 0 + case 'n': + value.Type = Null + value.Raw = tolit(json[i:]) + value.Str, value.Num = "", 0 + case 't': + value.Type = True + value.Raw = tolit(json[i:]) + value.Str, value.Num = "", 0 + case 'f': + value.Type = False + value.Raw = tolit(json[i:]) + value.Str, value.Num = "", 0 + case '"': + value.Type = String + value.Raw, value.Str = tostr(json[i:]) + value.Num = 0 + } + value.Index = i + t.Index + + i += len(value.Raw) - 1 + + if r.vc == '{' { + if count%2 == 0 { + key = value + } else { + if valueize { + if _, ok := r.oi[key.Str]; !ok { + r.oi[key.Str] = value.Value() + } + } else { + if _, ok := r.o[key.Str]; !ok { + r.o[key.Str] = value + } + } + } + count++ + } else { + if valueize { + r.ai = append(r.ai, value.Value()) + } else { + r.a = append(r.a, value) + } + } + } +end: + if t.Indexes != nil { + if len(t.Indexes) != len(r.a) { + for i := 0; i < len(r.a); i++ { + r.a[i].Index = 0 + } + } else { + for i := 0; i < len(r.a); i++ { + r.a[i].Index = t.Indexes[i] + } + } + } + return +} + +// Parse parses the json and returns a result. +// +// This function expects that the json is well-formed, and does not validate. +// Invalid json will not panic, but it may return back unexpected results. +// If you are consuming JSON from an unpredictable source then you may want to +// use the Valid function first. +func Parse(json string) Result { + var value Result + i := 0 + for ; i < len(json); i++ { + if json[i] == '{' || json[i] == '[' { + value.Type = JSON + value.Raw = json[i:] // just take the entire raw + break + } + if json[i] <= ' ' { + continue + } + switch json[i] { + case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'i', 'I', 'N': + value.Type = Number + value.Raw, value.Num = tonum(json[i:]) + case 'n': + if i+1 < len(json) && json[i+1] != 'u' { + // nan + value.Type = Number + value.Raw, value.Num = tonum(json[i:]) + } else { + // null + value.Type = Null + value.Raw = tolit(json[i:]) + } + case 't': + value.Type = True + value.Raw = tolit(json[i:]) + case 'f': + value.Type = False + value.Raw = tolit(json[i:]) + case '"': + value.Type = String + value.Raw, value.Str = tostr(json[i:]) + default: + return Result{} + } + break + } + if value.Exists() { + value.Index = i + } + return value +} + +// ParseBytes parses the json and returns a result. +// If working with bytes, this method preferred over Parse(string(data)) +func ParseBytes(json []byte) Result { + return Parse(string(json)) +} + +func squash(json string) string { + // expects that the lead character is a '[' or '{' or '(' or '"' + // squash the value, ignoring all nested arrays and objects. + var i, depth int + if json[0] != '"' { + i, depth = 1, 1 + } + for ; i < len(json); i++ { + if json[i] >= '"' && json[i] <= '}' { + switch json[i] { + case '"': + i++ + s2 := i + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > s2-1; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + if depth == 0 { + if i >= len(json) { + return json + } + return json[:i+1] + } + case '{', '[', '(': + depth++ + case '}', ']', ')': + depth-- + if depth == 0 { + return json[:i+1] + } + } + } + } + return json +} + +func tonum(json string) (raw string, num float64) { + for i := 1; i < len(json); i++ { + // less than dash might have valid characters + if json[i] <= '-' { + if json[i] <= ' ' || json[i] == ',' { + // break on whitespace and comma + raw = json[:i] + num, _ = strconv.ParseFloat(raw, 64) + return + } + // could be a '+' or '-'. let's assume so. + } else if json[i] == ']' || json[i] == '}' { + // break on ']' or '}' + raw = json[:i] + num, _ = strconv.ParseFloat(raw, 64) + return + } + } + raw = json + num, _ = strconv.ParseFloat(raw, 64) + return +} + +func tolit(json string) (raw string) { + for i := 1; i < len(json); i++ { + if json[i] < 'a' || json[i] > 'z' { + return json[:i] + } + } + return json +} + +func tostr(json string) (raw string, str string) { + // expects that the lead character is a '"' + for i := 1; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + return json[:i+1], json[1:i] + } + if json[i] == '\\' { + i++ + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > 0; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + return json[:i+1], unescape(json[1:i]) + } + } + var ret string + if i+1 < len(json) { + ret = json[:i+1] + } else { + ret = json[:i] + } + return ret, unescape(json[1:i]) + } + } + return json, json[1:] +} + +// Exists returns true if value exists. +// +// if gjson.Get(json, "name.last").Exists(){ +// println("value exists") +// } +func (t Result) Exists() bool { + return t.Type != Null || len(t.Raw) != 0 +} + +// Value returns one of these types: +// +// bool, for JSON booleans +// float64, for JSON numbers +// Number, for JSON numbers +// string, for JSON string literals +// nil, for JSON null +// map[string]interface{}, for JSON objects +// []interface{}, for JSON arrays +// +func (t Result) Value() interface{} { + if t.Type == String { + return t.Str + } + switch t.Type { + default: + return nil + case False: + return false + case Number: + return t.Num + case JSON: + r := t.arrayOrMap(0, true) + if r.vc == '{' { + return r.oi + } else if r.vc == '[' { + return r.ai + } + return nil + case True: + return true + } +} + +func parseString(json string, i int) (int, string, bool, bool) { + var s = i + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + return i + 1, json[s-1 : i+1], false, true + } + if json[i] == '\\' { + i++ + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > 0; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + return i + 1, json[s-1 : i+1], true, true + } + } + break + } + } + return i, json[s-1:], false, false +} + +func parseNumber(json string, i int) (int, string) { + var s = i + i++ + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ',' || json[i] == ']' || + json[i] == '}' { + return i, json[s:i] + } + } + return i, json[s:] +} + +func parseLiteral(json string, i int) (int, string) { + var s = i + i++ + for ; i < len(json); i++ { + if json[i] < 'a' || json[i] > 'z' { + return i, json[s:i] + } + } + return i, json[s:] +} + +type arrayPathResult struct { + part string + path string + pipe string + piped bool + more bool + alogok bool + arrch bool + alogkey string + query struct { + on bool + all bool + path string + op string + value string + } +} + +func parseArrayPath(path string) (r arrayPathResult) { + for i := 0; i < len(path); i++ { + if path[i] == '|' { + r.part = path[:i] + r.pipe = path[i+1:] + r.piped = true + return + } + if path[i] == '.' { + r.part = path[:i] + if !r.arrch && i < len(path)-1 && isDotPiperChar(path[i+1:]) { + r.pipe = path[i+1:] + r.piped = true + } else { + r.path = path[i+1:] + r.more = true + } + return + } + if path[i] == '#' { + r.arrch = true + if i == 0 && len(path) > 1 { + if path[1] == '.' { + r.alogok = true + r.alogkey = path[2:] + r.path = path[:1] + } else if path[1] == '[' || path[1] == '(' { + // query + r.query.on = true + qpath, op, value, _, fi, vesc, ok := + parseQuery(path[i:]) + if !ok { + // bad query, end now + break + } + if len(value) >= 2 && value[0] == '"' && + value[len(value)-1] == '"' { + value = value[1 : len(value)-1] + if vesc { + value = unescape(value) + } + } + r.query.path = qpath + r.query.op = op + r.query.value = value + + i = fi - 1 + if i+1 < len(path) && path[i+1] == '#' { + r.query.all = true + } + } + } + continue + } + } + r.part = path + r.path = "" + return +} + +// splitQuery takes a query and splits it into three parts: +// path, op, middle, and right. +// So for this query: +// #(first_name=="Murphy").last +// Becomes +// first_name # path +// =="Murphy" # middle +// .last # right +// Or, +// #(service_roles.#(=="one")).cap +// Becomes +// service_roles.#(=="one") # path +// # middle +// .cap # right +func parseQuery(query string) ( + path, op, value, remain string, i int, vesc, ok bool, +) { + if len(query) < 2 || query[0] != '#' || + (query[1] != '(' && query[1] != '[') { + return "", "", "", "", i, false, false + } + i = 2 + j := 0 // start of value part + depth := 1 + for ; i < len(query); i++ { + if depth == 1 && j == 0 { + switch query[i] { + case '!', '=', '<', '>', '%': + // start of the value part + j = i + continue + } + } + if query[i] == '\\' { + i++ + } else if query[i] == '[' || query[i] == '(' { + depth++ + } else if query[i] == ']' || query[i] == ')' { + depth-- + if depth == 0 { + break + } + } else if query[i] == '"' { + // inside selector string, balance quotes + i++ + for ; i < len(query); i++ { + if query[i] == '\\' { + vesc = true + i++ + } else if query[i] == '"' { + break + } + } + } + } + if depth > 0 { + return "", "", "", "", i, false, false + } + if j > 0 { + path = trim(query[2:j]) + value = trim(query[j:i]) + remain = query[i+1:] + // parse the compare op from the value + var opsz int + switch { + case len(value) == 1: + opsz = 1 + case value[0] == '!' && value[1] == '=': + opsz = 2 + case value[0] == '!' && value[1] == '%': + opsz = 2 + case value[0] == '<' && value[1] == '=': + opsz = 2 + case value[0] == '>' && value[1] == '=': + opsz = 2 + case value[0] == '=' && value[1] == '=': + value = value[1:] + opsz = 1 + case value[0] == '<': + opsz = 1 + case value[0] == '>': + opsz = 1 + case value[0] == '=': + opsz = 1 + case value[0] == '%': + opsz = 1 + } + op = value[:opsz] + value = trim(value[opsz:]) + } else { + path = trim(query[2:i]) + remain = query[i+1:] + } + return path, op, value, remain, i + 1, vesc, true +} + +func trim(s string) string { +left: + if len(s) > 0 && s[0] <= ' ' { + s = s[1:] + goto left + } +right: + if len(s) > 0 && s[len(s)-1] <= ' ' { + s = s[:len(s)-1] + goto right + } + return s +} + +// peek at the next byte and see if it's a '@', '[', or '{'. +func isDotPiperChar(s string) bool { + if DisableModifiers { + return false + } + c := s[0] + if c == '@' { + // check that the next component is *not* a modifier. + i := 1 + for ; i < len(s); i++ { + if s[i] == '.' || s[i] == '|' || s[i] == ':' { + break + } + } + _, ok := modifiers[s[1:i]] + return ok + } + return c == '[' || c == '{' +} + +type objectPathResult struct { + part string + path string + pipe string + piped bool + wild bool + more bool +} + +func parseObjectPath(path string) (r objectPathResult) { + for i := 0; i < len(path); i++ { + if path[i] == '|' { + r.part = path[:i] + r.pipe = path[i+1:] + r.piped = true + return + } + if path[i] == '.' { + r.part = path[:i] + if i < len(path)-1 && isDotPiperChar(path[i+1:]) { + r.pipe = path[i+1:] + r.piped = true + } else { + r.path = path[i+1:] + r.more = true + } + return + } + if path[i] == '*' || path[i] == '?' { + r.wild = true + continue + } + if path[i] == '\\' { + // go into escape mode. this is a slower path that + // strips off the escape character from the part. + epart := []byte(path[:i]) + i++ + if i < len(path) { + epart = append(epart, path[i]) + i++ + for ; i < len(path); i++ { + if path[i] == '\\' { + i++ + if i < len(path) { + epart = append(epart, path[i]) + } + continue + } else if path[i] == '.' { + r.part = string(epart) + if i < len(path)-1 && isDotPiperChar(path[i+1:]) { + r.pipe = path[i+1:] + r.piped = true + } else { + r.path = path[i+1:] + r.more = true + } + return + } else if path[i] == '|' { + r.part = string(epart) + r.pipe = path[i+1:] + r.piped = true + return + } else if path[i] == '*' || path[i] == '?' { + r.wild = true + } + epart = append(epart, path[i]) + } + } + // append the last part + r.part = string(epart) + return + } + } + r.part = path + return +} + +func parseSquash(json string, i int) (int, string) { + // expects that the lead character is a '[' or '{' or '(' + // squash the value, ignoring all nested arrays and objects. + // the first '[' or '{' or '(' has already been read + s := i + i++ + depth := 1 + for ; i < len(json); i++ { + if json[i] >= '"' && json[i] <= '}' { + switch json[i] { + case '"': + i++ + s2 := i + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > s2-1; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + case '{', '[', '(': + depth++ + case '}', ']', ')': + depth-- + if depth == 0 { + i++ + return i, json[s:i] + } + } + } + } + return i, json[s:] +} + +func parseObject(c *parseContext, i int, path string) (int, bool) { + var pmatch, kesc, vesc, ok, hit bool + var key, val string + rp := parseObjectPath(path) + if !rp.more && rp.piped { + c.pipe = rp.pipe + c.piped = true + } + for i < len(c.json) { + for ; i < len(c.json); i++ { + if c.json[i] == '"' { + // parse_key_string + // this is slightly different from getting s string value + // because we don't need the outer quotes. + i++ + var s = i + for ; i < len(c.json); i++ { + if c.json[i] > '\\' { + continue + } + if c.json[i] == '"' { + i, key, kesc, ok = i+1, c.json[s:i], false, true + goto parse_key_string_done + } + if c.json[i] == '\\' { + i++ + for ; i < len(c.json); i++ { + if c.json[i] > '\\' { + continue + } + if c.json[i] == '"' { + // look for an escaped slash + if c.json[i-1] == '\\' { + n := 0 + for j := i - 2; j > 0; j-- { + if c.json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + i, key, kesc, ok = i+1, c.json[s:i], true, true + goto parse_key_string_done + } + } + break + } + } + key, kesc, ok = c.json[s:], false, false + parse_key_string_done: + break + } + if c.json[i] == '}' { + return i + 1, false + } + } + if !ok { + return i, false + } + if rp.wild { + if kesc { + pmatch = matchLimit(unescape(key), rp.part) + } else { + pmatch = matchLimit(key, rp.part) + } + } else { + if kesc { + pmatch = rp.part == unescape(key) + } else { + pmatch = rp.part == key + } + } + hit = pmatch && !rp.more + for ; i < len(c.json); i++ { + var num bool + switch c.json[i] { + default: + continue + case '"': + i++ + i, val, vesc, ok = parseString(c.json, i) + if !ok { + return i, false + } + if hit { + if vesc { + c.value.Str = unescape(val[1 : len(val)-1]) + } else { + c.value.Str = val[1 : len(val)-1] + } + c.value.Raw = val + c.value.Type = String + return i, true + } + case '{': + if pmatch && !hit { + i, hit = parseObject(c, i+1, rp.path) + if hit { + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if hit { + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case '[': + if pmatch && !hit { + i, hit = parseArray(c, i+1, rp.path) + if hit { + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if hit { + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case 'n': + if i+1 < len(c.json) && c.json[i+1] != 'u' { + num = true + break + } + fallthrough + case 't', 'f': + vc := c.json[i] + i, val = parseLiteral(c.json, i) + if hit { + c.value.Raw = val + switch vc { + case 't': + c.value.Type = True + case 'f': + c.value.Type = False + } + return i, true + } + case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'i', 'I', 'N': + num = true + } + if num { + i, val = parseNumber(c.json, i) + if hit { + c.value.Raw = val + c.value.Type = Number + c.value.Num, _ = strconv.ParseFloat(val, 64) + return i, true + } + } + break + } + } + return i, false +} + +// matchLimit will limit the complexity of the match operation to avoid ReDos +// attacks from arbritary inputs. +// See the github.com/tidwall/match.MatchLimit function for more information. +func matchLimit(str, pattern string) bool { + matched, _ := match.MatchLimit(str, pattern, 10000) + return matched +} + +func queryMatches(rp *arrayPathResult, value Result) bool { + rpv := rp.query.value + if len(rpv) > 0 && rpv[0] == '~' { + // convert to bool + rpv = rpv[1:] + if value.Bool() { + value = Result{Type: True} + } else { + value = Result{Type: False} + } + } + if !value.Exists() { + return false + } + if rp.query.op == "" { + // the query is only looking for existence, such as: + // friends.#(name) + // which makes sure that the array "friends" has an element of + // "name" that exists + return true + } + switch value.Type { + case String: + switch rp.query.op { + case "=": + return value.Str == rpv + case "!=": + return value.Str != rpv + case "<": + return value.Str < rpv + case "<=": + return value.Str <= rpv + case ">": + return value.Str > rpv + case ">=": + return value.Str >= rpv + case "%": + return matchLimit(value.Str, rpv) + case "!%": + return !matchLimit(value.Str, rpv) + } + case Number: + rpvn, _ := strconv.ParseFloat(rpv, 64) + switch rp.query.op { + case "=": + return value.Num == rpvn + case "!=": + return value.Num != rpvn + case "<": + return value.Num < rpvn + case "<=": + return value.Num <= rpvn + case ">": + return value.Num > rpvn + case ">=": + return value.Num >= rpvn + } + case True: + switch rp.query.op { + case "=": + return rpv == "true" + case "!=": + return rpv != "true" + case ">": + return rpv == "false" + case ">=": + return true + } + case False: + switch rp.query.op { + case "=": + return rpv == "false" + case "!=": + return rpv != "false" + case "<": + return rpv == "true" + case "<=": + return true + } + } + return false +} +func parseArray(c *parseContext, i int, path string) (int, bool) { + var pmatch, vesc, ok, hit bool + var val string + var h int + var alog []int + var partidx int + var multires []byte + var queryIndexes []int + rp := parseArrayPath(path) + if !rp.arrch { + n, ok := parseUint(rp.part) + if !ok { + partidx = -1 + } else { + partidx = int(n) + } + } + if !rp.more && rp.piped { + c.pipe = rp.pipe + c.piped = true + } + + procQuery := func(qval Result) bool { + if rp.query.all { + if len(multires) == 0 { + multires = append(multires, '[') + } + } + var tmp parseContext + tmp.value = qval + fillIndex(c.json, &tmp) + parentIndex := tmp.value.Index + var res Result + if qval.Type == JSON { + res = qval.Get(rp.query.path) + } else { + if rp.query.path != "" { + return false + } + res = qval + } + if queryMatches(&rp, res) { + if rp.more { + left, right, ok := splitPossiblePipe(rp.path) + if ok { + rp.path = left + c.pipe = right + c.piped = true + } + res = qval.Get(rp.path) + } else { + res = qval + } + if rp.query.all { + raw := res.Raw + if len(raw) == 0 { + raw = res.String() + } + if raw != "" { + if len(multires) > 1 { + multires = append(multires, ',') + } + multires = append(multires, raw...) + queryIndexes = append(queryIndexes, res.Index+parentIndex) + } + } else { + c.value = res + return true + } + } + return false + } + for i < len(c.json)+1 { + if !rp.arrch { + pmatch = partidx == h + hit = pmatch && !rp.more + } + h++ + if rp.alogok { + alog = append(alog, i) + } + for ; ; i++ { + var ch byte + if i > len(c.json) { + break + } else if i == len(c.json) { + ch = ']' + } else { + ch = c.json[i] + } + var num bool + switch ch { + default: + continue + case '"': + i++ + i, val, vesc, ok = parseString(c.json, i) + if !ok { + return i, false + } + if rp.query.on { + var qval Result + if vesc { + qval.Str = unescape(val[1 : len(val)-1]) + } else { + qval.Str = val[1 : len(val)-1] + } + qval.Raw = val + qval.Type = String + if procQuery(qval) { + return i, true + } + } else if hit { + if rp.alogok { + break + } + if vesc { + c.value.Str = unescape(val[1 : len(val)-1]) + } else { + c.value.Str = val[1 : len(val)-1] + } + c.value.Raw = val + c.value.Type = String + return i, true + } + case '{': + if pmatch && !hit { + i, hit = parseObject(c, i+1, rp.path) + if hit { + if rp.alogok { + break + } + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if rp.query.on { + if procQuery(Result{Raw: val, Type: JSON}) { + return i, true + } + } else if hit { + if rp.alogok { + break + } + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case '[': + if pmatch && !hit { + i, hit = parseArray(c, i+1, rp.path) + if hit { + if rp.alogok { + break + } + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if rp.query.on { + if procQuery(Result{Raw: val, Type: JSON}) { + return i, true + } + } else if hit { + if rp.alogok { + break + } + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case 'n': + if i+1 < len(c.json) && c.json[i+1] != 'u' { + num = true + break + } + fallthrough + case 't', 'f': + vc := c.json[i] + i, val = parseLiteral(c.json, i) + if rp.query.on { + var qval Result + qval.Raw = val + switch vc { + case 't': + qval.Type = True + case 'f': + qval.Type = False + } + if procQuery(qval) { + return i, true + } + } else if hit { + if rp.alogok { + break + } + c.value.Raw = val + switch vc { + case 't': + c.value.Type = True + case 'f': + c.value.Type = False + } + return i, true + } + case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'i', 'I', 'N': + num = true + case ']': + if rp.arrch && rp.part == "#" { + if rp.alogok { + left, right, ok := splitPossiblePipe(rp.alogkey) + if ok { + rp.alogkey = left + c.pipe = right + c.piped = true + } + var indexes = make([]int, 0, 64) + var jsons = make([]byte, 0, 64) + jsons = append(jsons, '[') + for j, k := 0, 0; j < len(alog); j++ { + idx := alog[j] + for idx < len(c.json) { + switch c.json[idx] { + case ' ', '\t', '\r', '\n': + idx++ + continue + } + break + } + if idx < len(c.json) && c.json[idx] != ']' { + _, res, ok := parseAny(c.json, idx, true) + if ok { + res := res.Get(rp.alogkey) + if res.Exists() { + if k > 0 { + jsons = append(jsons, ',') + } + raw := res.Raw + if len(raw) == 0 { + raw = res.String() + } + jsons = append(jsons, []byte(raw)...) + indexes = append(indexes, res.Index) + k++ + } + } + } + } + jsons = append(jsons, ']') + c.value.Type = JSON + c.value.Raw = string(jsons) + c.value.Indexes = indexes + return i + 1, true + } + if rp.alogok { + break + } + + c.value.Type = Number + c.value.Num = float64(h - 1) + c.value.Raw = strconv.Itoa(h - 1) + c.calcd = true + return i + 1, true + } + if !c.value.Exists() { + if len(multires) > 0 { + c.value = Result{ + Raw: string(append(multires, ']')), + Type: JSON, + Indexes: queryIndexes, + } + } else if rp.query.all { + c.value = Result{ + Raw: "[]", + Type: JSON, + } + } + } + return i + 1, false + } + if num { + i, val = parseNumber(c.json, i) + if rp.query.on { + var qval Result + qval.Raw = val + qval.Type = Number + qval.Num, _ = strconv.ParseFloat(val, 64) + if procQuery(qval) { + return i, true + } + } else if hit { + if rp.alogok { + break + } + c.value.Raw = val + c.value.Type = Number + c.value.Num, _ = strconv.ParseFloat(val, 64) + return i, true + } + } + break + } + } + return i, false +} + +func splitPossiblePipe(path string) (left, right string, ok bool) { + // take a quick peek for the pipe character. If found we'll split the piped + // part of the path into the c.pipe field and shorten the rp. + var possible bool + for i := 0; i < len(path); i++ { + if path[i] == '|' { + possible = true + break + } + } + if !possible { + return + } + + if len(path) > 0 && path[0] == '{' { + squashed := squash(path[1:]) + if len(squashed) < len(path)-1 { + squashed = path[:len(squashed)+1] + remain := path[len(squashed):] + if remain[0] == '|' { + return squashed, remain[1:], true + } + } + return + } + + // split the left and right side of the path with the pipe character as + // the delimiter. This is a little tricky because we'll need to basically + // parse the entire path. + for i := 0; i < len(path); i++ { + if path[i] == '\\' { + i++ + } else if path[i] == '.' { + if i == len(path)-1 { + return + } + if path[i+1] == '#' { + i += 2 + if i == len(path) { + return + } + if path[i] == '[' || path[i] == '(' { + var start, end byte + if path[i] == '[' { + start, end = '[', ']' + } else { + start, end = '(', ')' + } + // inside selector, balance brackets + i++ + depth := 1 + for ; i < len(path); i++ { + if path[i] == '\\' { + i++ + } else if path[i] == start { + depth++ + } else if path[i] == end { + depth-- + if depth == 0 { + break + } + } else if path[i] == '"' { + // inside selector string, balance quotes + i++ + for ; i < len(path); i++ { + if path[i] == '\\' { + i++ + } else if path[i] == '"' { + break + } + } + } + } + } + } + } else if path[i] == '|' { + return path[:i], path[i+1:], true + } + } + return +} + +// ForEachLine iterates through lines of JSON as specified by the JSON Lines +// format (http://jsonlines.org/). +// Each line is returned as a GJSON Result. +func ForEachLine(json string, iterator func(line Result) bool) { + var res Result + var i int + for { + i, res, _ = parseAny(json, i, true) + if !res.Exists() { + break + } + if !iterator(res) { + return + } + } +} + +type subSelector struct { + name string + path string +} + +// parseSubSelectors returns the subselectors belonging to a '[path1,path2]' or +// '{"field1":path1,"field2":path2}' type subSelection. It's expected that the +// first character in path is either '[' or '{', and has already been checked +// prior to calling this function. +func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) { + modifier := 0 + depth := 1 + colon := 0 + start := 1 + i := 1 + pushSel := func() { + var sel subSelector + if colon == 0 { + sel.path = path[start:i] + } else { + sel.name = path[start:colon] + sel.path = path[colon+1 : i] + } + sels = append(sels, sel) + colon = 0 + modifier = 0 + start = i + 1 + } + for ; i < len(path); i++ { + switch path[i] { + case '\\': + i++ + case '@': + if modifier == 0 && i > 0 && (path[i-1] == '.' || path[i-1] == '|') { + modifier = i + } + case ':': + if modifier == 0 && colon == 0 && depth == 1 { + colon = i + } + case ',': + if depth == 1 { + pushSel() + } + case '"': + i++ + loop: + for ; i < len(path); i++ { + switch path[i] { + case '\\': + i++ + case '"': + break loop + } + } + case '[', '(', '{': + depth++ + case ']', ')', '}': + depth-- + if depth == 0 { + pushSel() + path = path[i+1:] + return sels, path, true + } + } + } + return +} + +// nameOfLast returns the name of the last component +func nameOfLast(path string) string { + for i := len(path) - 1; i >= 0; i-- { + if path[i] == '|' || path[i] == '.' { + if i > 0 { + if path[i-1] == '\\' { + continue + } + } + return path[i+1:] + } + } + return path +} + +func isSimpleName(component string) bool { + for i := 0; i < len(component); i++ { + if component[i] < ' ' { + return false + } + switch component[i] { + case '[', ']', '{', '}', '(', ')', '#', '|', '!': + return false + } + } + return true +} + +var hexchars = [...]byte{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', +} + +func appendHex16(dst []byte, x uint16) []byte { + return append(dst, + hexchars[x>>12&0xF], hexchars[x>>8&0xF], + hexchars[x>>4&0xF], hexchars[x>>0&0xF], + ) +} + +// AppendJSONString is a convenience function that converts the provided string +// to a valid JSON string and appends it to dst. +func AppendJSONString(dst []byte, s string) []byte { + dst = append(dst, make([]byte, len(s)+2)...) + dst = append(dst[:len(dst)-len(s)-2], '"') + for i := 0; i < len(s); i++ { + if s[i] < ' ' { + dst = append(dst, '\\') + switch s[i] { + case '\n': + dst = append(dst, 'n') + case '\r': + dst = append(dst, 'r') + case '\t': + dst = append(dst, 't') + default: + dst = append(dst, 'u') + dst = appendHex16(dst, uint16(s[i])) + } + } else if s[i] == '>' || s[i] == '<' || s[i] == '&' { + dst = append(dst, '\\', 'u') + dst = appendHex16(dst, uint16(s[i])) + } else if s[i] == '\\' { + dst = append(dst, '\\', '\\') + } else if s[i] == '"' { + dst = append(dst, '\\', '"') + } else if s[i] > 127 { + // read utf8 character + r, n := utf8.DecodeRuneInString(s[i:]) + if n == 0 { + break + } + if r == utf8.RuneError && n == 1 { + dst = append(dst, `\ufffd`...) + } else if r == '\u2028' || r == '\u2029' { + dst = append(dst, `\u202`...) + dst = append(dst, hexchars[r&0xF]) + } else { + dst = append(dst, s[i:i+n]...) + } + i = i + n - 1 + } else { + dst = append(dst, s[i]) + } + } + return append(dst, '"') +} + +type parseContext struct { + json string + value Result + pipe string + piped bool + calcd bool + lines bool +} + +// Get searches json for the specified path. +// A path is in dot syntax, such as "name.last" or "age". +// When the value is found it's returned immediately. +// +// A path is a series of keys separated by a dot. +// A key may contain special wildcard characters '*' and '?'. +// To access an array value use the index as the key. +// To get the number of elements in an array or to access a child path, use +// the '#' character. +// The dot and wildcard character can be escaped with '\'. +// +// { +// "name": {"first": "Tom", "last": "Anderson"}, +// "age":37, +// "children": ["Sara","Alex","Jack"], +// "friends": [ +// {"first": "James", "last": "Murphy"}, +// {"first": "Roger", "last": "Craig"} +// ] +// } +// "name.last" >> "Anderson" +// "age" >> 37 +// "children" >> ["Sara","Alex","Jack"] +// "children.#" >> 3 +// "children.1" >> "Alex" +// "child*.2" >> "Jack" +// "c?ildren.0" >> "Sara" +// "friends.#.first" >> ["James","Roger"] +// +// This function expects that the json is well-formed, and does not validate. +// Invalid json will not panic, but it may return back unexpected results. +// If you are consuming JSON from an unpredictable source then you may want to +// use the Valid function first. +func Get(json, path string) Result { + if len(path) > 1 { + if (path[0] == '@' && !DisableModifiers) || path[0] == '!' { + // possible modifier + var ok bool + var npath string + var rjson string + if path[0] == '@' && !DisableModifiers { + npath, rjson, ok = execModifier(json, path) + } else if path[0] == '!' { + npath, rjson, ok = execStatic(json, path) + } + if ok { + path = npath + if len(path) > 0 && (path[0] == '|' || path[0] == '.') { + res := Get(rjson, path[1:]) + res.Index = 0 + res.Indexes = nil + return res + } + return Parse(rjson) + } + } + if path[0] == '[' || path[0] == '{' { + // using a subselector path + kind := path[0] + var ok bool + var subs []subSelector + subs, path, ok = parseSubSelectors(path) + if ok { + if len(path) == 0 || (path[0] == '|' || path[0] == '.') { + var b []byte + b = append(b, kind) + var i int + for _, sub := range subs { + res := Get(json, sub.path) + if res.Exists() { + if i > 0 { + b = append(b, ',') + } + if kind == '{' { + if len(sub.name) > 0 { + if sub.name[0] == '"' && Valid(sub.name) { + b = append(b, sub.name...) + } else { + b = AppendJSONString(b, sub.name) + } + } else { + last := nameOfLast(sub.path) + if isSimpleName(last) { + b = AppendJSONString(b, last) + } else { + b = AppendJSONString(b, "_") + } + } + b = append(b, ':') + } + var raw string + if len(res.Raw) == 0 { + raw = res.String() + if len(raw) == 0 { + raw = "null" + } + } else { + raw = res.Raw + } + b = append(b, raw...) + i++ + } + } + b = append(b, kind+2) + var res Result + res.Raw = string(b) + res.Type = JSON + if len(path) > 0 { + res = res.Get(path[1:]) + } + res.Index = 0 + return res + } + } + } + } + var i int + var c = &parseContext{json: json} + if len(path) >= 2 && path[0] == '.' && path[1] == '.' { + c.lines = true + parseArray(c, 0, path[2:]) + } else { + for ; i < len(c.json); i++ { + if c.json[i] == '{' { + i++ + parseObject(c, i, path) + break + } + if c.json[i] == '[' { + i++ + parseArray(c, i, path) + break + } + } + } + if c.piped { + res := c.value.Get(c.pipe) + res.Index = 0 + return res + } + fillIndex(json, c) + return c.value +} + +// GetBytes searches json for the specified path. +// If working with bytes, this method preferred over Get(string(data), path) +func GetBytes(json []byte, path string) Result { + return getBytes(json, path) +} + +// runeit returns the rune from the the \uXXXX +func runeit(json string) rune { + n, _ := strconv.ParseUint(json[:4], 16, 64) + return rune(n) +} + +// unescape unescapes a string +func unescape(json string) string { + var str = make([]byte, 0, len(json)) + for i := 0; i < len(json); i++ { + switch { + default: + str = append(str, json[i]) + case json[i] < ' ': + return string(str) + case json[i] == '\\': + i++ + if i >= len(json) { + return string(str) + } + switch json[i] { + default: + return string(str) + case '\\': + str = append(str, '\\') + case '/': + str = append(str, '/') + case 'b': + str = append(str, '\b') + case 'f': + str = append(str, '\f') + case 'n': + str = append(str, '\n') + case 'r': + str = append(str, '\r') + case 't': + str = append(str, '\t') + case '"': + str = append(str, '"') + case 'u': + if i+5 > len(json) { + return string(str) + } + r := runeit(json[i+1:]) + i += 5 + if utf16.IsSurrogate(r) { + // need another code + if len(json[i:]) >= 6 && json[i] == '\\' && + json[i+1] == 'u' { + // we expect it to be correct so just consume it + r = utf16.DecodeRune(r, runeit(json[i+2:])) + i += 6 + } + } + // provide enough space to encode the largest utf8 possible + str = append(str, 0, 0, 0, 0, 0, 0, 0, 0) + n := utf8.EncodeRune(str[len(str)-8:], r) + str = str[:len(str)-8+n] + i-- // backtrack index by one + } + } + } + return string(str) +} + +// Less return true if a token is less than another token. +// The caseSensitive paramater is used when the tokens are Strings. +// The order when comparing two different type is: +// +// Null < False < Number < String < True < JSON +// +func (t Result) Less(token Result, caseSensitive bool) bool { + if t.Type < token.Type { + return true + } + if t.Type > token.Type { + return false + } + if t.Type == String { + if caseSensitive { + return t.Str < token.Str + } + return stringLessInsensitive(t.Str, token.Str) + } + if t.Type == Number { + return t.Num < token.Num + } + return t.Raw < token.Raw +} + +func stringLessInsensitive(a, b string) bool { + for i := 0; i < len(a) && i < len(b); i++ { + if a[i] >= 'A' && a[i] <= 'Z' { + if b[i] >= 'A' && b[i] <= 'Z' { + // both are uppercase, do nothing + if a[i] < b[i] { + return true + } else if a[i] > b[i] { + return false + } + } else { + // a is uppercase, convert a to lowercase + if a[i]+32 < b[i] { + return true + } else if a[i]+32 > b[i] { + return false + } + } + } else if b[i] >= 'A' && b[i] <= 'Z' { + // b is uppercase, convert b to lowercase + if a[i] < b[i]+32 { + return true + } else if a[i] > b[i]+32 { + return false + } + } else { + // neither are uppercase + if a[i] < b[i] { + return true + } else if a[i] > b[i] { + return false + } + } + } + return len(a) < len(b) +} + +// parseAny parses the next value from a json string. +// A Result is returned when the hit param is set. +// The return values are (i int, res Result, ok bool) +func parseAny(json string, i int, hit bool) (int, Result, bool) { + var res Result + var val string + for ; i < len(json); i++ { + if json[i] == '{' || json[i] == '[' { + i, val = parseSquash(json, i) + if hit { + res.Raw = val + res.Type = JSON + } + var tmp parseContext + tmp.value = res + fillIndex(json, &tmp) + return i, tmp.value, true + } + if json[i] <= ' ' { + continue + } + var num bool + switch json[i] { + case '"': + i++ + var vesc bool + var ok bool + i, val, vesc, ok = parseString(json, i) + if !ok { + return i, res, false + } + if hit { + res.Type = String + res.Raw = val + if vesc { + res.Str = unescape(val[1 : len(val)-1]) + } else { + res.Str = val[1 : len(val)-1] + } + } + return i, res, true + case 'n': + if i+1 < len(json) && json[i+1] != 'u' { + num = true + break + } + fallthrough + case 't', 'f': + vc := json[i] + i, val = parseLiteral(json, i) + if hit { + res.Raw = val + switch vc { + case 't': + res.Type = True + case 'f': + res.Type = False + } + return i, res, true + } + case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'i', 'I', 'N': + num = true + } + if num { + i, val = parseNumber(json, i) + if hit { + res.Raw = val + res.Type = Number + res.Num, _ = strconv.ParseFloat(val, 64) + } + return i, res, true + } + + } + return i, res, false +} + +// GetMany searches json for the multiple paths. +// The return value is a Result array where the number of items +// will be equal to the number of input paths. +func GetMany(json string, path ...string) []Result { + res := make([]Result, len(path)) + for i, path := range path { + res[i] = Get(json, path) + } + return res +} + +// GetManyBytes searches json for the multiple paths. +// The return value is a Result array where the number of items +// will be equal to the number of input paths. +func GetManyBytes(json []byte, path ...string) []Result { + res := make([]Result, len(path)) + for i, path := range path { + res[i] = GetBytes(json, path) + } + return res +} + +func validpayload(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + i, ok = validany(data, i) + if !ok { + return i, false + } + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + } + } + return i, true + case ' ', '\t', '\n', '\r': + continue + } + } + return i, false +} +func validany(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case '{': + return validobject(data, i+1) + case '[': + return validarray(data, i+1) + case '"': + return validstring(data, i+1) + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return validnumber(data, i+1) + case 't': + return validtrue(data, i+1) + case 'f': + return validfalse(data, i+1) + case 'n': + return validnull(data, i+1) + } + } + return i, false +} +func validobject(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case '}': + return i + 1, true + case '"': + key: + if i, ok = validstring(data, i+1); !ok { + return i, false + } + if i, ok = validcolon(data, i); !ok { + return i, false + } + if i, ok = validany(data, i); !ok { + return i, false + } + if i, ok = validcomma(data, i, '}'); !ok { + return i, false + } + if data[i] == '}' { + return i + 1, true + } + i++ + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case '"': + goto key + } + } + return i, false + } + } + return i, false +} +func validcolon(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case ':': + return i + 1, true + } + } + return i, false +} +func validcomma(data []byte, i int, end byte) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case ',': + return i, true + case end: + return i, true + } + } + return i, false +} +func validarray(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + for ; i < len(data); i++ { + if i, ok = validany(data, i); !ok { + return i, false + } + if i, ok = validcomma(data, i, ']'); !ok { + return i, false + } + if data[i] == ']' { + return i + 1, true + } + } + case ' ', '\t', '\n', '\r': + continue + case ']': + return i + 1, true + } + } + return i, false +} +func validstring(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + if data[i] < ' ' { + return i, false + } else if data[i] == '\\' { + i++ + if i == len(data) { + return i, false + } + switch data[i] { + default: + return i, false + case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': + case 'u': + for j := 0; j < 4; j++ { + i++ + if i >= len(data) { + return i, false + } + if !((data[i] >= '0' && data[i] <= '9') || + (data[i] >= 'a' && data[i] <= 'f') || + (data[i] >= 'A' && data[i] <= 'F')) { + return i, false + } + } + } + } else if data[i] == '"' { + return i + 1, true + } + } + return i, false +} +func validnumber(data []byte, i int) (outi int, ok bool) { + i-- + // sign + if data[i] == '-' { + i++ + if i == len(data) { + return i, false + } + if data[i] < '0' || data[i] > '9' { + return i, false + } + } + // int + if i == len(data) { + return i, false + } + if data[i] == '0' { + i++ + } else { + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break + } + } + // frac + if i == len(data) { + return i, true + } + if data[i] == '.' { + i++ + if i == len(data) { + return i, false + } + if data[i] < '0' || data[i] > '9' { + return i, false + } + i++ + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break + } + } + // exp + if i == len(data) { + return i, true + } + if data[i] == 'e' || data[i] == 'E' { + i++ + if i == len(data) { + return i, false + } + if data[i] == '+' || data[i] == '-' { + i++ + } + if i == len(data) { + return i, false + } + if data[i] < '0' || data[i] > '9' { + return i, false + } + i++ + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break + } + } + return i, true +} + +func validtrue(data []byte, i int) (outi int, ok bool) { + if i+3 <= len(data) && data[i] == 'r' && data[i+1] == 'u' && + data[i+2] == 'e' { + return i + 3, true + } + return i, false +} +func validfalse(data []byte, i int) (outi int, ok bool) { + if i+4 <= len(data) && data[i] == 'a' && data[i+1] == 'l' && + data[i+2] == 's' && data[i+3] == 'e' { + return i + 4, true + } + return i, false +} +func validnull(data []byte, i int) (outi int, ok bool) { + if i+3 <= len(data) && data[i] == 'u' && data[i+1] == 'l' && + data[i+2] == 'l' { + return i + 3, true + } + return i, false +} + +// Valid returns true if the input is valid json. +// +// if !gjson.Valid(json) { +// return errors.New("invalid json") +// } +// value := gjson.Get(json, "name.last") +// +func Valid(json string) bool { + _, ok := validpayload(stringBytes(json), 0) + return ok +} + +// ValidBytes returns true if the input is valid json. +// +// if !gjson.Valid(json) { +// return errors.New("invalid json") +// } +// value := gjson.Get(json, "name.last") +// +// If working with bytes, this method preferred over ValidBytes(string(data)) +// +func ValidBytes(json []byte) bool { + _, ok := validpayload(json, 0) + return ok +} + +func parseUint(s string) (n uint64, ok bool) { + var i int + if i == len(s) { + return 0, false + } + for ; i < len(s); i++ { + if s[i] >= '0' && s[i] <= '9' { + n = n*10 + uint64(s[i]-'0') + } else { + return 0, false + } + } + return n, true +} + +func parseInt(s string) (n int64, ok bool) { + var i int + var sign bool + if len(s) > 0 && s[0] == '-' { + sign = true + i++ + } + if i == len(s) { + return 0, false + } + for ; i < len(s); i++ { + if s[i] >= '0' && s[i] <= '9' { + n = n*10 + int64(s[i]-'0') + } else { + return 0, false + } + } + if sign { + return n * -1, true + } + return n, true +} + +// safeInt validates a given JSON number +// ensures it lies within the minimum and maximum representable JSON numbers +func safeInt(f float64) (n int64, ok bool) { + // https://tc39.es/ecma262/#sec-number.min_safe_integer + // https://tc39.es/ecma262/#sec-number.max_safe_integer + if f < -9007199254740991 || f > 9007199254740991 { + return 0, false + } + return int64(f), true +} + +// execStatic parses the path to find a static value. +// The input expects that the path already starts with a '!' +func execStatic(json, path string) (pathOut, res string, ok bool) { + name := path[1:] + if len(name) > 0 { + switch name[0] { + case '{', '[', '"', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9': + _, res = parseSquash(name, 0) + pathOut = name[len(res):] + return pathOut, res, true + } + } + for i := 1; i < len(path); i++ { + if path[i] == '|' { + pathOut = path[i:] + name = path[1:i] + break + } + if path[i] == '.' { + pathOut = path[i:] + name = path[1:i] + break + } + } + switch strings.ToLower(name) { + case "true", "false", "null", "nan", "inf": + return pathOut, name, true + } + return pathOut, res, false +} + +// execModifier parses the path to find a matching modifier function. +// The input expects that the path already starts with a '@' +func execModifier(json, path string) (pathOut, res string, ok bool) { + name := path[1:] + var hasArgs bool + for i := 1; i < len(path); i++ { + if path[i] == ':' { + pathOut = path[i+1:] + name = path[1:i] + hasArgs = len(pathOut) > 0 + break + } + if path[i] == '|' { + pathOut = path[i:] + name = path[1:i] + break + } + if path[i] == '.' { + pathOut = path[i:] + name = path[1:i] + break + } + } + if fn, ok := modifiers[name]; ok { + var args string + if hasArgs { + var parsedArgs bool + switch pathOut[0] { + case '{', '[', '"': + res := Parse(pathOut) + if res.Exists() { + args = squash(pathOut) + pathOut = pathOut[len(args):] + parsedArgs = true + } + } + if !parsedArgs { + idx := strings.IndexByte(pathOut, '|') + if idx == -1 { + args = pathOut + pathOut = "" + } else { + args = pathOut[:idx] + pathOut = pathOut[idx:] + } + } + } + return pathOut, fn(json, args), true + } + return pathOut, res, false +} + +// unwrap removes the '[]' or '{}' characters around json +func unwrap(json string) string { + json = trim(json) + if len(json) >= 2 && (json[0] == '[' || json[0] == '{') { + json = json[1 : len(json)-1] + } + return json +} + +// DisableModifiers will disable the modifier syntax +var DisableModifiers = false + +var modifiers = map[string]func(json, arg string) string{ + "pretty": modPretty, + "ugly": modUgly, + "reverse": modReverse, + "this": modThis, + "flatten": modFlatten, + "join": modJoin, + "valid": modValid, + "keys": modKeys, + "values": modValues, + "tostr": modToStr, + "fromstr": modFromStr, + "group": modGroup, +} + +// AddModifier binds a custom modifier command to the GJSON syntax. +// This operation is not thread safe and should be executed prior to +// using all other gjson function. +func AddModifier(name string, fn func(json, arg string) string) { + modifiers[name] = fn +} + +// ModifierExists returns true when the specified modifier exists. +func ModifierExists(name string, fn func(json, arg string) string) bool { + _, ok := modifiers[name] + return ok +} + +// cleanWS remove any non-whitespace from string +func cleanWS(s string) string { + for i := 0; i < len(s); i++ { + switch s[i] { + case ' ', '\t', '\n', '\r': + continue + default: + var s2 []byte + for i := 0; i < len(s); i++ { + switch s[i] { + case ' ', '\t', '\n', '\r': + s2 = append(s2, s[i]) + } + } + return string(s2) + } + } + return s +} + +// @pretty modifier makes the json look nice. +func modPretty(json, arg string) string { + if len(arg) > 0 { + opts := *pretty.DefaultOptions + Parse(arg).ForEach(func(key, value Result) bool { + switch key.String() { + case "sortKeys": + opts.SortKeys = value.Bool() + case "indent": + opts.Indent = cleanWS(value.String()) + case "prefix": + opts.Prefix = cleanWS(value.String()) + case "width": + opts.Width = int(value.Int()) + } + return true + }) + return bytesString(pretty.PrettyOptions(stringBytes(json), &opts)) + } + return bytesString(pretty.Pretty(stringBytes(json))) +} + +// @this returns the current element. Can be used to retrieve the root element. +func modThis(json, arg string) string { + return json +} + +// @ugly modifier removes all whitespace. +func modUgly(json, arg string) string { + return bytesString(pretty.Ugly(stringBytes(json))) +} + +// @reverse reverses array elements or root object members. +func modReverse(json, arg string) string { + res := Parse(json) + if res.IsArray() { + var values []Result + res.ForEach(func(_, value Result) bool { + values = append(values, value) + return true + }) + out := make([]byte, 0, len(json)) + out = append(out, '[') + for i, j := len(values)-1, 0; i >= 0; i, j = i-1, j+1 { + if j > 0 { + out = append(out, ',') + } + out = append(out, values[i].Raw...) + } + out = append(out, ']') + return bytesString(out) + } + if res.IsObject() { + var keyValues []Result + res.ForEach(func(key, value Result) bool { + keyValues = append(keyValues, key, value) + return true + }) + out := make([]byte, 0, len(json)) + out = append(out, '{') + for i, j := len(keyValues)-2, 0; i >= 0; i, j = i-2, j+1 { + if j > 0 { + out = append(out, ',') + } + out = append(out, keyValues[i+0].Raw...) + out = append(out, ':') + out = append(out, keyValues[i+1].Raw...) + } + out = append(out, '}') + return bytesString(out) + } + return json +} + +// @flatten an array with child arrays. +// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,[6,7]] +// The {"deep":true} arg can be provide for deep flattening. +// [1,[2],[3,4],[5,[6,7]]] -> [1,2,3,4,5,6,7] +// The original json is returned when the json is not an array. +func modFlatten(json, arg string) string { + res := Parse(json) + if !res.IsArray() { + return json + } + var deep bool + if arg != "" { + Parse(arg).ForEach(func(key, value Result) bool { + if key.String() == "deep" { + deep = value.Bool() + } + return true + }) + } + var out []byte + out = append(out, '[') + var idx int + res.ForEach(func(_, value Result) bool { + var raw string + if value.IsArray() { + if deep { + raw = unwrap(modFlatten(value.Raw, arg)) + } else { + raw = unwrap(value.Raw) + } + } else { + raw = value.Raw + } + raw = strings.TrimSpace(raw) + if len(raw) > 0 { + if idx > 0 { + out = append(out, ',') + } + out = append(out, raw...) + idx++ + } + return true + }) + out = append(out, ']') + return bytesString(out) +} + +// @keys extracts the keys from an object. +// {"first":"Tom","last":"Smith"} -> ["first","last"] +func modKeys(json, arg string) string { + v := Parse(json) + if !v.Exists() { + return "[]" + } + obj := v.IsObject() + var out strings.Builder + out.WriteByte('[') + var i int + v.ForEach(func(key, _ Result) bool { + if i > 0 { + out.WriteByte(',') + } + if obj { + out.WriteString(key.Raw) + } else { + out.WriteString("null") + } + i++ + return true + }) + out.WriteByte(']') + return out.String() +} + +// @values extracts the values from an object. +// {"first":"Tom","last":"Smith"} -> ["Tom","Smith"] +func modValues(json, arg string) string { + v := Parse(json) + if !v.Exists() { + return "[]" + } + if v.IsArray() { + return json + } + var out strings.Builder + out.WriteByte('[') + var i int + v.ForEach(func(_, value Result) bool { + if i > 0 { + out.WriteByte(',') + } + out.WriteString(value.Raw) + i++ + return true + }) + out.WriteByte(']') + return out.String() +} + +// @join multiple objects into a single object. +// [{"first":"Tom"},{"last":"Smith"}] -> {"first","Tom","last":"Smith"} +// The arg can be "true" to specify that duplicate keys should be preserved. +// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":37,"age":41} +// Without preserved keys: +// [{"first":"Tom","age":37},{"age":41}] -> {"first","Tom","age":41} +// The original json is returned when the json is not an object. +func modJoin(json, arg string) string { + res := Parse(json) + if !res.IsArray() { + return json + } + var preserve bool + if arg != "" { + Parse(arg).ForEach(func(key, value Result) bool { + if key.String() == "preserve" { + preserve = value.Bool() + } + return true + }) + } + var out []byte + out = append(out, '{') + if preserve { + // Preserve duplicate keys. + var idx int + res.ForEach(func(_, value Result) bool { + if !value.IsObject() { + return true + } + if idx > 0 { + out = append(out, ',') + } + out = append(out, unwrap(value.Raw)...) + idx++ + return true + }) + } else { + // Deduplicate keys and generate an object with stable ordering. + var keys []Result + kvals := make(map[string]Result) + res.ForEach(func(_, value Result) bool { + if !value.IsObject() { + return true + } + value.ForEach(func(key, value Result) bool { + k := key.String() + if _, ok := kvals[k]; !ok { + keys = append(keys, key) + } + kvals[k] = value + return true + }) + return true + }) + for i := 0; i < len(keys); i++ { + if i > 0 { + out = append(out, ',') + } + out = append(out, keys[i].Raw...) + out = append(out, ':') + out = append(out, kvals[keys[i].String()].Raw...) + } + } + out = append(out, '}') + return bytesString(out) +} + +// @valid ensures that the json is valid before moving on. An empty string is +// returned when the json is not valid, otherwise it returns the original json. +func modValid(json, arg string) string { + if !Valid(json) { + return "" + } + return json +} + +// @fromstr converts a string to json +// "{\"id\":1023,\"name\":\"alert\"}" -> {"id":1023,"name":"alert"} +func modFromStr(json, arg string) string { + if !Valid(json) { + return "" + } + return Parse(json).String() +} + +// @tostr converts a string to json +// {"id":1023,"name":"alert"} -> "{\"id\":1023,\"name\":\"alert\"}" +func modToStr(str, arg string) string { + return string(AppendJSONString(nil, str)) +} + +func modGroup(json, arg string) string { + res := Parse(json) + if !res.IsObject() { + return "" + } + var all [][]byte + res.ForEach(func(key, value Result) bool { + if !value.IsArray() { + return true + } + var idx int + value.ForEach(func(_, value Result) bool { + if idx == len(all) { + all = append(all, []byte{}) + } + all[idx] = append(all[idx], ("," + key.Raw + ":" + value.Raw)...) + idx++ + return true + }) + return true + }) + var data []byte + data = append(data, '[') + for i, item := range all { + if i > 0 { + data = append(data, ',') + } + data = append(data, '{') + data = append(data, item[1:]...) + data = append(data, '}') + } + data = append(data, ']') + return string(data) +} + +// stringHeader instead of reflect.StringHeader +type stringHeader struct { + data unsafe.Pointer + len int +} + +// sliceHeader instead of reflect.SliceHeader +type sliceHeader struct { + data unsafe.Pointer + len int + cap int +} + +// getBytes casts the input json bytes to a string and safely returns the +// results as uniquely allocated data. This operation is intended to minimize +// copies and allocations for the large json string->[]byte. +func getBytes(json []byte, path string) Result { + var result Result + if json != nil { + // unsafe cast to string + result = Get(*(*string)(unsafe.Pointer(&json)), path) + // safely get the string headers + rawhi := *(*stringHeader)(unsafe.Pointer(&result.Raw)) + strhi := *(*stringHeader)(unsafe.Pointer(&result.Str)) + // create byte slice headers + rawh := sliceHeader{data: rawhi.data, len: rawhi.len, cap: rawhi.len} + strh := sliceHeader{data: strhi.data, len: strhi.len, cap: rawhi.len} + if strh.data == nil { + // str is nil + if rawh.data == nil { + // raw is nil + result.Raw = "" + } else { + // raw has data, safely copy the slice header to a string + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + } + result.Str = "" + } else if rawh.data == nil { + // raw is nil + result.Raw = "" + // str has data, safely copy the slice header to a string + result.Str = string(*(*[]byte)(unsafe.Pointer(&strh))) + } else if uintptr(strh.data) >= uintptr(rawh.data) && + uintptr(strh.data)+uintptr(strh.len) <= + uintptr(rawh.data)+uintptr(rawh.len) { + // Str is a substring of Raw. + start := uintptr(strh.data) - uintptr(rawh.data) + // safely copy the raw slice header + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + // substring the raw + result.Str = result.Raw[start : start+uintptr(strh.len)] + } else { + // safely copy both the raw and str slice headers to strings + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + result.Str = string(*(*[]byte)(unsafe.Pointer(&strh))) + } + } + return result +} + +// fillIndex finds the position of Raw data and assigns it to the Index field +// of the resulting value. If the position cannot be found then Index zero is +// used instead. +func fillIndex(json string, c *parseContext) { + if len(c.value.Raw) > 0 && !c.calcd { + jhdr := *(*stringHeader)(unsafe.Pointer(&json)) + rhdr := *(*stringHeader)(unsafe.Pointer(&(c.value.Raw))) + c.value.Index = int(uintptr(rhdr.data) - uintptr(jhdr.data)) + if c.value.Index < 0 || c.value.Index >= len(json) { + c.value.Index = 0 + } + } +} + +func stringBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer(&sliceHeader{ + data: (*stringHeader)(unsafe.Pointer(&s)).data, + len: len(s), + cap: len(s), + })) +} + +func bytesString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +func revSquash(json string) string { + // reverse squash + // expects that the tail character is a ']' or '}' or ')' or '"' + // squash the value, ignoring all nested arrays and objects. + i := len(json) - 1 + var depth int + if json[i] != '"' { + depth++ + } + if json[i] == '}' || json[i] == ']' || json[i] == ')' { + i-- + } + for ; i >= 0; i-- { + switch json[i] { + case '"': + i-- + for ; i >= 0; i-- { + if json[i] == '"' { + esc := 0 + for i > 0 && json[i-1] == '\\' { + i-- + esc++ + } + if esc%2 == 1 { + continue + } + i += esc + break + } + } + if depth == 0 { + if i < 0 { + i = 0 + } + return json[i:] + } + case '}', ']', ')': + depth++ + case '{', '[', '(': + depth-- + if depth == 0 { + return json[i:] + } + } + } + return json +} + +// Paths returns the original GJSON paths for a Result where the Result came +// from a simple query path that returns an array, like: +// +// gjson.Get(json, "friends.#.first") +// +// The returned value will be in the form of a JSON array: +// +// ["friends.0.first","friends.1.first","friends.2.first"] +// +// The param 'json' must be the original JSON used when calling Get. +// +// Returns an empty string if the paths cannot be determined, which can happen +// when the Result came from a path that contained a multipath, modifier, +// or a nested query. +func (t Result) Paths(json string) []string { + if t.Indexes == nil { + return nil + } + paths := make([]string, 0, len(t.Indexes)) + t.ForEach(func(_, value Result) bool { + paths = append(paths, value.Path(json)) + return true + }) + if len(paths) != len(t.Indexes) { + return nil + } + return paths +} + +// Path returns the original GJSON path for a Result where the Result came +// from a simple path that returns a single value, like: +// +// gjson.Get(json, "friends.#(last=Murphy)") +// +// The returned value will be in the form of a JSON string: +// +// "friends.0" +// +// The param 'json' must be the original JSON used when calling Get. +// +// Returns an empty string if the paths cannot be determined, which can happen +// when the Result came from a path that contained a multipath, modifier, +// or a nested query. +func (t Result) Path(json string) string { + var path []byte + var comps []string // raw components + i := t.Index - 1 + if t.Index+len(t.Raw) > len(json) { + // JSON cannot safely contain Result. + goto fail + } + if !strings.HasPrefix(json[t.Index:], t.Raw) { + // Result is not at the JSON index as exepcted. + goto fail + } + for ; i >= 0; i-- { + if json[i] <= ' ' { + continue + } + if json[i] == ':' { + // inside of object, get the key + for ; i >= 0; i-- { + if json[i] != '"' { + continue + } + break + } + raw := revSquash(json[:i+1]) + i = i - len(raw) + comps = append(comps, raw) + // key gotten, now squash the rest + raw = revSquash(json[:i+1]) + i = i - len(raw) + i++ // increment the index for next loop step + } else if json[i] == '{' { + // Encountered an open object. The original result was probably an + // object key. + goto fail + } else if json[i] == ',' || json[i] == '[' { + // inside of an array, count the position + var arrIdx int + if json[i] == ',' { + arrIdx++ + i-- + } + for ; i >= 0; i-- { + if json[i] == ':' { + // Encountered an unexpected colon. The original result was + // probably an object key. + goto fail + } else if json[i] == ',' { + arrIdx++ + } else if json[i] == '[' { + comps = append(comps, strconv.Itoa(arrIdx)) + break + } else if json[i] == ']' || json[i] == '}' || json[i] == '"' { + raw := revSquash(json[:i+1]) + i = i - len(raw) + 1 + } + } + } + } + if len(comps) == 0 { + if DisableModifiers { + goto fail + } + return "@this" + } + for i := len(comps) - 1; i >= 0; i-- { + rcomp := Parse(comps[i]) + if !rcomp.Exists() { + goto fail + } + comp := escapeComp(rcomp.String()) + path = append(path, '.') + path = append(path, comp...) + } + if len(path) > 0 { + path = path[1:] + } + return string(path) +fail: + return "" +} + +// isSafePathKeyChar returns true if the input character is safe for not +// needing escaping. +func isSafePathKeyChar(c byte) bool { + return c <= ' ' || c > '~' || c == '_' || c == '-' || c == ':' || + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') +} + +// escapeComp escaped a path compontent, making it safe for generating a +// path for later use. +func escapeComp(comp string) string { + for i := 0; i < len(comp); i++ { + if !isSafePathKeyChar(comp[i]) { + ncomp := []byte(comp[:i]) + for ; i < len(comp); i++ { + if !isSafePathKeyChar(comp[i]) { + ncomp = append(ncomp, '\\') + } + ncomp = append(ncomp, comp[i]) + } + return string(ncomp) + } + } + return comp +} diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/logo.png b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/gjson/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..17a8bbe9d651e5d79cbd59a3645a152443440ad6 GIT binary patch literal 15936 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

Bg_)L$48&o}ack zt!HAQladuqH6nxcdZjxF6vTK*7%AV0IOX}JZ^u_OkMumMel#gIzv1VJUb|?$j71?Q zE%B-a&vFk0&Z20HLn$L5hH9dQ4Ef5bKHOX_RfA5_`gJvR%bW4}0DCGg0Va&YC>upU z1&8GC>xLY}LkxejF;yq2sOY~5Cqo4UPpDVfIk~O=P(Gb%k`up^XsPRv9)fXL9y}y5 z_nYE{jUJUly7pJ?$r_eiajL0B@N?t6efF1pS9lk2UGDMWIj>@eS3OH*kme)>qMs4c zXm_q!8hu`I8Dhv5J1?UvlYLJ*X6D(~=y^sb&mT@}>JxIp-$sTvzSdP`p2 zJSTpI%GX_eG4+h<`8LXOC{XRWB^Zd0<_&>b2eoF`!(!~j1?f~Xrey`D;63!OSo@1j zhVkNITfkbp3}EkdgcmMq1ZjCrPrG8@X$!y3nZFDG<8?k82gdmVJ<<=koN(R0uQJ&q&;Hm9we)+ z>EiW_<2L!S=W{bXQR{t-Xk&^=i3!xJ1GH*$ft^*`kFY2U5$7}?R(xpv1OE!wXhM-U z{|-m&g!{Wci5J~AR7xhAcz2%T1X}X;Lm5kqn$n>CdZG%!jlgyEgu}8cSE=Tna>KaW zE{i{1$MRasX61aWzh$Kz+gg_XXfmLI)gJjN8%-etjvKrZa0TIVu@FNzi_DBbgG}?X zc%`<^VRHLdaERnl?k6tkmy~V-6wDDz--2n{wg8GA9pEX|*>WDA)uFS%@&MN%-fruuv;9cz5hkb^M5ny{e|a>hsQrwX-D^ zNBO*f*^1b~?2NHQ2g~@Hiq$_V;Z8|Q@sz(OJ-_;@xOeF3^57a-&;bjXTR0^(9&O1`Qs5mDXKaZ{gYh~^a4>N0el@Q8ji@;G0VwtnJC=gh zkvhS5%hY0~-FeS=8*`P?hYjBB(TJ(h3vvLJ;q{z?7Jf~hQZOlh z8(M!wZ6}#fur(A{mkQvWp^(z{kJ9Qcf&hRH6HK!c5b1?W28wck^aQ{Ju^0{PD&(m< zLbu{`I17`!l>tz)uDi~j;Q*7#M!X;*rP#E#K`0MHVO=C_Y>Kpq3I%tz)KS21z(70w zu+S+efLBlwQ<3knLW3-aM&dW%uuv1r?kQqDWsn|wHc35K!jBl%t~9oLn(8w!(iF2Y zj)Ca3zC|-dT69O*c44Drzb zTWRcGB4pjuzq;tNLKJ7H7&3CTH`^5&fs^&Csh+4TdNT~n>o18P123FK1<6Ap7>9OE zH?~wjjt8TicxY-E;Ld@{^P%j9BpeC^HJlw8iW2I<*Vc#1VH5Y&k@$T23>riN5a^fE z1xtK3We+L~pTm`C@mKj&g@flbKtRjuc?i2+3nW^d516YT2Qx*|&}0%IIZ{BK)n@-A z7W$f>T8g%2qR?15Ezb7#Al1jSng=Um;<;^4CZ)UF4*B#s16}WxXDR&e;IFDemyJBP zQD>pCTMx2W)Iwg$ckc4ElkYeRp?hVt(&_O^6u-=WO4y!T>T70vx)M)1f17nmsqw{$ zZhYP=C<2j}m^Lg-ex&8RKf~0WrfrL!~NLQ^Xk?)No>Z1xiNn~MBa)Y#@)-369lm6 zDH#U3Pv&kw{mt3+_t3uW8=*{`^%?Rl0~ff@XZrjZDZCjasI5PtLP6)zBrRO$pYlfV z4DCdS&(8)uR*23AuP?jhTi@#6yL=G*b~7!5d3?2d_2DzzR3V+SVCZe@ULo|*Qz`CV z*j}+=>D|^uy$<{s0tgCLYba;t*4jWsj}CfRI?T4cS90us!fy8Lz;(qp7V}H&`+loKEpPZr6wt`2EqWMWj z6-EjjNWU@D@IQdNmlvT5>qUq{`9m maxcomp*len(str). +func MatchLimit(str, pattern string, maxcomp int) (matched, stopped bool) { + if pattern == "*" { + return true, false + } + counter := 0 + r := match(str, pattern, len(str), &counter, maxcomp) + if r == rStop { + return false, true + } + return r == rMatch, false +} + +type result int + +const ( + rNoMatch result = iota + rMatch + rStop +) + +func match(str, pat string, slen int, counter *int, maxcomp int) result { + // check complexity limit + if maxcomp > -1 { + if *counter > slen*maxcomp { + return rStop + } + *counter++ + } + + for len(pat) > 0 { + var wild bool + pc, ps := rune(pat[0]), 1 + if pc > 0x7f { + pc, ps = utf8.DecodeRuneInString(pat) + } + var sc rune + var ss int + if len(str) > 0 { + sc, ss = rune(str[0]), 1 + if sc > 0x7f { + sc, ss = utf8.DecodeRuneInString(str) + } + } + switch pc { + case '?': + if ss == 0 { + return rNoMatch + } + case '*': + // Ignore repeating stars. + for len(pat) > 1 && pat[1] == '*' { + pat = pat[1:] + } + + // If this star is the last character then it must be a match. + if len(pat) == 1 { + return rMatch + } + + // Match and trim any non-wildcard suffix characters. + var ok bool + str, pat, ok = matchTrimSuffix(str, pat) + if !ok { + return rNoMatch + } + + // Check for single star again. + if len(pat) == 1 { + return rMatch + } + + // Perform recursive wildcard search. + r := match(str, pat[1:], slen, counter, maxcomp) + if r != rNoMatch { + return r + } + if len(str) == 0 { + return rNoMatch + } + wild = true + default: + if ss == 0 { + return rNoMatch + } + if pc == '\\' { + pat = pat[ps:] + pc, ps = utf8.DecodeRuneInString(pat) + if ps == 0 { + return rNoMatch + } + } + if sc != pc { + return rNoMatch + } + } + str = str[ss:] + if !wild { + pat = pat[ps:] + } + } + if len(str) == 0 { + return rMatch + } + return rNoMatch +} + +// matchTrimSuffix matches and trims any non-wildcard suffix characters. +// Returns the trimed string and pattern. +// +// This is called because the pattern contains extra data after the wildcard +// star. Here we compare any suffix characters in the pattern to the suffix of +// the target string. Basically a reverse match that stops when a wildcard +// character is reached. This is a little trickier than a forward match because +// we need to evaluate an escaped character in reverse. +// +// Any matched characters will be trimmed from both the target +// string and the pattern. +func matchTrimSuffix(str, pat string) (string, string, bool) { + // It's expected that the pattern has at least two bytes and the first byte + // is a wildcard star '*' + match := true + for len(str) > 0 && len(pat) > 1 { + pc, ps := utf8.DecodeLastRuneInString(pat) + var esc bool + for i := 0; ; i++ { + if pat[len(pat)-ps-i-1] != '\\' { + if i&1 == 1 { + esc = true + ps++ + } + break + } + } + if pc == '*' && !esc { + match = true + break + } + sc, ss := utf8.DecodeLastRuneInString(str) + if !((pc == '?' && !esc) || pc == sc) { + match = false + break + } + str = str[:len(str)-ss] + pat = pat[:len(pat)-ps] + } + return str, pat, match +} + +var maxRuneBytes = [...]byte{244, 143, 191, 191} + +// Allowable parses the pattern and determines the minimum and maximum allowable +// values that the pattern can represent. +// When the max cannot be determined, 'true' will be returned +// for infinite. +func Allowable(pattern string) (min, max string) { + if pattern == "" || pattern[0] == '*' { + return "", "" + } + + minb := make([]byte, 0, len(pattern)) + maxb := make([]byte, 0, len(pattern)) + var wild bool + for i := 0; i < len(pattern); i++ { + if pattern[i] == '*' { + wild = true + break + } + if pattern[i] == '?' { + minb = append(minb, 0) + maxb = append(maxb, maxRuneBytes[:]...) + } else { + minb = append(minb, pattern[i]) + maxb = append(maxb, pattern[i]) + } + } + if wild { + r, n := utf8.DecodeLastRune(maxb) + if r != utf8.RuneError { + if r < utf8.MaxRune { + r++ + if r > 0x7f { + b := make([]byte, 4) + nn := utf8.EncodeRune(b, r) + maxb = append(maxb[:len(maxb)-n], b[:nn]...) + } else { + maxb = append(maxb[:len(maxb)-n], byte(r)) + } + } + } + } + return string(minb), string(maxb) +} + +// IsPattern returns true if the string is a pattern. +func IsPattern(str string) bool { + for i := 0; i < len(str); i++ { + if str[i] == '*' || str[i] == '?' { + return true + } + } + return false +} diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/LICENSE b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/LICENSE new file mode 100644 index 00000000..993b83f2 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/README.md b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/README.md new file mode 100644 index 00000000..76c06a5e --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/README.md @@ -0,0 +1,122 @@ +# Pretty + +[![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/tidwall/pretty) + +Pretty is a Go package that provides [fast](#performance) methods for formatting JSON for human readability, or to compact JSON for smaller payloads. + +Getting Started +=============== + +## Installing + +To start using Pretty, install Go and run `go get`: + +```sh +$ go get -u github.com/tidwall/pretty +``` + +This will retrieve the library. + +## Pretty + +Using this example: + +```json +{"name": {"first":"Tom","last":"Anderson"}, "age":37, +"children": ["Sara","Alex","Jack"], +"fav.movie": "Deer Hunter", "friends": [ + {"first": "Janet", "last": "Murphy", "age": 44} + ]} +``` + +The following code: +```go +result = pretty.Pretty(example) +``` + +Will format the json to: + +```json +{ + "name": { + "first": "Tom", + "last": "Anderson" + }, + "age": 37, + "children": ["Sara", "Alex", "Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + { + "first": "Janet", + "last": "Murphy", + "age": 44 + } + ] +} +``` + +## Color + +Color will colorize the json for outputing to the screen. + +```go +result = pretty.Color(json, nil) +``` + +Will add color to the result for printing to the terminal. +The second param is used for a customizing the style, and passing nil will use the default `pretty.TerminalStyle`. + +## Ugly + +The following code: +```go +result = pretty.Ugly(example) +``` + +Will format the json to: + +```json +{"name":{"first":"Tom","last":"Anderson"},"age":37,"children":["Sara","Alex","Jack"],"fav.movie":"Deer Hunter","friends":[{"first":"Janet","last":"Murphy","age":44}]}``` +``` + +## Customized output + +There's a `PrettyOptions(json, opts)` function which allows for customizing the output with the following options: + +```go +type Options struct { + // Width is an max column width for single line arrays + // Default is 80 + Width int + // Prefix is a prefix for all lines + // Default is an empty string + Prefix string + // Indent is the nested indentation + // Default is two spaces + Indent string + // SortKeys will sort the keys alphabetically + // Default is false + SortKeys bool +} +``` +## Performance + +Benchmarks of Pretty alongside the builtin `encoding/json` Indent/Compact methods. +``` +BenchmarkPretty-16 1000000 1034 ns/op 720 B/op 2 allocs/op +BenchmarkPrettySortKeys-16 586797 1983 ns/op 2848 B/op 14 allocs/op +BenchmarkUgly-16 4652365 254 ns/op 240 B/op 1 allocs/op +BenchmarkUglyInPlace-16 6481233 183 ns/op 0 B/op 0 allocs/op +BenchmarkJSONIndent-16 450654 2687 ns/op 1221 B/op 0 allocs/op +BenchmarkJSONCompact-16 685111 1699 ns/op 442 B/op 0 allocs/op +``` + +*These benchmarks were run on a MacBook Pro 2.4 GHz 8-Core Intel Core i9.* + +## Contact +Josh Baker [@tidwall](http://twitter.com/tidwall) + +## License + +Pretty source code is available under the MIT [License](/LICENSE). + diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/pretty.go b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/pretty.go new file mode 100644 index 00000000..d705f9cd --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/pretty/pretty.go @@ -0,0 +1,682 @@ +package pretty + +import ( + "bytes" + "encoding/json" + "sort" + "strconv" +) + +// Options is Pretty options +type Options struct { + // Width is an max column width for single line arrays + // Default is 80 + Width int + // Prefix is a prefix for all lines + // Default is an empty string + Prefix string + // Indent is the nested indentation + // Default is two spaces + Indent string + // SortKeys will sort the keys alphabetically + // Default is false + SortKeys bool +} + +// DefaultOptions is the default options for pretty formats. +var DefaultOptions = &Options{Width: 80, Prefix: "", Indent: " ", SortKeys: false} + +// Pretty converts the input json into a more human readable format where each +// element is on it's own line with clear indentation. +func Pretty(json []byte) []byte { return PrettyOptions(json, nil) } + +// PrettyOptions is like Pretty but with customized options. +func PrettyOptions(json []byte, opts *Options) []byte { + if opts == nil { + opts = DefaultOptions + } + buf := make([]byte, 0, len(json)) + if len(opts.Prefix) != 0 { + buf = append(buf, opts.Prefix...) + } + buf, _, _, _ = appendPrettyAny(buf, json, 0, true, + opts.Width, opts.Prefix, opts.Indent, opts.SortKeys, + 0, 0, -1) + if len(buf) > 0 { + buf = append(buf, '\n') + } + return buf +} + +// Ugly removes insignificant space characters from the input json byte slice +// and returns the compacted result. +func Ugly(json []byte) []byte { + buf := make([]byte, 0, len(json)) + return ugly(buf, json) +} + +// UglyInPlace removes insignificant space characters from the input json +// byte slice and returns the compacted result. This method reuses the +// input json buffer to avoid allocations. Do not use the original bytes +// slice upon return. +func UglyInPlace(json []byte) []byte { return ugly(json, json) } + +func ugly(dst, src []byte) []byte { + dst = dst[:0] + for i := 0; i < len(src); i++ { + if src[i] > ' ' { + dst = append(dst, src[i]) + if src[i] == '"' { + for i = i + 1; i < len(src); i++ { + dst = append(dst, src[i]) + if src[i] == '"' { + j := i - 1 + for ; ; j-- { + if src[j] != '\\' { + break + } + } + if (j-i)%2 != 0 { + break + } + } + } + } + } + } + return dst +} + +func isNaNOrInf(src []byte) bool { + return src[0] == 'i' || //Inf + src[0] == 'I' || // inf + src[0] == '+' || // +Inf + src[0] == 'N' || // Nan + (src[0] == 'n' && len(src) > 1 && src[1] != 'u') // nan +} + +func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) { + for ; i < len(json); i++ { + if json[i] <= ' ' { + continue + } + if json[i] == '"' { + return appendPrettyString(buf, json, i, nl) + } + + if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' || isNaNOrInf(json[i:]) { + return appendPrettyNumber(buf, json, i, nl) + } + if json[i] == '{' { + return appendPrettyObject(buf, json, i, '{', '}', pretty, width, prefix, indent, sortkeys, tabs, nl, max) + } + if json[i] == '[' { + return appendPrettyObject(buf, json, i, '[', ']', pretty, width, prefix, indent, sortkeys, tabs, nl, max) + } + switch json[i] { + case 't': + return append(buf, 't', 'r', 'u', 'e'), i + 4, nl, true + case 'f': + return append(buf, 'f', 'a', 'l', 's', 'e'), i + 5, nl, true + case 'n': + return append(buf, 'n', 'u', 'l', 'l'), i + 4, nl, true + } + } + return buf, i, nl, true +} + +type pair struct { + kstart, kend int + vstart, vend int +} + +type byKeyVal struct { + sorted bool + json []byte + buf []byte + pairs []pair +} + +func (arr *byKeyVal) Len() int { + return len(arr.pairs) +} +func (arr *byKeyVal) Less(i, j int) bool { + if arr.isLess(i, j, byKey) { + return true + } + if arr.isLess(j, i, byKey) { + return false + } + return arr.isLess(i, j, byVal) +} +func (arr *byKeyVal) Swap(i, j int) { + arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i] + arr.sorted = true +} + +type byKind int + +const ( + byKey byKind = 0 + byVal byKind = 1 +) + +type jtype int + +const ( + jnull jtype = iota + jfalse + jnumber + jstring + jtrue + jjson +) + +func getjtype(v []byte) jtype { + if len(v) == 0 { + return jnull + } + switch v[0] { + case '"': + return jstring + case 'f': + return jfalse + case 't': + return jtrue + case 'n': + return jnull + case '[', '{': + return jjson + default: + return jnumber + } +} + +func (arr *byKeyVal) isLess(i, j int, kind byKind) bool { + k1 := arr.json[arr.pairs[i].kstart:arr.pairs[i].kend] + k2 := arr.json[arr.pairs[j].kstart:arr.pairs[j].kend] + var v1, v2 []byte + if kind == byKey { + v1 = k1 + v2 = k2 + } else { + v1 = bytes.TrimSpace(arr.buf[arr.pairs[i].vstart:arr.pairs[i].vend]) + v2 = bytes.TrimSpace(arr.buf[arr.pairs[j].vstart:arr.pairs[j].vend]) + if len(v1) >= len(k1)+1 { + v1 = bytes.TrimSpace(v1[len(k1)+1:]) + } + if len(v2) >= len(k2)+1 { + v2 = bytes.TrimSpace(v2[len(k2)+1:]) + } + } + t1 := getjtype(v1) + t2 := getjtype(v2) + if t1 < t2 { + return true + } + if t1 > t2 { + return false + } + if t1 == jstring { + s1 := parsestr(v1) + s2 := parsestr(v2) + return string(s1) < string(s2) + } + if t1 == jnumber { + n1, _ := strconv.ParseFloat(string(v1), 64) + n2, _ := strconv.ParseFloat(string(v2), 64) + return n1 < n2 + } + return string(v1) < string(v2) + +} + +func parsestr(s []byte) []byte { + for i := 1; i < len(s); i++ { + if s[i] == '\\' { + var str string + json.Unmarshal(s, &str) + return []byte(str) + } + if s[i] == '"' { + return s[1:i] + } + } + return nil +} + +func appendPrettyObject(buf, json []byte, i int, open, close byte, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) { + var ok bool + if width > 0 { + if pretty && open == '[' && max == -1 { + // here we try to create a single line array + max := width - (len(buf) - nl) + if max > 3 { + s1, s2 := len(buf), i + buf, i, _, ok = appendPrettyObject(buf, json, i, '[', ']', false, width, prefix, "", sortkeys, 0, 0, max) + if ok && len(buf)-s1 <= max { + return buf, i, nl, true + } + buf = buf[:s1] + i = s2 + } + } else if max != -1 && open == '{' { + return buf, i, nl, false + } + } + buf = append(buf, open) + i++ + var pairs []pair + if open == '{' && sortkeys { + pairs = make([]pair, 0, 8) + } + var n int + for ; i < len(json); i++ { + if json[i] <= ' ' { + continue + } + if json[i] == close { + if pretty { + if open == '{' && sortkeys { + buf = sortPairs(json, buf, pairs) + } + if n > 0 { + nl = len(buf) + if buf[nl-1] == ' ' { + buf[nl-1] = '\n' + } else { + buf = append(buf, '\n') + } + } + if buf[len(buf)-1] != open { + buf = appendTabs(buf, prefix, indent, tabs) + } + } + buf = append(buf, close) + return buf, i + 1, nl, open != '{' + } + if open == '[' || json[i] == '"' { + if n > 0 { + buf = append(buf, ',') + if width != -1 && open == '[' { + buf = append(buf, ' ') + } + } + var p pair + if pretty { + nl = len(buf) + if buf[nl-1] == ' ' { + buf[nl-1] = '\n' + } else { + buf = append(buf, '\n') + } + if open == '{' && sortkeys { + p.kstart = i + p.vstart = len(buf) + } + buf = appendTabs(buf, prefix, indent, tabs+1) + } + if open == '{' { + buf, i, nl, _ = appendPrettyString(buf, json, i, nl) + if sortkeys { + p.kend = i + } + buf = append(buf, ':') + if pretty { + buf = append(buf, ' ') + } + } + buf, i, nl, ok = appendPrettyAny(buf, json, i, pretty, width, prefix, indent, sortkeys, tabs+1, nl, max) + if max != -1 && !ok { + return buf, i, nl, false + } + if pretty && open == '{' && sortkeys { + p.vend = len(buf) + if p.kstart > p.kend || p.vstart > p.vend { + // bad data. disable sorting + sortkeys = false + } else { + pairs = append(pairs, p) + } + } + i-- + n++ + } + } + return buf, i, nl, open != '{' +} +func sortPairs(json, buf []byte, pairs []pair) []byte { + if len(pairs) == 0 { + return buf + } + vstart := pairs[0].vstart + vend := pairs[len(pairs)-1].vend + arr := byKeyVal{false, json, buf, pairs} + sort.Stable(&arr) + if !arr.sorted { + return buf + } + nbuf := make([]byte, 0, vend-vstart) + for i, p := range pairs { + nbuf = append(nbuf, buf[p.vstart:p.vend]...) + if i < len(pairs)-1 { + nbuf = append(nbuf, ',') + nbuf = append(nbuf, '\n') + } + } + return append(buf[:vstart], nbuf...) +} + +func appendPrettyString(buf, json []byte, i, nl int) ([]byte, int, int, bool) { + s := i + i++ + for ; i < len(json); i++ { + if json[i] == '"' { + var sc int + for j := i - 1; j > s; j-- { + if json[j] == '\\' { + sc++ + } else { + break + } + } + if sc%2 == 1 { + continue + } + i++ + break + } + } + return append(buf, json[s:i]...), i, nl, true +} + +func appendPrettyNumber(buf, json []byte, i, nl int) ([]byte, int, int, bool) { + s := i + i++ + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ',' || json[i] == ':' || json[i] == ']' || json[i] == '}' { + break + } + } + return append(buf, json[s:i]...), i, nl, true +} + +func appendTabs(buf []byte, prefix, indent string, tabs int) []byte { + if len(prefix) != 0 { + buf = append(buf, prefix...) + } + if len(indent) == 2 && indent[0] == ' ' && indent[1] == ' ' { + for i := 0; i < tabs; i++ { + buf = append(buf, ' ', ' ') + } + } else { + for i := 0; i < tabs; i++ { + buf = append(buf, indent...) + } + } + return buf +} + +// Style is the color style +type Style struct { + Key, String, Number [2]string + True, False, Null [2]string + Escape [2]string + Brackets [2]string + Append func(dst []byte, c byte) []byte +} + +func hexp(p byte) byte { + switch { + case p < 10: + return p + '0' + default: + return (p - 10) + 'a' + } +} + +// TerminalStyle is for terminals +var TerminalStyle *Style + +func init() { + TerminalStyle = &Style{ + Key: [2]string{"\x1B[1m\x1B[94m", "\x1B[0m"}, + String: [2]string{"\x1B[32m", "\x1B[0m"}, + Number: [2]string{"\x1B[33m", "\x1B[0m"}, + True: [2]string{"\x1B[36m", "\x1B[0m"}, + False: [2]string{"\x1B[36m", "\x1B[0m"}, + Null: [2]string{"\x1B[2m", "\x1B[0m"}, + Escape: [2]string{"\x1B[35m", "\x1B[0m"}, + Brackets: [2]string{"\x1B[1m", "\x1B[0m"}, + Append: func(dst []byte, c byte) []byte { + if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') { + dst = append(dst, "\\u00"...) + dst = append(dst, hexp((c>>4)&0xF)) + return append(dst, hexp((c)&0xF)) + } + return append(dst, c) + }, + } +} + +// Color will colorize the json. The style parma is used for customizing +// the colors. Passing nil to the style param will use the default +// TerminalStyle. +func Color(src []byte, style *Style) []byte { + if style == nil { + style = TerminalStyle + } + apnd := style.Append + if apnd == nil { + apnd = func(dst []byte, c byte) []byte { + return append(dst, c) + } + } + type stackt struct { + kind byte + key bool + } + var dst []byte + var stack []stackt + for i := 0; i < len(src); i++ { + if src[i] == '"' { + key := len(stack) > 0 && stack[len(stack)-1].key + if key { + dst = append(dst, style.Key[0]...) + } else { + dst = append(dst, style.String[0]...) + } + dst = apnd(dst, '"') + esc := false + uesc := 0 + for i = i + 1; i < len(src); i++ { + if src[i] == '\\' { + if key { + dst = append(dst, style.Key[1]...) + } else { + dst = append(dst, style.String[1]...) + } + dst = append(dst, style.Escape[0]...) + dst = apnd(dst, src[i]) + esc = true + if i+1 < len(src) && src[i+1] == 'u' { + uesc = 5 + } else { + uesc = 1 + } + } else if esc { + dst = apnd(dst, src[i]) + if uesc == 1 { + esc = false + dst = append(dst, style.Escape[1]...) + if key { + dst = append(dst, style.Key[0]...) + } else { + dst = append(dst, style.String[0]...) + } + } else { + uesc-- + } + } else { + dst = apnd(dst, src[i]) + } + if src[i] == '"' { + j := i - 1 + for ; ; j-- { + if src[j] != '\\' { + break + } + } + if (j-i)%2 != 0 { + break + } + } + } + if esc { + dst = append(dst, style.Escape[1]...) + } else if key { + dst = append(dst, style.Key[1]...) + } else { + dst = append(dst, style.String[1]...) + } + } else if src[i] == '{' || src[i] == '[' { + stack = append(stack, stackt{src[i], src[i] == '{'}) + dst = append(dst, style.Brackets[0]...) + dst = apnd(dst, src[i]) + dst = append(dst, style.Brackets[1]...) + } else if (src[i] == '}' || src[i] == ']') && len(stack) > 0 { + stack = stack[:len(stack)-1] + dst = append(dst, style.Brackets[0]...) + dst = apnd(dst, src[i]) + dst = append(dst, style.Brackets[1]...) + } else if (src[i] == ':' || src[i] == ',') && len(stack) > 0 && stack[len(stack)-1].kind == '{' { + stack[len(stack)-1].key = !stack[len(stack)-1].key + dst = append(dst, style.Brackets[0]...) + dst = apnd(dst, src[i]) + dst = append(dst, style.Brackets[1]...) + } else { + var kind byte + if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' || isNaNOrInf(src[i:]) { + kind = '0' + dst = append(dst, style.Number[0]...) + } else if src[i] == 't' { + kind = 't' + dst = append(dst, style.True[0]...) + } else if src[i] == 'f' { + kind = 'f' + dst = append(dst, style.False[0]...) + } else if src[i] == 'n' { + kind = 'n' + dst = append(dst, style.Null[0]...) + } else { + dst = apnd(dst, src[i]) + } + if kind != 0 { + for ; i < len(src); i++ { + if src[i] <= ' ' || src[i] == ',' || src[i] == ':' || src[i] == ']' || src[i] == '}' { + i-- + break + } + dst = apnd(dst, src[i]) + } + if kind == '0' { + dst = append(dst, style.Number[1]...) + } else if kind == 't' { + dst = append(dst, style.True[1]...) + } else if kind == 'f' { + dst = append(dst, style.False[1]...) + } else if kind == 'n' { + dst = append(dst, style.Null[1]...) + } + } + } + } + return dst +} + +// Spec strips out comments and trailing commas and convert the input to a +// valid JSON per the official spec: https://tools.ietf.org/html/rfc8259 +// +// The resulting JSON will always be the same length as the input and it will +// include all of the same line breaks at matching offsets. This is to ensure +// the result can be later processed by a external parser and that that +// parser will report messages or errors with the correct offsets. +func Spec(src []byte) []byte { + return spec(src, nil) +} + +// SpecInPlace is the same as Spec, but this method reuses the input json +// buffer to avoid allocations. Do not use the original bytes slice upon return. +func SpecInPlace(src []byte) []byte { + return spec(src, src) +} + +func spec(src, dst []byte) []byte { + dst = dst[:0] + for i := 0; i < len(src); i++ { + if src[i] == '/' { + if i < len(src)-1 { + if src[i+1] == '/' { + dst = append(dst, ' ', ' ') + i += 2 + for ; i < len(src); i++ { + if src[i] == '\n' { + dst = append(dst, '\n') + break + } else if src[i] == '\t' || src[i] == '\r' { + dst = append(dst, src[i]) + } else { + dst = append(dst, ' ') + } + } + continue + } + if src[i+1] == '*' { + dst = append(dst, ' ', ' ') + i += 2 + for ; i < len(src)-1; i++ { + if src[i] == '*' && src[i+1] == '/' { + dst = append(dst, ' ', ' ') + i++ + break + } else if src[i] == '\n' || src[i] == '\t' || + src[i] == '\r' { + dst = append(dst, src[i]) + } else { + dst = append(dst, ' ') + } + } + continue + } + } + } + dst = append(dst, src[i]) + if src[i] == '"' { + for i = i + 1; i < len(src); i++ { + dst = append(dst, src[i]) + if src[i] == '"' { + j := i - 1 + for ; ; j-- { + if src[j] != '\\' { + break + } + } + if (j-i)%2 != 0 { + break + } + } + } + } else if src[i] == '}' || src[i] == ']' { + for j := len(dst) - 2; j >= 0; j-- { + if dst[j] <= ' ' { + continue + } + if dst[j] == ',' { + dst[j] = ' ' + } + break + } + } + } + return dst +} diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/LICENSE b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/LICENSE new file mode 100644 index 00000000..89593c7c --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/README.md b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/README.md new file mode 100644 index 00000000..4598424e --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/README.md @@ -0,0 +1,278 @@ +

+SJSON +
+GoDoc +

+ +

set a json value quickly

+ +SJSON is a Go package that provides a [very fast](#performance) and simple way to set a value in a json document. +For quickly retrieving json values check out [GJSON](https://github.com/tidwall/gjson). + +For a command line interface check out [JJ](https://github.com/tidwall/jj). + +Getting Started +=============== + +Installing +---------- + +To start using SJSON, install Go and run `go get`: + +```sh +$ go get -u github.com/tidwall/sjson +``` + +This will retrieve the library. + +Set a value +----------- +Set sets the value for the specified path. +A path is in dot syntax, such as "name.last" or "age". +This function expects that the json is well-formed and validated. +Invalid json will not panic, but it may return back unexpected results. +Invalid paths may return an error. + +```go +package main + +import "github.com/tidwall/sjson" + +const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}` + +func main() { + value, _ := sjson.Set(json, "name.last", "Anderson") + println(value) +} +``` + +This will print: + +```json +{"name":{"first":"Janet","last":"Anderson"},"age":47} +``` + +Path syntax +----------- + +A path is a series of keys separated by a dot. +The dot and colon characters can be escaped with ``\``. + +```json +{ + "name": {"first": "Tom", "last": "Anderson"}, + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"first": "James", "last": "Murphy"}, + {"first": "Roger", "last": "Craig"} + ] +} +``` +``` +"name.last" >> "Anderson" +"age" >> 37 +"children.1" >> "Alex" +"friends.1.last" >> "Craig" +``` + +The `-1` key can be used to append a value to an existing array: + +``` +"children.-1" >> appends a new value to the end of the children array +``` + +Normally number keys are used to modify arrays, but it's possible to force a numeric object key by using the colon character: + +```json +{ + "users":{ + "2313":{"name":"Sara"}, + "7839":{"name":"Andy"} + } +} +``` + +A colon path would look like: + +``` +"users.:2313.name" >> "Sara" +``` + +Supported types +--------------- + +Pretty much any type is supported: + +```go +sjson.Set(`{"key":true}`, "key", nil) +sjson.Set(`{"key":true}`, "key", false) +sjson.Set(`{"key":true}`, "key", 1) +sjson.Set(`{"key":true}`, "key", 10.5) +sjson.Set(`{"key":true}`, "key", "hello") +sjson.Set(`{"key":true}`, "key", []string{"hello", "world"}) +sjson.Set(`{"key":true}`, "key", map[string]interface{}{"hello":"world"}) +``` + +When a type is not recognized, SJSON will fallback to the `encoding/json` Marshaller. + + +Examples +-------- + +Set a value from empty document: +```go +value, _ := sjson.Set("", "name", "Tom") +println(value) + +// Output: +// {"name":"Tom"} +``` + +Set a nested value from empty document: +```go +value, _ := sjson.Set("", "name.last", "Anderson") +println(value) + +// Output: +// {"name":{"last":"Anderson"}} +``` + +Set a new value: +```go +value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.first", "Sara") +println(value) + +// Output: +// {"name":{"first":"Sara","last":"Anderson"}} +``` + +Update an existing value: +```go +value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.last", "Smith") +println(value) + +// Output: +// {"name":{"last":"Smith"}} +``` + +Set a new array value: +```go +value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.2", "Sara") +println(value) + +// Output: +// {"friends":["Andy","Carol","Sara"] +``` + +Append an array value by using the `-1` key in a path: +```go +value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.-1", "Sara") +println(value) + +// Output: +// {"friends":["Andy","Carol","Sara"] +``` + +Append an array value that is past the end: +```go +value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.4", "Sara") +println(value) + +// Output: +// {"friends":["Andy","Carol",null,null,"Sara"] +``` + +Delete a value: +```go +value, _ := sjson.Delete(`{"name":{"first":"Sara","last":"Anderson"}}`, "name.first") +println(value) + +// Output: +// {"name":{"last":"Anderson"}} +``` + +Delete an array value: +```go +value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.1") +println(value) + +// Output: +// {"friends":["Andy"]} +``` + +Delete the last array value: +```go +value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.-1") +println(value) + +// Output: +// {"friends":["Andy"]} +``` + +## Performance + +Benchmarks of SJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), +[ffjson](https://github.com/pquerna/ffjson), +[EasyJSON](https://github.com/mailru/easyjson), +and [Gabs](https://github.com/Jeffail/gabs) + +``` +Benchmark_SJSON-8 3000000 805 ns/op 1077 B/op 3 allocs/op +Benchmark_SJSON_ReplaceInPlace-8 3000000 449 ns/op 0 B/op 0 allocs/op +Benchmark_JSON_Map-8 300000 21236 ns/op 6392 B/op 150 allocs/op +Benchmark_JSON_Struct-8 300000 14691 ns/op 1789 B/op 24 allocs/op +Benchmark_Gabs-8 300000 21311 ns/op 6752 B/op 150 allocs/op +Benchmark_FFJSON-8 300000 17673 ns/op 3589 B/op 47 allocs/op +Benchmark_EasyJSON-8 1500000 3119 ns/op 1061 B/op 13 allocs/op +``` + +JSON document used: + +```json +{ + "widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } + } +} +``` + +Each operation was rotated though one of the following search paths: + +``` +widget.window.name +widget.image.hOffset +widget.text.onMouseUp +``` + +*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7 and can be be found [here](https://github.com/tidwall/sjson-benchmarks)*. + +## Contact +Josh Baker [@tidwall](http://twitter.com/tidwall) + +## License + +SJSON source code is available under the MIT [License](/LICENSE). diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/logo.png b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b5aa257b6b5a72bf7d759c23d94c5928cd8eb318 GIT binary patch literal 16874 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 literal 0 HcmV?d00001 diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/sjson.go b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/sjson.go new file mode 100644 index 00000000..a55eef3f --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/tidwall/sjson/sjson.go @@ -0,0 +1,737 @@ +// Package sjson provides setting json values. +package sjson + +import ( + jsongo "encoding/json" + "sort" + "strconv" + "unsafe" + + "github.com/tidwall/gjson" +) + +type errorType struct { + msg string +} + +func (err *errorType) Error() string { + return err.msg +} + +// Options represents additional options for the Set and Delete functions. +type Options struct { + // Optimistic is a hint that the value likely exists which + // allows for the sjson to perform a fast-track search and replace. + Optimistic bool + // ReplaceInPlace is a hint to replace the input json rather than + // allocate a new json byte slice. When this field is specified + // the input json will not longer be valid and it should not be used + // In the case when the destination slice doesn't have enough free + // bytes to replace the data in place, a new bytes slice will be + // created under the hood. + // The Optimistic flag must be set to true and the input must be a + // byte slice in order to use this field. + ReplaceInPlace bool +} + +type pathResult struct { + part string // current key part + gpart string // gjson get part + path string // remaining path + force bool // force a string key + more bool // there is more path to parse +} + +func isSimpleChar(ch byte) bool { + switch ch { + case '|', '#', '@', '*', '?': + return false + default: + return true + } +} + +func parsePath(path string) (res pathResult, simple bool) { + var r pathResult + if len(path) > 0 && path[0] == ':' { + r.force = true + path = path[1:] + } + for i := 0; i < len(path); i++ { + if path[i] == '.' { + r.part = path[:i] + r.gpart = path[:i] + r.path = path[i+1:] + r.more = true + return r, true + } + if !isSimpleChar(path[i]) { + return r, false + } + if path[i] == '\\' { + // go into escape mode. this is a slower path that + // strips off the escape character from the part. + epart := []byte(path[:i]) + gpart := []byte(path[:i+1]) + i++ + if i < len(path) { + epart = append(epart, path[i]) + gpart = append(gpart, path[i]) + i++ + for ; i < len(path); i++ { + if path[i] == '\\' { + gpart = append(gpart, '\\') + i++ + if i < len(path) { + epart = append(epart, path[i]) + gpart = append(gpart, path[i]) + } + continue + } else if path[i] == '.' { + r.part = string(epart) + r.gpart = string(gpart) + r.path = path[i+1:] + r.more = true + return r, true + } else if !isSimpleChar(path[i]) { + return r, false + } + epart = append(epart, path[i]) + gpart = append(gpart, path[i]) + } + } + // append the last part + r.part = string(epart) + r.gpart = string(gpart) + return r, true + } + } + r.part = path + r.gpart = path + return r, true +} + +func mustMarshalString(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < ' ' || s[i] > 0x7f || s[i] == '"' || s[i] == '\\' { + return true + } + } + return false +} + +// appendStringify makes a json string and appends to buf. +func appendStringify(buf []byte, s string) []byte { + if mustMarshalString(s) { + b, _ := jsongo.Marshal(s) + return append(buf, b...) + } + buf = append(buf, '"') + buf = append(buf, s...) + buf = append(buf, '"') + return buf +} + +// appendBuild builds a json block from a json path. +func appendBuild(buf []byte, array bool, paths []pathResult, raw string, + stringify bool) []byte { + if !array { + buf = appendStringify(buf, paths[0].part) + buf = append(buf, ':') + } + if len(paths) > 1 { + n, numeric := atoui(paths[1]) + if numeric || (!paths[1].force && paths[1].part == "-1") { + buf = append(buf, '[') + buf = appendRepeat(buf, "null,", n) + buf = appendBuild(buf, true, paths[1:], raw, stringify) + buf = append(buf, ']') + } else { + buf = append(buf, '{') + buf = appendBuild(buf, false, paths[1:], raw, stringify) + buf = append(buf, '}') + } + } else { + if stringify { + buf = appendStringify(buf, raw) + } else { + buf = append(buf, raw...) + } + } + return buf +} + +// atoui does a rip conversion of string -> unigned int. +func atoui(r pathResult) (n int, ok bool) { + if r.force { + return 0, false + } + for i := 0; i < len(r.part); i++ { + if r.part[i] < '0' || r.part[i] > '9' { + return 0, false + } + n = n*10 + int(r.part[i]-'0') + } + return n, true +} + +// appendRepeat repeats string "n" times and appends to buf. +func appendRepeat(buf []byte, s string, n int) []byte { + for i := 0; i < n; i++ { + buf = append(buf, s...) + } + return buf +} + +// trim does a rip trim +func trim(s string) string { + for len(s) > 0 { + if s[0] <= ' ' { + s = s[1:] + continue + } + break + } + for len(s) > 0 { + if s[len(s)-1] <= ' ' { + s = s[:len(s)-1] + continue + } + break + } + return s +} + +// deleteTailItem deletes the previous key or comma. +func deleteTailItem(buf []byte) ([]byte, bool) { +loop: + for i := len(buf) - 1; i >= 0; i-- { + // look for either a ',',':','[' + switch buf[i] { + case '[': + return buf, true + case ',': + return buf[:i], false + case ':': + // delete tail string + i-- + for ; i >= 0; i-- { + if buf[i] == '"' { + i-- + for ; i >= 0; i-- { + if buf[i] == '"' { + i-- + if i >= 0 && buf[i] == '\\' { + i-- + continue + } + for ; i >= 0; i-- { + // look for either a ',','{' + switch buf[i] { + case '{': + return buf[:i+1], true + case ',': + return buf[:i], false + } + } + } + } + break + } + } + break loop + } + } + return buf, false +} + +var errNoChange = &errorType{"no change"} + +func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string, + stringify, del bool) ([]byte, error) { + var err error + var res gjson.Result + var found bool + if del { + if paths[0].part == "-1" && !paths[0].force { + res = gjson.Get(jstr, "#") + if res.Int() > 0 { + res = gjson.Get(jstr, strconv.FormatInt(int64(res.Int()-1), 10)) + found = true + } + } + } + if !found { + res = gjson.Get(jstr, paths[0].gpart) + } + if res.Index > 0 { + if len(paths) > 1 { + buf = append(buf, jstr[:res.Index]...) + buf, err = appendRawPaths(buf, res.Raw, paths[1:], raw, + stringify, del) + if err != nil { + return nil, err + } + buf = append(buf, jstr[res.Index+len(res.Raw):]...) + return buf, nil + } + buf = append(buf, jstr[:res.Index]...) + var exidx int // additional forward stripping + if del { + var delNextComma bool + buf, delNextComma = deleteTailItem(buf) + if delNextComma { + i, j := res.Index+len(res.Raw), 0 + for ; i < len(jstr); i, j = i+1, j+1 { + if jstr[i] <= ' ' { + continue + } + if jstr[i] == ',' { + exidx = j + 1 + } + break + } + } + } else { + if stringify { + buf = appendStringify(buf, raw) + } else { + buf = append(buf, raw...) + } + } + buf = append(buf, jstr[res.Index+len(res.Raw)+exidx:]...) + return buf, nil + } + if del { + return nil, errNoChange + } + n, numeric := atoui(paths[0]) + isempty := true + for i := 0; i < len(jstr); i++ { + if jstr[i] > ' ' { + isempty = false + break + } + } + if isempty { + if numeric { + jstr = "[]" + } else { + jstr = "{}" + } + } + jsres := gjson.Parse(jstr) + if jsres.Type != gjson.JSON { + if numeric { + jstr = "[]" + } else { + jstr = "{}" + } + jsres = gjson.Parse(jstr) + } + var comma bool + for i := 1; i < len(jsres.Raw); i++ { + if jsres.Raw[i] <= ' ' { + continue + } + if jsres.Raw[i] == '}' || jsres.Raw[i] == ']' { + break + } + comma = true + break + } + switch jsres.Raw[0] { + default: + return nil, &errorType{"json must be an object or array"} + case '{': + end := len(jsres.Raw) - 1 + for ; end > 0; end-- { + if jsres.Raw[end] == '}' { + break + } + } + buf = append(buf, jsres.Raw[:end]...) + if comma { + buf = append(buf, ',') + } + buf = appendBuild(buf, false, paths, raw, stringify) + buf = append(buf, '}') + return buf, nil + case '[': + var appendit bool + if !numeric { + if paths[0].part == "-1" && !paths[0].force { + appendit = true + } else { + return nil, &errorType{ + "cannot set array element for non-numeric key '" + + paths[0].part + "'"} + } + } + if appendit { + njson := trim(jsres.Raw) + if njson[len(njson)-1] == ']' { + njson = njson[:len(njson)-1] + } + buf = append(buf, njson...) + if comma { + buf = append(buf, ',') + } + + buf = appendBuild(buf, true, paths, raw, stringify) + buf = append(buf, ']') + return buf, nil + } + buf = append(buf, '[') + ress := jsres.Array() + for i := 0; i < len(ress); i++ { + if i > 0 { + buf = append(buf, ',') + } + buf = append(buf, ress[i].Raw...) + } + if len(ress) == 0 { + buf = appendRepeat(buf, "null,", n-len(ress)) + } else { + buf = appendRepeat(buf, ",null", n-len(ress)) + if comma { + buf = append(buf, ',') + } + } + buf = appendBuild(buf, true, paths, raw, stringify) + buf = append(buf, ']') + return buf, nil + } +} + +func isOptimisticPath(path string) bool { + for i := 0; i < len(path); i++ { + if path[i] < '.' || path[i] > 'z' { + return false + } + if path[i] > '9' && path[i] < 'A' { + return false + } + if path[i] > 'z' { + return false + } + } + return true +} + +// Set sets a json value for the specified path. +// A path is in dot syntax, such as "name.last" or "age". +// This function expects that the json is well-formed, and does not validate. +// Invalid json will not panic, but it may return back unexpected results. +// An error is returned if the path is not valid. +// +// A path is a series of keys separated by a dot. +// +// { +// "name": {"first": "Tom", "last": "Anderson"}, +// "age":37, +// "children": ["Sara","Alex","Jack"], +// "friends": [ +// {"first": "James", "last": "Murphy"}, +// {"first": "Roger", "last": "Craig"} +// ] +// } +// "name.last" >> "Anderson" +// "age" >> 37 +// "children.1" >> "Alex" +// +func Set(json, path string, value interface{}) (string, error) { + return SetOptions(json, path, value, nil) +} + +// SetBytes sets a json value for the specified path. +// If working with bytes, this method preferred over +// Set(string(data), path, value) +func SetBytes(json []byte, path string, value interface{}) ([]byte, error) { + return SetBytesOptions(json, path, value, nil) +} + +// SetRaw sets a raw json value for the specified path. +// This function works the same as Set except that the value is set as a +// raw block of json. This allows for setting premarshalled json objects. +func SetRaw(json, path, value string) (string, error) { + return SetRawOptions(json, path, value, nil) +} + +// SetRawOptions sets a raw json value for the specified path with options. +// This furnction works the same as SetOptions except that the value is set +// as a raw block of json. This allows for setting premarshalled json objects. +func SetRawOptions(json, path, value string, opts *Options) (string, error) { + var optimistic bool + if opts != nil { + optimistic = opts.Optimistic + } + res, err := set(json, path, value, false, false, optimistic, false) + if err == errNoChange { + return json, nil + } + return string(res), err +} + +// SetRawBytes sets a raw json value for the specified path. +// If working with bytes, this method preferred over +// SetRaw(string(data), path, value) +func SetRawBytes(json []byte, path string, value []byte) ([]byte, error) { + return SetRawBytesOptions(json, path, value, nil) +} + +type dtype struct{} + +// Delete deletes a value from json for the specified path. +func Delete(json, path string) (string, error) { + return Set(json, path, dtype{}) +} + +// DeleteBytes deletes a value from json for the specified path. +func DeleteBytes(json []byte, path string) ([]byte, error) { + return SetBytes(json, path, dtype{}) +} + +type stringHeader struct { + data unsafe.Pointer + len int +} + +type sliceHeader struct { + data unsafe.Pointer + len int + cap int +} + +func set(jstr, path, raw string, + stringify, del, optimistic, inplace bool) ([]byte, error) { + if path == "" { + return []byte(jstr), &errorType{"path cannot be empty"} + } + if !del && optimistic && isOptimisticPath(path) { + res := gjson.Get(jstr, path) + if res.Exists() && res.Index > 0 { + sz := len(jstr) - len(res.Raw) + len(raw) + if stringify { + sz += 2 + } + if inplace && sz <= len(jstr) { + if !stringify || !mustMarshalString(raw) { + jsonh := *(*stringHeader)(unsafe.Pointer(&jstr)) + jsonbh := sliceHeader{ + data: jsonh.data, len: jsonh.len, cap: jsonh.len} + jbytes := *(*[]byte)(unsafe.Pointer(&jsonbh)) + if stringify { + jbytes[res.Index] = '"' + copy(jbytes[res.Index+1:], []byte(raw)) + jbytes[res.Index+1+len(raw)] = '"' + copy(jbytes[res.Index+1+len(raw)+1:], + jbytes[res.Index+len(res.Raw):]) + } else { + copy(jbytes[res.Index:], []byte(raw)) + copy(jbytes[res.Index+len(raw):], + jbytes[res.Index+len(res.Raw):]) + } + return jbytes[:sz], nil + } + return []byte(jstr), nil + } + buf := make([]byte, 0, sz) + buf = append(buf, jstr[:res.Index]...) + if stringify { + buf = appendStringify(buf, raw) + } else { + buf = append(buf, raw...) + } + buf = append(buf, jstr[res.Index+len(res.Raw):]...) + return buf, nil + } + } + var paths []pathResult + r, simple := parsePath(path) + if simple { + paths = append(paths, r) + for r.more { + r, simple = parsePath(r.path) + if !simple { + break + } + paths = append(paths, r) + } + } + if !simple { + if del { + return []byte(jstr), + &errorType{"cannot delete value from a complex path"} + } + return setComplexPath(jstr, path, raw, stringify) + } + njson, err := appendRawPaths(nil, jstr, paths, raw, stringify, del) + if err != nil { + return []byte(jstr), err + } + return njson, nil +} + +func setComplexPath(jstr, path, raw string, stringify bool) ([]byte, error) { + res := gjson.Get(jstr, path) + if !res.Exists() || !(res.Index != 0 || len(res.Indexes) != 0) { + return []byte(jstr), errNoChange + } + if res.Index != 0 { + njson := []byte(jstr[:res.Index]) + if stringify { + njson = appendStringify(njson, raw) + } else { + njson = append(njson, raw...) + } + njson = append(njson, jstr[res.Index+len(res.Raw):]...) + jstr = string(njson) + } + if len(res.Indexes) > 0 { + type val struct { + index int + res gjson.Result + } + vals := make([]val, 0, len(res.Indexes)) + res.ForEach(func(_, vres gjson.Result) bool { + vals = append(vals, val{res: vres}) + return true + }) + if len(res.Indexes) != len(vals) { + return []byte(jstr), errNoChange + } + for i := 0; i < len(res.Indexes); i++ { + vals[i].index = res.Indexes[i] + } + sort.SliceStable(vals, func(i, j int) bool { + return vals[i].index > vals[j].index + }) + for _, val := range vals { + vres := val.res + index := val.index + njson := []byte(jstr[:index]) + if stringify { + njson = appendStringify(njson, raw) + } else { + njson = append(njson, raw...) + } + njson = append(njson, jstr[index+len(vres.Raw):]...) + jstr = string(njson) + } + } + return []byte(jstr), nil +} + +// SetOptions sets a json value for the specified path with options. +// A path is in dot syntax, such as "name.last" or "age". +// This function expects that the json is well-formed, and does not validate. +// Invalid json will not panic, but it may return back unexpected results. +// An error is returned if the path is not valid. +func SetOptions(json, path string, value interface{}, + opts *Options) (string, error) { + if opts != nil { + if opts.ReplaceInPlace { + // it's not safe to replace bytes in-place for strings + // copy the Options and set options.ReplaceInPlace to false. + nopts := *opts + opts = &nopts + opts.ReplaceInPlace = false + } + } + jsonh := *(*stringHeader)(unsafe.Pointer(&json)) + jsonbh := sliceHeader{data: jsonh.data, len: jsonh.len, cap: jsonh.len} + jsonb := *(*[]byte)(unsafe.Pointer(&jsonbh)) + res, err := SetBytesOptions(jsonb, path, value, opts) + return string(res), err +} + +// SetBytesOptions sets a json value for the specified path with options. +// If working with bytes, this method preferred over +// SetOptions(string(data), path, value) +func SetBytesOptions(json []byte, path string, value interface{}, + opts *Options) ([]byte, error) { + var optimistic, inplace bool + if opts != nil { + optimistic = opts.Optimistic + inplace = opts.ReplaceInPlace + } + jstr := *(*string)(unsafe.Pointer(&json)) + var res []byte + var err error + switch v := value.(type) { + default: + b, merr := jsongo.Marshal(value) + if merr != nil { + return nil, merr + } + raw := *(*string)(unsafe.Pointer(&b)) + res, err = set(jstr, path, raw, false, false, optimistic, inplace) + case dtype: + res, err = set(jstr, path, "", false, true, optimistic, inplace) + case string: + res, err = set(jstr, path, v, true, false, optimistic, inplace) + case []byte: + raw := *(*string)(unsafe.Pointer(&v)) + res, err = set(jstr, path, raw, true, false, optimistic, inplace) + case bool: + if v { + res, err = set(jstr, path, "true", false, false, optimistic, inplace) + } else { + res, err = set(jstr, path, "false", false, false, optimistic, inplace) + } + case int8: + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), + false, false, optimistic, inplace) + case int16: + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), + false, false, optimistic, inplace) + case int32: + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), + false, false, optimistic, inplace) + case int64: + res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), + false, false, optimistic, inplace) + case uint8: + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), + false, false, optimistic, inplace) + case uint16: + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), + false, false, optimistic, inplace) + case uint32: + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), + false, false, optimistic, inplace) + case uint64: + res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), + false, false, optimistic, inplace) + case float32: + res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), + false, false, optimistic, inplace) + case float64: + res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), + false, false, optimistic, inplace) + } + if err == errNoChange { + return json, nil + } + return res, err +} + +// SetRawBytesOptions sets a raw json value for the specified path with options. +// If working with bytes, this method preferred over +// SetRawOptions(string(data), path, value, opts) +func SetRawBytesOptions(json []byte, path string, value []byte, + opts *Options) ([]byte, error) { + jstr := *(*string)(unsafe.Pointer(&json)) + vstr := *(*string)(unsafe.Pointer(&value)) + var optimistic, inplace bool + if opts != nil { + optimistic = opts.Optimistic + inplace = opts.ReplaceInPlace + } + res, err := set(jstr, path, vstr, false, false, optimistic, inplace) + if err == errNoChange { + return json, nil + } + return res, err +} diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/LICENSE b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/LICENSE new file mode 100644 index 00000000..cd6e5328 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/LICENSE @@ -0,0 +1,199 @@ +Apache License + +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the +copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other +entities that control, are controlled by, or are under common control +with that entity. For the purposes of this definition, "control" means +(i) the power, direct or indirect, to cause the direction or management +of such entity, whether by contract or otherwise, or (ii) ownership +of fifty percent (50%) or more of the outstanding shares, or (iii) +beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, +and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation +or translation of a Source form, including but not limited to compiled +object code, generated documentation, and conversions to other media +types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a copyright +notice that is included in or attached to the work (an example is provided +in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial +revisions, annotations, elaborations, or other modifications represent, +as a whole, an original work of authorship. For the purposes of this +License, Derivative Works shall not include works that remain separable +from, or merely link (or bind by name) to the interfaces of, the Work +and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the +original version of the Work and any modifications or additions to +that Work or Derivative Works thereof, that is intentionally submitted +to Licensor for inclusion in the Work by the copyright owner or by an +individual or Legal Entity authorized to submit on behalf of the copyright +owner. For the purposes of this definition, "submitted" means any form of +electronic, verbal, or written communication sent to the Licensor or its +representatives, including but not limited to communication on electronic +mailing lists, source code control systems, and issue tracking systems +that are managed by, or on behalf of, the Licensor for the purpose of +discussing and improving the Work, but excluding communication that is +conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. +Subject to the terms and conditions of this License, each Contributor +hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable copyright license to reproduce, prepare +Derivative Works of, publicly display, publicly perform, sublicense, and +distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. +Subject to the terms and conditions of this License, each Contributor +hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, +royalty- free, irrevocable (except as stated in this section) patent +license to make, have made, use, offer to sell, sell, import, and +otherwise transfer the Work, where such license applies only to those +patent claims licensable by such Contributor that are necessarily +infringed by their Contribution(s) alone or by combination of +their Contribution(s) with the Work to which such Contribution(s) +was submitted. If You institute patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Work or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses granted +to You under this License for that Work shall terminate as of the date +such litigation is filed. + +4. Redistribution. +You may reproduce and distribute copies of the Work or Derivative Works +thereof in any medium, with or without modifications, and in Source or +Object form, provided that You meet the following conditions: + + a. You must give any other recipients of the Work or Derivative Works + a copy of this License; and + + b. You must cause any modified files to carry prominent notices stating + that You changed the files; and + + c. You must retain, in the Source form of any Derivative Works that + You distribute, all copyright, patent, trademark, and attribution + notices from the Source form of the Work, excluding those notices + that do not pertain to any part of the Derivative Works; and + + d. If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one of + the following places: within a NOTICE text file distributed as part + of the Derivative Works; within the Source form or documentation, + if provided along with the Derivative Works; or, within a display + generated by the Derivative Works, if and wherever such third-party + notices normally appear. The contents of the NOTICE file are for + informational purposes only and do not modify the License. You + may add Your own attribution notices within Derivative Works that + You distribute, alongside or as an addendum to the NOTICE text + from the Work, provided that such additional attribution notices + cannot be construed as modifying the License. You may add Your own + copyright statement to Your modifications and may provide additional + or different license terms and conditions for use, reproduction, or + distribution of Your modifications, or for any such Derivative Works + as a whole, provided Your use, reproduction, and distribution of the + Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. +Unless You explicitly state otherwise, any Contribution intentionally +submitted for inclusion in the Work by You to the Licensor shall be +under the terms and conditions of this License, without any additional +terms or conditions. Notwithstanding the above, nothing herein shall +supersede or modify the terms of any separate license agreement you may +have executed with Licensor regarding such Contributions. + +6. Trademarks. +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. +Unless required by applicable law or agreed to in writing, Licensor +provides the Work (and each Contributor provides its Contributions) on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +express or implied, including, without limitation, any warranties or +conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR +A PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any risks +associated with Your exercise of permissions under this License. + +8. Limitation of Liability. +In no event and under no legal theory, whether in tort (including +negligence), contract, or otherwise, unless required by applicable law +(such as deliberate and grossly negligent acts) or agreed to in writing, +shall any Contributor be liable to You for damages, including any direct, +indirect, special, incidental, or consequential damages of any character +arising as a result of this License or out of the use or inability to +use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other +commercial damages or losses), even if such Contributor has been advised +of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. +While redistributing the Work or Derivative Works thereof, You may +choose to offer, and charge a fee for, acceptance of support, warranty, +indemnity, or other liability obligations and/or rights consistent with +this License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf of +any other Contributor, and only if You agree to indemnify, defend, and +hold each Contributor harmless for any liability incurred by, or claims +asserted against, such Contributor by reason of your accepting any such +warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2022 VMware, Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/NOTICE b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/NOTICE new file mode 100644 index 00000000..482e2cbe --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/NOTICE @@ -0,0 +1,5 @@ +Copyright 2022 VMware, Inc. + +This product is licensed to you under the Apache License, V2.0 (the "License"). You may not use this product except in compliance with the License. + +This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/README.md b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/README.md new file mode 100644 index 00000000..cd4ea43d --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/README.md @@ -0,0 +1,61 @@ +# Go kit + +This folder contains the Go kit or SDK for Wasm Workers Server. Currently, it uses the regular STDIN / STDOUT approach to receive the request and provide the response. In the latest version we introduced the new HTTP bindings to send HTTP requests from inside the worker. + +## Bindings + +Wasm Workers Server is on the road to adopt Wasm components, but it's not there yet. However, we started adopting WIT to generate the bindings for the different languages. + +The host (Wasm Workers Server) and other languages like Rust and JavaScript rely on [wit-bindgen v0.2](https://github.com/bytecodealliance/wit-bindgen/tree/v0.2.0). However, the Go bindings were not available on that version so it caused some extra work to generate the Go bindings. + +These are the steps to recreate the current Go bindings: + +- Clone the wit-binding repository and checkout to the [35cb45f2](https://github.com/bytecodealliance/wit-bindgen/commit/35cb45f25eb113b54406f269778d46a37716a7c5) commit (between v0.6 - v0.7). This commit produces compatible binding identifiers and fixes an error with the types on the generated C / Go code: + + ```shell-session + git clone https://github.com/bytecodealliance/wit-bindgen/tree/main && \ + git checkout 35cb45f25eb113b54406f269778d46a37716a7c5 + ``` + +- Compile the project: + + ```shell-session + cargo build --release + ``` + +- Change your current directory to `wasm-workers-server/kits/go/worker/bindings`. +- Now, you need to use the compiled `wit-bindgen`: + + ```shell-session + ~/YOUR_LOCATION/wit-bindgen/target/release/wit-bindgen tiny-go ../../../../wit/go-ephemeral/ + ``` + +- Just note that we're using a specific `wit` folder for Go. The reason is that the syntax changed from v0.3. We will consolidate it once we adopt components. +- Edit the `bindings.c` file to define the `canonical_abi_realloc` and `canonical_abi_free`. wit-bindgen v0.2 expects these methods to be exported. However, the first method was renamed to `cabi_realloc` and the second was removed on v3.0. To fix it, locate the `__attribute__((__weak__, __export_name__("cabi_realloc")))` and replace it with the following two methods: + + ```c + __attribute__((__weak__, __export_name__("canonical_abi_realloc"))) void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) + { + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; + } + + __attribute__((weak, export_name("canonical_abi_free"))) void canonical_abi_free( + void *ptr, + size_t size, + size_t align) + { + free(ptr); + } + ``` + +- Done! + +## References + +* [Go documentation](https://workers.wasmlabs.dev/docs/languages/go) +* [Announcing Go Support for Wasm Workers Server](https://wasmlabs.dev/articles/go-support-on-wasm-workers-server/) diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.c b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.c new file mode 100644 index 00000000..7060edf6 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.c @@ -0,0 +1,223 @@ +// Generated by `wit-bindgen` 0.6.0. DO NOT EDIT! +#include "bindings.h" + +__attribute__((__import_module__("http"), __import_name__("send-http-request"))) void __wasm_import_http_send_http_request(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); + +__attribute__((__weak__, __export_name__("canonical_abi_realloc"))) void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) +{ + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +__attribute__((weak, export_name("canonical_abi_free"))) void canonical_abi_free( + void *ptr, + size_t size, + size_t align) +{ + free(ptr); +} + +// Helper Functions + +void http_types_uri_free(http_types_uri_t *ptr) +{ + bindings_string_free(ptr); +} + +void http_types_http_param_free(http_types_http_param_t *ptr) +{ + bindings_string_free(&ptr->f0); + bindings_string_free(&ptr->f1); +} + +void http_types_http_params_free(http_types_http_params_t *ptr) +{ + for (size_t i = 0; i < ptr->len; i++) + { + http_types_http_param_free(&ptr->ptr[i]); + } + if (ptr->len > 0) + { + free(ptr->ptr); + } +} + +void http_types_http_header_free(http_types_http_header_t *ptr) +{ + bindings_string_free(&ptr->f0); + bindings_string_free(&ptr->f1); +} + +void http_types_http_headers_free(http_types_http_headers_t *ptr) +{ + for (size_t i = 0; i < ptr->len; i++) + { + http_types_http_header_free(&ptr->ptr[i]); + } + if (ptr->len > 0) + { + free(ptr->ptr); + } +} + +void http_types_http_request_error_free(http_types_http_request_error_t *ptr) +{ + bindings_string_free(&ptr->message); +} + +void http_types_http_body_free(http_types_http_body_t *ptr) +{ + if (ptr->len > 0) + { + free(ptr->ptr); + } +} + +void bindings_option_http_body_free(bindings_option_http_body_t *ptr) +{ + if (ptr->is_some) + { + http_types_http_body_free(&ptr->val); + } +} + +void http_types_http_response_free(http_types_http_response_t *ptr) +{ + bindings_option_http_body_free(&ptr->body); + http_types_http_headers_free(&ptr->headers); +} + +void http_types_http_request_free(http_types_http_request_t *ptr) +{ + bindings_option_http_body_free(&ptr->body); + http_types_http_headers_free(&ptr->headers); + http_types_http_params_free(&ptr->params); + http_types_uri_free(&ptr->uri); +} + +void http_interface_http_request_free(http_interface_http_request_t *ptr) +{ + http_types_http_request_free(ptr); +} + +void http_interface_http_response_free(http_interface_http_response_t *ptr) +{ + http_types_http_response_free(ptr); +} + +void http_interface_http_request_error_free(http_interface_http_request_error_t *ptr) +{ + http_types_http_request_error_free(ptr); +} + +void bindings_result_http_response_http_request_error_free(bindings_result_http_response_http_request_error_t *ptr) +{ + if (!ptr->is_err) + { + http_interface_http_response_free(&ptr->val.ok); + } + else + { + http_interface_http_request_error_free(&ptr->val.err); + } +} + +void bindings_string_set(bindings_string_t *ret, const char *s) +{ + ret->ptr = (char *)s; + ret->len = strlen(s); +} + +void bindings_string_dup(bindings_string_t *ret, const char *s) +{ + ret->len = strlen(s); + ret->ptr = cabi_realloc(NULL, 0, 1, ret->len * 1); + memcpy(ret->ptr, s, ret->len * 1); +} + +void bindings_string_free(bindings_string_t *ret) +{ + if (ret->len > 0) + { + free(ret->ptr); + } + ret->ptr = NULL; + ret->len = 0; +} + +// Component Adapters + +void http_send_http_request(http_interface_http_request_t *request, bindings_result_http_response_http_request_error_t *ret) +{ + __attribute__((__aligned__(4))) + uint8_t ret_area[28]; + int32_t option; + int32_t option1; + int32_t option2; + if (((*request).body).is_some) + { + const http_types_http_body_t *payload0 = &((*request).body).val; + option = 1; + option1 = (int32_t)(*payload0).ptr; + option2 = (int32_t)(*payload0).len; + } + else + { + option = 0; + option1 = 0; + option2 = 0; + } + + int32_t ptr = (int32_t)&ret_area; + __wasm_import_http_send_http_request(option, option1, option2, (int32_t)((*request).headers).ptr, (int32_t)((*request).headers).len, (int32_t)(*request).method, (int32_t)((*request).params).ptr, (int32_t)((*request).params).len, (int32_t)((*request).uri).ptr, (int32_t)((*request).uri).len, ptr); + bindings_result_http_response_http_request_error_t result; + switch ((int32_t)(*((uint8_t *)(ptr + 0)))) + { + case 0: + { + result.is_err = false; + bindings_option_http_body_t option3; + switch ((int32_t)(*((uint8_t *)(ptr + 4)))) + { + case 0: + { + option3.is_some = false; + break; + } + case 1: + { + option3.is_some = true; + option3.val = (http_types_http_body_t){(uint8_t *)(*((int32_t *)(ptr + 8))), (size_t)(*((int32_t *)(ptr + 12)))}; + break; + } + } + + result.val.ok = (http_types_http_response_t){ + option3, + (http_types_http_headers_t){(http_types_http_header_t *)(*((int32_t *)(ptr + 16))), (size_t)(*((int32_t *)(ptr + 20)))}, + (uint16_t)((int32_t)(*((uint16_t *)(ptr + 24)))), + }; + break; + } + case 1: + { + result.is_err = true; + result.val.err = (http_types_http_request_error_t){ + (int32_t)(*((uint8_t *)(ptr + 4))), + (bindings_string_t){(char *)(*((int32_t *)(ptr + 8))), (size_t)(*((int32_t *)(ptr + 12)))}, + }; + break; + } + } + *ret = result; +} + +extern void __component_type_object_force_link_bindings(void); +void __component_type_object_force_link_bindings_public_use_in_this_compilation_unit(void) +{ + __component_type_object_force_link_bindings(); +} diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.go b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.go new file mode 100644 index 00000000..3d10f5fd --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.go @@ -0,0 +1,337 @@ +package bindings + +// #include "bindings.h" +import "C" + +import "unsafe" + +// http-types +type HttpTypesUri = string +type HttpTypesHttpStatus = uint16 +type HttpTypesHttpParam struct { + F0 string + F1 string +} + +type HttpTypesHttpParams = HttpTypesHttpParam +type HttpTypesHttpMethodKind int + +const ( +HttpTypesHttpMethodKindGet HttpTypesHttpMethodKind = iota +HttpTypesHttpMethodKindPost +HttpTypesHttpMethodKindPut +HttpTypesHttpMethodKindPatch +HttpTypesHttpMethodKindDelete +HttpTypesHttpMethodKindOptions +HttpTypesHttpMethodKindHead +) + +type HttpTypesHttpMethod struct { + kind HttpTypesHttpMethodKind +} + +func (n HttpTypesHttpMethod) Kind() HttpTypesHttpMethodKind { + return n.kind +} + +func HttpTypesHttpMethodGet() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindGet} +} + +func HttpTypesHttpMethodPost() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindPost} +} + +func HttpTypesHttpMethodPut() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindPut} +} + +func HttpTypesHttpMethodPatch() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindPatch} +} + +func HttpTypesHttpMethodDelete() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindDelete} +} + +func HttpTypesHttpMethodOptions() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindOptions} +} + +func HttpTypesHttpMethodHead() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindHead} +} + +type HttpTypesHttpHeader struct { + F0 string + F1 string +} + +type HttpTypesHttpHeaders = HttpTypesHttpHeader +type HttpTypesHttpErrorKind int + +const ( +HttpTypesHttpErrorKindInvalidRequest HttpTypesHttpErrorKind = iota +HttpTypesHttpErrorKindInvalidRequestBody +HttpTypesHttpErrorKindInvalidResponseBody +HttpTypesHttpErrorKindNotAllowed +HttpTypesHttpErrorKindInternalError +HttpTypesHttpErrorKindTimeout +HttpTypesHttpErrorKindRedirectLoop +) + +type HttpTypesHttpError struct { + kind HttpTypesHttpErrorKind +} + +func (n HttpTypesHttpError) Kind() HttpTypesHttpErrorKind { + return n.kind +} + +func HttpTypesHttpErrorInvalidRequest() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInvalidRequest} +} + +func HttpTypesHttpErrorInvalidRequestBody() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInvalidRequestBody} +} + +func HttpTypesHttpErrorInvalidResponseBody() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInvalidResponseBody} +} + +func HttpTypesHttpErrorNotAllowed() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindNotAllowed} +} + +func HttpTypesHttpErrorInternalError() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInternalError} +} + +func HttpTypesHttpErrorTimeout() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindTimeout} +} + +func HttpTypesHttpErrorRedirectLoop() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindRedirectLoop} +} + +type HttpTypesHttpRequestError struct { + Error HttpTypesHttpError + Message string +} + +type HttpTypesHttpBody = uint8 +type HttpTypesHttpResponse struct { + Body Option[[]uint8] + Headers []HttpTypesHttpHeader + Status uint16 +} + +type HttpTypesHttpRequest struct { + Body Option[[]uint8] + Headers []HttpTypesHttpHeader + Method HttpTypesHttpMethod + Params []HttpTypesHttpParam + Uri string +} + +// http +type HttpHttpRequest = HttpTypesHttpRequest +type HttpHttpResponse = HttpTypesHttpResponse +type HttpHttpRequestError = HttpTypesHttpRequestError +func HttpSendHttpRequest(request HttpTypesHttpRequest) Result[HttpTypesHttpResponse, HttpTypesHttpRequestError] { + var lower_request C.http_types_http_request_t + var lower_request_val C.http_types_http_request_t + var lower_request_val_body C.bindings_option_http_body_t + if request.Body.IsSome() { + var lower_request_val_body_val C.http_types_http_body_t + if len(request.Body.Unwrap()) == 0 { + lower_request_val_body_val.ptr = nil + lower_request_val_body_val.len = 0 + } else { + var empty_lower_request_val_body_val C.uint8_t + lower_request_val_body_val.ptr = (*C.uint8_t)(C.malloc(C.size_t(len(request.Body.Unwrap())) * C.size_t(unsafe.Sizeof(empty_lower_request_val_body_val)))) + lower_request_val_body_val.len = C.size_t(len(request.Body.Unwrap())) + for lower_request_val_body_val_i := range request.Body.Unwrap() { + lower_request_val_body_val_ptr := (*C.uint8_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lower_request_val_body_val.ptr)) + + uintptr(lower_request_val_body_val_i)*unsafe.Sizeof(empty_lower_request_val_body_val))) + lower_request_val_body_val_ptr_value := C.uint8_t(request.Body.Unwrap()[lower_request_val_body_val_i]) + *lower_request_val_body_val_ptr = lower_request_val_body_val_ptr_value + } + } + lower_request_val_body.val = lower_request_val_body_val + lower_request_val_body.is_some = true + } + lower_request_val.body = lower_request_val_body + var lower_request_val_headers C.http_types_http_headers_t + if len(request.Headers) == 0 { + lower_request_val_headers.ptr = nil + lower_request_val_headers.len = 0 + } else { + var empty_lower_request_val_headers C.http_types_http_header_t + lower_request_val_headers.ptr = (*C.http_types_http_header_t)(C.malloc(C.size_t(len(request.Headers)) * C.size_t(unsafe.Sizeof(empty_lower_request_val_headers)))) + lower_request_val_headers.len = C.size_t(len(request.Headers)) + for lower_request_val_headers_i := range request.Headers { + lower_request_val_headers_ptr := (*C.http_types_http_header_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lower_request_val_headers.ptr)) + + uintptr(lower_request_val_headers_i)*unsafe.Sizeof(empty_lower_request_val_headers))) + var lower_request_val_headers_ptr_value C.http_types_http_header_t + var lower_request_val_headers_ptr_value_f0 C.bindings_string_t + + lower_request_val_headers_ptr_value_f0.ptr = C.CString(request.Headers[lower_request_val_headers_i].F0) + lower_request_val_headers_ptr_value_f0.len = C.size_t(len(request.Headers[lower_request_val_headers_i].F0)) + lower_request_val_headers_ptr_value.f0 = lower_request_val_headers_ptr_value_f0 + var lower_request_val_headers_ptr_value_f1 C.bindings_string_t + + lower_request_val_headers_ptr_value_f1.ptr = C.CString(request.Headers[lower_request_val_headers_i].F1) + lower_request_val_headers_ptr_value_f1.len = C.size_t(len(request.Headers[lower_request_val_headers_i].F1)) + lower_request_val_headers_ptr_value.f1 = lower_request_val_headers_ptr_value_f1 + *lower_request_val_headers_ptr = lower_request_val_headers_ptr_value + } + } + lower_request_val.headers = lower_request_val_headers + var lower_request_val_method C.http_types_http_method_t + if request.Method.Kind() == HttpTypesHttpMethodKindGet { + lower_request_val_method = 0 + } + if request.Method.Kind() == HttpTypesHttpMethodKindPost { + lower_request_val_method = 1 + } + if request.Method.Kind() == HttpTypesHttpMethodKindPut { + lower_request_val_method = 2 + } + if request.Method.Kind() == HttpTypesHttpMethodKindPatch { + lower_request_val_method = 3 + } + if request.Method.Kind() == HttpTypesHttpMethodKindDelete { + lower_request_val_method = 4 + } + if request.Method.Kind() == HttpTypesHttpMethodKindOptions { + lower_request_val_method = 5 + } + if request.Method.Kind() == HttpTypesHttpMethodKindHead { + lower_request_val_method = 6 + } + lower_request_val.method = lower_request_val_method + var lower_request_val_params C.http_types_http_params_t + if len(request.Params) == 0 { + lower_request_val_params.ptr = nil + lower_request_val_params.len = 0 + } else { + var empty_lower_request_val_params C.http_types_http_param_t + lower_request_val_params.ptr = (*C.http_types_http_param_t)(C.malloc(C.size_t(len(request.Params)) * C.size_t(unsafe.Sizeof(empty_lower_request_val_params)))) + lower_request_val_params.len = C.size_t(len(request.Params)) + for lower_request_val_params_i := range request.Params { + lower_request_val_params_ptr := (*C.http_types_http_param_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lower_request_val_params.ptr)) + + uintptr(lower_request_val_params_i)*unsafe.Sizeof(empty_lower_request_val_params))) + var lower_request_val_params_ptr_value C.http_types_http_param_t + var lower_request_val_params_ptr_value_f0 C.bindings_string_t + + lower_request_val_params_ptr_value_f0.ptr = C.CString(request.Params[lower_request_val_params_i].F0) + lower_request_val_params_ptr_value_f0.len = C.size_t(len(request.Params[lower_request_val_params_i].F0)) + lower_request_val_params_ptr_value.f0 = lower_request_val_params_ptr_value_f0 + var lower_request_val_params_ptr_value_f1 C.bindings_string_t + + lower_request_val_params_ptr_value_f1.ptr = C.CString(request.Params[lower_request_val_params_i].F1) + lower_request_val_params_ptr_value_f1.len = C.size_t(len(request.Params[lower_request_val_params_i].F1)) + lower_request_val_params_ptr_value.f1 = lower_request_val_params_ptr_value_f1 + *lower_request_val_params_ptr = lower_request_val_params_ptr_value + } + } + lower_request_val.params = lower_request_val_params + var lower_request_val_uri C.bindings_string_t + var lower_request_val_uri_val C.bindings_string_t + + lower_request_val_uri_val.ptr = C.CString(request.Uri) + lower_request_val_uri_val.len = C.size_t(len(request.Uri)) + lower_request_val_uri = lower_request_val_uri_val + lower_request_val.uri = lower_request_val_uri + lower_request = lower_request_val + defer C.http_interface_http_request_free(&lower_request) + var ret C.bindings_result_http_response_http_request_error_t + C.http_send_http_request(&lower_request, &ret) + var lift_ret Result[HttpTypesHttpResponse, HttpTypesHttpRequestError] + if ret.is_err { + lift_ret_ptr := *(*C.http_interface_http_request_error_t)(unsafe.Pointer(&ret.val)) + var lift_ret_val HttpTypesHttpRequestError + var lift_ret_val_val HttpTypesHttpRequestError + var lift_ret_val_val_Error HttpTypesHttpError + if lift_ret_ptr.error == 0 { + lift_ret_val_val_Error = HttpTypesHttpErrorInvalidRequest() + } + if lift_ret_ptr.error == 1 { + lift_ret_val_val_Error = HttpTypesHttpErrorInvalidRequestBody() + } + if lift_ret_ptr.error == 2 { + lift_ret_val_val_Error = HttpTypesHttpErrorInvalidResponseBody() + } + if lift_ret_ptr.error == 3 { + lift_ret_val_val_Error = HttpTypesHttpErrorNotAllowed() + } + if lift_ret_ptr.error == 4 { + lift_ret_val_val_Error = HttpTypesHttpErrorInternalError() + } + if lift_ret_ptr.error == 5 { + lift_ret_val_val_Error = HttpTypesHttpErrorTimeout() + } + if lift_ret_ptr.error == 6 { + lift_ret_val_val_Error = HttpTypesHttpErrorRedirectLoop() + } + lift_ret_val_val.Error = lift_ret_val_val_Error + var lift_ret_val_val_Message string + lift_ret_val_val_Message = C.GoStringN(lift_ret_ptr.message.ptr, C.int(lift_ret_ptr.message.len)) + lift_ret_val_val.Message = lift_ret_val_val_Message + lift_ret_val = lift_ret_val_val + lift_ret.SetErr(lift_ret_val) + } else { + lift_ret_ptr := *(*C.http_interface_http_response_t)(unsafe.Pointer(&ret.val)) + var lift_ret_val HttpTypesHttpResponse + var lift_ret_val_val HttpTypesHttpResponse + var lift_ret_val_val_Body Option[[]uint8] + if lift_ret_ptr.body.is_some { + var lift_ret_val_val_Body_val []uint8 + lift_ret_val_val_Body_val = make([]uint8, lift_ret_ptr.body.val.len) + if lift_ret_ptr.body.val.len > 0 { + for lift_ret_val_val_Body_val_i := 0; lift_ret_val_val_Body_val_i < int(lift_ret_ptr.body.val.len); lift_ret_val_val_Body_val_i++ { + var empty_lift_ret_val_val_Body_val C.uint8_t + lift_ret_val_val_Body_val_ptr := *(*C.uint8_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lift_ret_ptr.body.val.ptr)) + + uintptr(lift_ret_val_val_Body_val_i)*unsafe.Sizeof(empty_lift_ret_val_val_Body_val))) + var list_lift_ret_val_val_Body_val uint8 + list_lift_ret_val_val_Body_val = uint8(lift_ret_val_val_Body_val_ptr) + lift_ret_val_val_Body_val[lift_ret_val_val_Body_val_i] = list_lift_ret_val_val_Body_val + } + } + lift_ret_val_val_Body.Set(lift_ret_val_val_Body_val) + } else { + lift_ret_val_val_Body.Unset() + } + lift_ret_val_val.Body = lift_ret_val_val_Body + var lift_ret_val_val_Headers []HttpTypesHttpHeader + lift_ret_val_val_Headers = make([]HttpTypesHttpHeader, lift_ret_ptr.headers.len) + if lift_ret_ptr.headers.len > 0 { + for lift_ret_val_val_Headers_i := 0; lift_ret_val_val_Headers_i < int(lift_ret_ptr.headers.len); lift_ret_val_val_Headers_i++ { + var empty_lift_ret_val_val_Headers C.http_types_http_header_t + lift_ret_val_val_Headers_ptr := *(*C.http_types_http_header_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lift_ret_ptr.headers.ptr)) + + uintptr(lift_ret_val_val_Headers_i)*unsafe.Sizeof(empty_lift_ret_val_val_Headers))) + var list_lift_ret_val_val_Headers HttpTypesHttpHeader + var list_lift_ret_val_val_Headers_F0 string + list_lift_ret_val_val_Headers_F0 = C.GoStringN(lift_ret_val_val_Headers_ptr.f0.ptr, C.int(lift_ret_val_val_Headers_ptr.f0.len)) + list_lift_ret_val_val_Headers.F0 = list_lift_ret_val_val_Headers_F0 + var list_lift_ret_val_val_Headers_F1 string + list_lift_ret_val_val_Headers_F1 = C.GoStringN(lift_ret_val_val_Headers_ptr.f1.ptr, C.int(lift_ret_val_val_Headers_ptr.f1.len)) + list_lift_ret_val_val_Headers.F1 = list_lift_ret_val_val_Headers_F1 + lift_ret_val_val_Headers[lift_ret_val_val_Headers_i] = list_lift_ret_val_val_Headers + } + } + lift_ret_val_val.Headers = lift_ret_val_val_Headers + var lift_ret_val_val_Status uint16 + var lift_ret_val_val_Status_val uint16 + lift_ret_val_val_Status_val = uint16(lift_ret_ptr.status) + lift_ret_val_val_Status = lift_ret_val_val_Status_val + lift_ret_val_val.Status = lift_ret_val_val_Status + lift_ret_val = lift_ret_val_val + lift_ret.Set(lift_ret_val) + } + return lift_ret +} + diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.h b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.h new file mode 100644 index 00000000..4952b434 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings.h @@ -0,0 +1,131 @@ +// Generated by `wit-bindgen` 0.6.0. DO NOT EDIT! +#ifndef __BINDINGS_BINDINGS_H +#define __BINDINGS_BINDINGS_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +typedef struct { + char*ptr; + size_t len; +} bindings_string_t; + +typedef bindings_string_t http_types_uri_t; + +typedef uint16_t http_types_http_status_t; + +typedef struct { + bindings_string_t f0; + bindings_string_t f1; +} http_types_http_param_t; + +typedef struct { + http_types_http_param_t *ptr; + size_t len; +} http_types_http_params_t; + +typedef uint8_t http_types_http_method_t; + +#define HTTP_TYPES_HTTP_METHOD_GET 0 +#define HTTP_TYPES_HTTP_METHOD_POST 1 +#define HTTP_TYPES_HTTP_METHOD_PUT 2 +#define HTTP_TYPES_HTTP_METHOD_PATCH 3 +#define HTTP_TYPES_HTTP_METHOD_DELETE 4 +#define HTTP_TYPES_HTTP_METHOD_OPTIONS 5 +#define HTTP_TYPES_HTTP_METHOD_HEAD 6 + +typedef struct { + bindings_string_t f0; + bindings_string_t f1; +} http_types_http_header_t; + +typedef struct { + http_types_http_header_t *ptr; + size_t len; +} http_types_http_headers_t; + +typedef uint8_t http_types_http_error_t; + +#define HTTP_TYPES_HTTP_ERROR_INVALID_REQUEST 0 +#define HTTP_TYPES_HTTP_ERROR_INVALID_REQUEST_BODY 1 +#define HTTP_TYPES_HTTP_ERROR_INVALID_RESPONSE_BODY 2 +#define HTTP_TYPES_HTTP_ERROR_NOT_ALLOWED 3 +#define HTTP_TYPES_HTTP_ERROR_INTERNAL_ERROR 4 +#define HTTP_TYPES_HTTP_ERROR_TIMEOUT 5 +#define HTTP_TYPES_HTTP_ERROR_REDIRECT_LOOP 6 + +typedef struct { + http_types_http_error_t error; + bindings_string_t message; +} http_types_http_request_error_t; + +typedef struct { + uint8_t *ptr; + size_t len; +} http_types_http_body_t; + +typedef struct { + bool is_some; + http_types_http_body_t val; +} bindings_option_http_body_t; + +typedef struct { + bindings_option_http_body_t body; + http_types_http_headers_t headers; + http_types_http_status_t status; +} http_types_http_response_t; + +typedef struct { + bindings_option_http_body_t body; + http_types_http_headers_t headers; + http_types_http_method_t method; + http_types_http_params_t params; + http_types_uri_t uri; +} http_types_http_request_t; + +typedef http_types_http_request_t http_interface_http_request_t; + +typedef http_types_http_response_t http_interface_http_response_t; + +typedef http_types_http_request_error_t http_interface_http_request_error_t; + +typedef struct { + bool is_err; + union { + http_interface_http_response_t ok; + http_interface_http_request_error_t err; + } val; +} bindings_result_http_response_http_request_error_t; + +// Imported Functions from `http` +void http_send_http_request(http_interface_http_request_t *request, bindings_result_http_response_http_request_error_t *ret); + +// Helper Functions + +void http_types_uri_free(http_types_uri_t *ptr); +void http_types_http_param_free(http_types_http_param_t *ptr); +void http_types_http_params_free(http_types_http_params_t *ptr); +void http_types_http_header_free(http_types_http_header_t *ptr); +void http_types_http_headers_free(http_types_http_headers_t *ptr); +void http_types_http_request_error_free(http_types_http_request_error_t *ptr); +void http_types_http_body_free(http_types_http_body_t *ptr); +void bindings_option_http_body_free(bindings_option_http_body_t *ptr); +void http_types_http_response_free(http_types_http_response_t *ptr); +void http_types_http_request_free(http_types_http_request_t *ptr); +void http_interface_http_request_free(http_interface_http_request_t *ptr); +void http_interface_http_response_free(http_interface_http_response_t *ptr); +void http_interface_http_request_error_free(http_interface_http_request_error_t *ptr); +void bindings_result_http_response_http_request_error_free(bindings_result_http_response_http_request_error_t *ptr); +void bindings_string_set(bindings_string_t *ret, const char*s); +void bindings_string_dup(bindings_string_t *ret, const char*s); +void bindings_string_free(bindings_string_t *ret); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings_types.go b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings_types.go new file mode 100644 index 00000000..3d923fc8 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings/bindings_types.go @@ -0,0 +1,108 @@ +package bindings + +// inspired from https://github.com/moznion/go-optional + +type optionKind int + +const ( +none optionKind = iota +some +) + +type Option[T any] struct { + kind optionKind + val T +} + +// IsNone returns true if the option is None. +func (o Option[T]) IsNone() bool { + return o.kind == none +} + +// IsSome returns true if the option is Some. +func (o Option[T]) IsSome() bool { + return o.kind == some +} + +// Unwrap returns the value if the option is Some. +func (o Option[T]) Unwrap() T { + if o.kind != some { + panic("Option is None") + } + return o.val +} + +// Set sets the value and returns it. +func (o *Option[T]) Set(val T) T { + o.kind = some + o.val = val + return val +} + +// Unset sets the value to None. +func (o *Option[T]) Unset() { + o.kind = none +} + +// Some is a constructor for Option[T] which represents Some. +func Some[T any](v T) Option[T] { + return Option[T]{ + kind: some, + val: v, + } +} + +// None is a constructor for Option[T] which represents None. +func None[T any]() Option[T] { + return Option[T]{ + kind: none, + } +} + +type ResultKind int + +const ( +Ok ResultKind = iota +Err +) + +type Result[T any, E any] struct { + Kind ResultKind + Val T + Err E +} + +func (r Result[T, E]) IsOk() bool { + return r.Kind == Ok +} + +func (r Result[T, E]) IsErr() bool { + return r.Kind == Err +} + +func (r Result[T, E]) Unwrap() T { + if r.Kind != Ok { + panic("Result is Err") + } + return r.Val +} + +func (r Result[T, E]) UnwrapErr() E { + if r.Kind != Err { + panic("Result is Ok") + } + return r.Err +} + +func (r *Result[T, E]) Set(val T) T { + r.Kind = Ok + r.Val = val + return val +} + +func (r *Result[T, E]) SetErr(err E) E { + r.Kind = Err + r.Err = err + return err +} + diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/doc.go b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/doc.go new file mode 100644 index 00000000..2f1fbafb --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/doc.go @@ -0,0 +1,13 @@ +/** + * + * === Go support for WASM Workers Server === + * + * This package provides a simple way to write WASM workers in Go. It uses the gjson, sjson libraries instead + * of Go's standard encoding/json package due to the following reasons: + * -- as of writing this file, the default Go compiler does not support the WASI backend, + * -- TinyGo (which does support WASI) does not support reflection and hence, we need to rely on a JSON library + * that does not use reflection + * + */ + +package worker diff --git a/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/worker.go b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/worker.go new file mode 100644 index 00000000..ddb4fcae --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/github.com/vmware-labs/wasm-workers-server/kits/go/worker/worker.go @@ -0,0 +1,267 @@ +package worker + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "strings" + "unicode/utf8" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings" + + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +type ContextKey string + +const ( + CacheKey ContextKey = "CACHE" + ParamsKey ContextKey = "PARAMS" +) + +type input struct { + Url string + Method string + Headers map[string]string + Body string +} + +type output struct { + Data string + Headers map[string]string + Status uint16 + Base64 bool + + httpHeader http.Header +} + +var ( + cache map[string]string + params map[string]string +) + +func init() { + cache = make(map[string]string) + params = make(map[string]string) +} + +// output implements the http.ResponseWriter interface + +func (o *output) Header() http.Header { + if o.httpHeader == nil { + o.httpHeader = http.Header{} + } + + return o.httpHeader +} + +func (o *output) Write(data []byte) (int, error) { + if utf8.Valid(data) { + o.Data = string(data) + } else { + o.Base64 = true + o.Data = base64.StdEncoding.EncodeToString(data) + } + + if o.Status == 0 { + o.Status = 200 + } + + for k, v := range o.httpHeader { + o.Headers[k] = v[0] + } + + out, _ := sjson.Set("", "data", o.Data) + out, _ = sjson.Set(out, "status", o.Status) + out, _ = sjson.Set(out, "base64", o.Base64) + out, _ = sjson.SetRaw(out, "headers", "{}") + out, _ = sjson.SetRaw(out, "kv", "{}") + + for k, v := range o.Headers { + out, _ = sjson.Set(out, fmt.Sprintf("headers.%s", k), v) + } + + for k, v := range cache { + out, _ = sjson.Set(out, fmt.Sprintf("kv.%s", k), v) + } + + fmt.Println(out) + + return len(o.Data), nil +} + +func (o *output) WriteHeader(statusCode int) { + o.Status = uint16(statusCode) +} + +func readInput() (*input, error) { + stdin, err := io.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + + in := &input{ + Url: gjson.GetBytes(stdin, "url").String(), + Method: gjson.GetBytes(stdin, "method").String(), + Body: gjson.GetBytes(stdin, "body").String(), + Headers: make(map[string]string), + } + + if gjson.GetBytes(stdin, "headers").Exists() { + gjson.GetBytes(stdin, "headers").ForEach(func(key, value gjson.Result) bool { + in.Headers[key.String()] = value.String() + return true + }) + } + + if gjson.GetBytes(stdin, "kv").Exists() { + gjson.GetBytes(stdin, "kv").ForEach(func(key, value gjson.Result) bool { + cache[key.String()] = value.String() + return true + }) + } + + if gjson.GetBytes(stdin, "params").Exists() { + gjson.GetBytes(stdin, "params").ForEach(func(key, value gjson.Result) bool { + params[key.String()] = value.String() + return true + }) + } + + return in, nil +} + +func createRequest(in *input) (*http.Request, error) { + req, err := http.NewRequest(in.Method, in.Url, strings.NewReader(in.Body)) + if err != nil { + return nil, err + } + + for k, v := range in.Headers { + req.Header.Set(k, v) + } + + req = req.WithContext(context.WithValue(req.Context(), CacheKey, cache)) + req = req.WithContext(context.WithValue(req.Context(), ParamsKey, params)) + + return req, nil +} + +func getWriterRequest() (*output, *http.Request) { + in, err := readInput() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + req, err := createRequest(in) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + w := &output{ + Headers: make(map[string]string), + } + + return w, req +} + +func Serve(handler http.Handler) { + handler.ServeHTTP(getWriterRequest()) +} + +func ServeFunc(handler http.HandlerFunc) { + handler(getWriterRequest()) +} + +func SendHttpRequest(req *http.Request) (*http.Response, error) { + var method bindings.HttpTypesHttpMethod + switch req.Method { + case "GET": + method = bindings.HttpTypesHttpMethodGet() + case "POST": + method = bindings.HttpTypesHttpMethodPost() + case "PUT": + method = bindings.HttpTypesHttpMethodPut() + case "PATCH": + method = bindings.HttpTypesHttpMethodPatch() + case "DELETE": + method = bindings.HttpTypesHttpMethodDelete() + case "OPTIONS": + method = bindings.HttpTypesHttpMethodOptions() + case "HEAD": + method = bindings.HttpTypesHttpMethodHead() + default: + method = bindings.HttpTypesHttpMethodGet() + } + + // Iterate to get the headers + headers := make([]bindings.HttpTypesHttpHeader, 0, len(req.Header)) + for key, values := range req.Header { + for _, value := range values { + header := bindings.HttpTypesHttpHeader{F0: key, F1: value} + headers = append(headers, header) + } + } + + // Read the body request and convert it + body := []uint8{} + + if req.Body != nil { + readBody, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, err + } + defer req.Body.Close() + + body = readBody + } + + + // Convert body to []uint8 + bodyBytes := []uint8(body) + + bRequest := bindings.HttpTypesHttpRequest { + Body: bindings.Some(bodyBytes), + // Body: bindings.Some([]uint8{}), + Headers: headers, + Method: method, + Params: []bindings.HttpTypesHttpParam{}, + Uri: req.URL.String(), + } + + result := bindings.HttpSendHttpRequest(bRequest) + + if result.IsOk() { + response := result.Unwrap() + + // Create a new http.Response + httpResponse := &http.Response{} + httpResponse.StatusCode = int(response.Status) + + if response.Body.IsSome() { + body := response.Body.Unwrap() + httpResponse.Body = ioutil.NopCloser(bytes.NewReader(body)) + } + + // Set the headers + httpResponse.Header = make(http.Header) + for _, header := range response.Headers { + httpResponse.Header.Add(header.F0, header.F1) + } + + return httpResponse, nil + } else { + err := result.UnwrapErr() + + return nil, errors.New(err.Message) + } +} diff --git a/examples/containers/apps-src/user-generation-go/vendor/modules.txt b/examples/containers/apps-src/user-generation-go/vendor/modules.txt new file mode 100644 index 00000000..0dadb3a7 --- /dev/null +++ b/examples/containers/apps-src/user-generation-go/vendor/modules.txt @@ -0,0 +1,16 @@ +# github.com/tidwall/gjson v1.14.4 +## explicit; go 1.12 +github.com/tidwall/gjson +# github.com/tidwall/match v1.1.1 +## explicit; go 1.15 +github.com/tidwall/match +# github.com/tidwall/pretty v1.2.1 +## explicit; go 1.16 +github.com/tidwall/pretty +# github.com/tidwall/sjson v1.2.5 +## explicit; go 1.14 +github.com/tidwall/sjson +# github.com/vmware-labs/wasm-workers-server v1.4.0 +## explicit; go 1.20 +github.com/vmware-labs/wasm-workers-server/kits/go/worker +github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings diff --git a/examples/containers/apps-src/user-generation-js/user-generation-js.js b/examples/containers/apps-src/user-generation-js/user-generation-js.js new file mode 100644 index 00000000..0cd29d76 --- /dev/null +++ b/examples/containers/apps-src/user-generation-js/user-generation-js.js @@ -0,0 +1,41 @@ +const setCache = (key, data) => Cache.set(key, data); +const getCache = key => Cache.get(key); + +const reply = async (request) => { + try { + let res = await fetch("https://random-data-api.com/api/v2/users"); + let res_json = await res.json(); + + let generated_users_counter = getCache("generated_users_counter"); + if (!generated_users_counter) { + generated_users_counter = 1; + } else { + generated_users_counter = parseInt(generated_users_counter, 10) + 1; + } + setCache("generated_users_counter", generated_users_counter.toString()); + + return new Response( + JSON.stringify({ + "user": { + "first_name": res_json.first_name, + "last_name": res_json.last_name, + "username": res_json.username, + "email": res_json.email + }, + "generated_users": generated_users_counter + }), + { + "headers": { + "x-generated-by": "wasm-workers-server" + } + } + ); + } catch (e) { + return new Response(JSON.stringify({ "error": e.toString() }), { "status": 500 }); + } +} + +// Subscribe to the Fetch event +addEventListener("fetch", event => { + return event.respondWith(reply(event.request)); +}); diff --git a/examples/containers/apps-src/user-generation-js/user-generation-js.toml b/examples/containers/apps-src/user-generation-js/user-generation-js.toml new file mode 100644 index 00000000..2ce902ec --- /dev/null +++ b/examples/containers/apps-src/user-generation-js/user-generation-js.toml @@ -0,0 +1,14 @@ +name = "user-generation-js" +version = "1" + +[data] +[data.kv] +namespace = "generated_users_counter" + +[[folders]] +from = "./tmp" +to = "/tmp" + +[features] +[features.http_requests] +allowed_hosts = ["random-data-api.com"] diff --git a/examples/containers/apps-src/user-generation-python/index.py b/examples/containers/apps-src/user-generation-python/index.py new file mode 100644 index 00000000..c5433e7d --- /dev/null +++ b/examples/containers/apps-src/user-generation-python/index.py @@ -0,0 +1,29 @@ +import json +from collections import namedtuple + +class User: + def __init__(self, first_name, last_name, username, email): + self.first_name, self.last_name, self.username, self.email = first_name, last_name, username, email + + @staticmethod + def from_json(dict): + return User(dict['first_name'], + dict['last_name'], + dict['username'], + dict['email']) + +def worker(request): + sample_json = """{ + "first_name": "Tracie", + "last_name": "Schroeder", + "username": "tracie.schroeder", + "email": "tracie.schroeder@email.com", + "password": "secret" + }""" + user = json.loads(sample_json, object_hook=User.from_json) + return Response( + json.dumps({ + "user": user.__dict__, + "some_file_contents": open("/tmp/file.txt").read(), + }, separators=(',', ':')) + ) diff --git a/examples/containers/apps-src/user-generation-python/index.toml b/examples/containers/apps-src/user-generation-python/index.toml new file mode 100644 index 00000000..e3e76103 --- /dev/null +++ b/examples/containers/apps-src/user-generation-python/index.toml @@ -0,0 +1,6 @@ +name = "user-generation-python" +version = "1" + +[[folders]] +from = "./tmp" +to = "/tmp" \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-python/tmp b/examples/containers/apps-src/user-generation-python/tmp new file mode 120000 index 00000000..b684689e --- /dev/null +++ b/examples/containers/apps-src/user-generation-python/tmp @@ -0,0 +1 @@ +../tmp/ \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-ruby/index.rb b/examples/containers/apps-src/user-generation-ruby/index.rb new file mode 100644 index 00000000..c56248da --- /dev/null +++ b/examples/containers/apps-src/user-generation-ruby/index.rb @@ -0,0 +1,31 @@ +require 'json' + +def allowed_attributes + [:first_name, :last_name, :username, :email] +end + +def worker(req) + sampleJson = <<-JSON + { + "first_name": "Tracie", + "last_name": "Schroeder", + "username": "tracie.schroeder", + "email": "tracie.schroeder@email.com", + "password": "secret" + } + JSON + + user = JSON.parse sampleJson, symbolize_names: true + + user_response = Hash.new + allowed_attributes.each do |attribute| + user_response[attribute] = user[attribute] + end + + res = Response.new({ + "user": user_response, + "some_file_contents": File.read("/tmp/file.txt") + }.to_json) + res.headers["x-generated-by"] = "wasm-workers-server" + res +end diff --git a/examples/containers/apps-src/user-generation-ruby/index.toml b/examples/containers/apps-src/user-generation-ruby/index.toml new file mode 100644 index 00000000..6387e4b9 --- /dev/null +++ b/examples/containers/apps-src/user-generation-ruby/index.toml @@ -0,0 +1,6 @@ +name = "user-generation-ruby" +version = "1" + +[[folders]] +from = "./tmp" +to = "/tmp" \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-ruby/tmp b/examples/containers/apps-src/user-generation-ruby/tmp new file mode 120000 index 00000000..b684689e --- /dev/null +++ b/examples/containers/apps-src/user-generation-ruby/tmp @@ -0,0 +1 @@ +../tmp/ \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-rust/.cargo/config.toml b/examples/containers/apps-src/user-generation-rust/.cargo/config.toml new file mode 100644 index 00000000..c410192d --- /dev/null +++ b/examples/containers/apps-src/user-generation-rust/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +target = "wasm32-wasi" +rustflags = ["-Cstrip=symbols", "-Copt-level=z", "-Cpanic=abort", "-Cdebuginfo=none"] \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-rust/.gitignore b/examples/containers/apps-src/user-generation-rust/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/examples/containers/apps-src/user-generation-rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/containers/apps-src/user-generation-rust/Cargo.lock b/examples/containers/apps-src/user-generation-rust/Cargo.lock new file mode 100644 index 00000000..b1b8c151 --- /dev/null +++ b/examples/containers/apps-src/user-generation-rust/Cargo.lock @@ -0,0 +1,332 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "user-generation-rust" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "wasm-workers-rs", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-workers-rs" +version = "1.4.0" +source = "git+https://github.com/vmware-labs/wasm-workers-server/?tag=v1.4.0#0f5404b445e2ffef282a6f7d4a29693f33b31e6b" +dependencies = [ + "anyhow", + "base64", + "http", + "serde", + "serde_json", + "wit-bindgen-rust", + "worker", +] + +[[package]] +name = "wit-bindgen-gen-core" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "anyhow", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-gen-rust" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "heck", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-gen-rust-wasm" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "heck", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "async-trait", + "bitflags", + "wit-bindgen-rust-impl", +] + +[[package]] +name = "wit-bindgen-rust-impl" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "proc-macro2", + "syn 1.0.109", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust-wasm", +] + +[[package]] +name = "wit-parser" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "anyhow", + "id-arena", + "pulldown-cmark", + "unicode-normalization", + "unicode-xid", +] + +[[package]] +name = "worker" +version = "1.4.0" +source = "git+https://github.com/vmware-labs/wasm-workers-server/?tag=v1.4.0#0f5404b445e2ffef282a6f7d4a29693f33b31e6b" +dependencies = [ + "anyhow", + "http", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "wasi", +] diff --git a/examples/containers/apps-src/user-generation-rust/Cargo.toml b/examples/containers/apps-src/user-generation-rust/Cargo.toml new file mode 100644 index 00000000..83ebef51 --- /dev/null +++ b/examples/containers/apps-src/user-generation-rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "user-generation-rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.85" +wasm-workers-rs = { git = "https://github.com/vmware-labs/wasm-workers-server/", tag = "v1.4.0" } \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-rust/rust-toolchain.toml b/examples/containers/apps-src/user-generation-rust/rust-toolchain.toml new file mode 100644 index 00000000..b6582546 --- /dev/null +++ b/examples/containers/apps-src/user-generation-rust/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +targets = ["wasm32-wasi"] \ No newline at end of file diff --git a/examples/containers/apps-src/user-generation-rust/src/main.rs b/examples/containers/apps-src/user-generation-rust/src/main.rs new file mode 100644 index 00000000..649a20c3 --- /dev/null +++ b/examples/containers/apps-src/user-generation-rust/src/main.rs @@ -0,0 +1,61 @@ +use anyhow::{anyhow, Result}; +use serde::{Deserialize, Serialize}; +use std::fs::read_to_string; + +use wasm_workers_rs::{ + bindings, + http::{Request, Response}, + worker, Cache, Content, +}; + +#[derive(Deserialize, Serialize)] +struct User { + first_name: String, + last_name: String, + username: String, + email: String, +} + +#[derive(Deserialize, Serialize)] +struct ResponseData { + user: User, + some_file_contents: String, + generated_users: u32, +} + +const GENERATED_USERS_COUNTER: &str = "generated_users_counter"; + +#[worker(cache)] +fn reply(_req: Request, cache: &mut Cache) -> Result> { + let external_request = Request::builder() + .uri("https://random-data-api.com/api/v2/users") + .body(String::new()) + .map_err(|err| anyhow!("could not build request: {:?}", err))?; + let response = bindings::send_http_request(external_request) + .map_err(|err| anyhow!("could not fetch data from remote service: {:?}", err))?; + let user: User = serde_json::from_slice(response.body()) + .map_err(|err| anyhow!("invalid data returned by remote service: {:?}", err))?; + + let generated_users_counter = match cache.get(GENERATED_USERS_COUNTER) { + Some(counter) => counter.parse::().unwrap_or(0), + None => 0, + } + 1; + + cache.insert(GENERATED_USERS_COUNTER.to_string(), generated_users_counter.to_string()); + + let response = ResponseData { + user, + some_file_contents: read_to_string("/tmp/file.txt")?, + generated_users: generated_users_counter, + }; + + Ok(Response::builder() + .status(200) + .header("x-generated-by", "wasm-workers-server") + .body( + serde_json::to_string(&response) + .map_err(|err| anyhow!("could not marshal result: {:?}", err))? + .into(), + ) + .map_err(|err| anyhow!("could not retrieve remote service result: {:?}", err))?) +} diff --git a/examples/containers/apps-src/user-generation-rust/user-generation-rust.toml b/examples/containers/apps-src/user-generation-rust/user-generation-rust.toml new file mode 100644 index 00000000..63dc8671 --- /dev/null +++ b/examples/containers/apps-src/user-generation-rust/user-generation-rust.toml @@ -0,0 +1,14 @@ +name = "user-generation-rust" +version = "1" + +[data] +[data.kv] +namespace = "generated_users_counter" + +[[folders]] +from = "./tmp" +to = "/tmp" + +[features] +[features.http_requests] +allowed_hosts = ["random-data-api.com"] diff --git a/examples/containers/apps/root/.wws.toml b/examples/containers/apps/root/.wws.toml new file mode 100644 index 00000000..16c478cf --- /dev/null +++ b/examples/containers/apps/root/.wws.toml @@ -0,0 +1,83 @@ +version = 1 + +[[repositories]] +name = "wasmlabs" +url = "https://workers.wasmlabs.dev/repository/v1/index.toml" + +[[repositories.runtimes]] +name = "python" +version = "3.11.1+20230217-1" +tags = [ + "latest", + "3.11", + "3.11.1", +] +status = "active" +extensions = ["py"] +args = [ + "--", + "/src/index.py", +] + +[repositories.runtimes.binary] +url = "https://github.com/vmware-labs/webassembly-language-runtimes/releases/download/python%2F3.11.1%2B20230217-15dfbed/python-3.11.1.wasm" +filename = "python.wasm" + +[repositories.runtimes.binary.checksum] +type = "sha256" +value = "66589b289f76bd716120f76f234e4dd663064ed5b6256c92d441d84e51d7585d" + +[repositories.runtimes.polyfill] +url = "https://workers.wasmlabs.dev/repository/v1/files/python/3-1/poly.py" +filename = "poly.py" + +[repositories.runtimes.polyfill.checksum] +type = "sha256" +value = "74d10132b0577a39e4ea30002d4605b7cdfb8f39abca327a45c8b313de7ea304" + +[repositories.runtimes.wrapper] +url = "https://workers.wasmlabs.dev/repository/v1/files/python/3-1/wrapper.txt" +filename = "wrapper.txt" + +[repositories.runtimes.wrapper.checksum] +type = "sha256" +value = "cf1edc5b1427180ec09d18f4d169580379f1b12001f30e330759f9a0f8745357" + +[[repositories.runtimes]] +name = "ruby" +version = "3.2.0+20230215-1" +tags = [ + "latest", + "3.2", + "3.2.0", +] +status = "active" +extensions = ["rb"] +args = [ + "--", + "/src/index.rb", +] + +[repositories.runtimes.binary] +url = "https://github.com/vmware-labs/webassembly-language-runtimes/releases/download/ruby%2F3.2.0%2B20230215-1349da9/ruby-3.2.0.wasm" +filename = "ruby.wasm" + +[repositories.runtimes.binary.checksum] +type = "sha256" +value = "abe348fba157a756f86194be445c77c99e8ed64ca76495ea07ed984f09eb66ae" + +[repositories.runtimes.polyfill] +url = "https://workers.wasmlabs.dev/repository/v1/files/ruby/3-1/poly.rb" +filename = "poly.rb" + +[repositories.runtimes.polyfill.checksum] +type = "sha256" +value = "449855a5d315879ab0ad830aa6a3f689e68fed4490617ea03efc77c9da64f630" + +[repositories.runtimes.wrapper] +url = "https://workers.wasmlabs.dev/repository/v1/files/ruby/3-1/wrapper.txt" +filename = "wrapper.txt" + +[repositories.runtimes.wrapper.checksum] +type = "sha256" +value = "6d808b4747cf30f82665a38a47e1176513bbdd6ad558c09db03d719e33ad2da0" \ No newline at end of file diff --git a/examples/containers/apps/root/user-generation-go.toml b/examples/containers/apps/root/user-generation-go.toml new file mode 100644 index 00000000..d8c05b5b --- /dev/null +++ b/examples/containers/apps/root/user-generation-go.toml @@ -0,0 +1,14 @@ +name = "user-generation-go" +version = "1" + +[data] +[data.kv] +namespace = "generated_users_counter" + +[[folders]] +from = "./tmp" +to = "/tmp" + +[features] +[features.http_requests] +allowed_hosts = ["random-data-api.com"] \ No newline at end of file diff --git a/examples/containers/apps/root/user-generation-js.js b/examples/containers/apps/root/user-generation-js.js new file mode 100644 index 00000000..0cd29d76 --- /dev/null +++ b/examples/containers/apps/root/user-generation-js.js @@ -0,0 +1,41 @@ +const setCache = (key, data) => Cache.set(key, data); +const getCache = key => Cache.get(key); + +const reply = async (request) => { + try { + let res = await fetch("https://random-data-api.com/api/v2/users"); + let res_json = await res.json(); + + let generated_users_counter = getCache("generated_users_counter"); + if (!generated_users_counter) { + generated_users_counter = 1; + } else { + generated_users_counter = parseInt(generated_users_counter, 10) + 1; + } + setCache("generated_users_counter", generated_users_counter.toString()); + + return new Response( + JSON.stringify({ + "user": { + "first_name": res_json.first_name, + "last_name": res_json.last_name, + "username": res_json.username, + "email": res_json.email + }, + "generated_users": generated_users_counter + }), + { + "headers": { + "x-generated-by": "wasm-workers-server" + } + } + ); + } catch (e) { + return new Response(JSON.stringify({ "error": e.toString() }), { "status": 500 }); + } +} + +// Subscribe to the Fetch event +addEventListener("fetch", event => { + return event.respondWith(reply(event.request)); +}); diff --git a/examples/containers/apps/root/user-generation-js.toml b/examples/containers/apps/root/user-generation-js.toml new file mode 100644 index 00000000..2ce902ec --- /dev/null +++ b/examples/containers/apps/root/user-generation-js.toml @@ -0,0 +1,14 @@ +name = "user-generation-js" +version = "1" + +[data] +[data.kv] +namespace = "generated_users_counter" + +[[folders]] +from = "./tmp" +to = "/tmp" + +[features] +[features.http_requests] +allowed_hosts = ["random-data-api.com"] diff --git a/examples/containers/apps/root/user-generation-python/index.py b/examples/containers/apps/root/user-generation-python/index.py new file mode 100644 index 00000000..c5433e7d --- /dev/null +++ b/examples/containers/apps/root/user-generation-python/index.py @@ -0,0 +1,29 @@ +import json +from collections import namedtuple + +class User: + def __init__(self, first_name, last_name, username, email): + self.first_name, self.last_name, self.username, self.email = first_name, last_name, username, email + + @staticmethod + def from_json(dict): + return User(dict['first_name'], + dict['last_name'], + dict['username'], + dict['email']) + +def worker(request): + sample_json = """{ + "first_name": "Tracie", + "last_name": "Schroeder", + "username": "tracie.schroeder", + "email": "tracie.schroeder@email.com", + "password": "secret" + }""" + user = json.loads(sample_json, object_hook=User.from_json) + return Response( + json.dumps({ + "user": user.__dict__, + "some_file_contents": open("/tmp/file.txt").read(), + }, separators=(',', ':')) + ) diff --git a/examples/containers/apps/root/user-generation-python/index.toml b/examples/containers/apps/root/user-generation-python/index.toml new file mode 100644 index 00000000..e3e76103 --- /dev/null +++ b/examples/containers/apps/root/user-generation-python/index.toml @@ -0,0 +1,6 @@ +name = "user-generation-python" +version = "1" + +[[folders]] +from = "./tmp" +to = "/tmp" \ No newline at end of file diff --git a/examples/containers/apps/root/user-generation-python/tmp/file.txt b/examples/containers/apps/root/user-generation-python/tmp/file.txt new file mode 100644 index 00000000..0ee38955 --- /dev/null +++ b/examples/containers/apps/root/user-generation-python/tmp/file.txt @@ -0,0 +1 @@ +Some content diff --git a/examples/containers/apps/root/user-generation-ruby/index.rb b/examples/containers/apps/root/user-generation-ruby/index.rb new file mode 100644 index 00000000..c56248da --- /dev/null +++ b/examples/containers/apps/root/user-generation-ruby/index.rb @@ -0,0 +1,31 @@ +require 'json' + +def allowed_attributes + [:first_name, :last_name, :username, :email] +end + +def worker(req) + sampleJson = <<-JSON + { + "first_name": "Tracie", + "last_name": "Schroeder", + "username": "tracie.schroeder", + "email": "tracie.schroeder@email.com", + "password": "secret" + } + JSON + + user = JSON.parse sampleJson, symbolize_names: true + + user_response = Hash.new + allowed_attributes.each do |attribute| + user_response[attribute] = user[attribute] + end + + res = Response.new({ + "user": user_response, + "some_file_contents": File.read("/tmp/file.txt") + }.to_json) + res.headers["x-generated-by"] = "wasm-workers-server" + res +end diff --git a/examples/containers/apps/root/user-generation-ruby/index.toml b/examples/containers/apps/root/user-generation-ruby/index.toml new file mode 100644 index 00000000..6387e4b9 --- /dev/null +++ b/examples/containers/apps/root/user-generation-ruby/index.toml @@ -0,0 +1,6 @@ +name = "user-generation-ruby" +version = "1" + +[[folders]] +from = "./tmp" +to = "/tmp" \ No newline at end of file diff --git a/examples/containers/apps/root/user-generation-ruby/tmp/file.txt b/examples/containers/apps/root/user-generation-ruby/tmp/file.txt new file mode 100644 index 00000000..0ee38955 --- /dev/null +++ b/examples/containers/apps/root/user-generation-ruby/tmp/file.txt @@ -0,0 +1 @@ +Some content diff --git a/examples/containers/apps/root/user-generation-rust.toml b/examples/containers/apps/root/user-generation-rust.toml new file mode 100644 index 00000000..63dc8671 --- /dev/null +++ b/examples/containers/apps/root/user-generation-rust.toml @@ -0,0 +1,14 @@ +name = "user-generation-rust" +version = "1" + +[data] +[data.kv] +namespace = "generated_users_counter" + +[[folders]] +from = "./tmp" +to = "/tmp" + +[features] +[features.http_requests] +allowed_hosts = ["random-data-api.com"] diff --git a/examples/containers/tmp/file.txt b/examples/containers/tmp/file.txt new file mode 100644 index 00000000..f42d6a47 --- /dev/null +++ b/examples/containers/tmp/file.txt @@ -0,0 +1 @@ +Mount contents coming from the Docker Host :)