├── .eslintignore ├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ ├── benchmarks.yaml │ ├── lint.yml │ ├── releases.yaml │ └── tests.yaml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── buffer.go ├── buffer_test.go ├── c_bindings ├── Cargo.toml ├── decoder.rs ├── encoder.rs ├── lib.rs ├── tests │ └── polyglot_test.c └── types.rs ├── decode.go ├── decode_test.go ├── decoder.go ├── decoder.rs ├── decoder.test.ts ├── decoder.ts ├── decoder_test.go ├── encode.go ├── encode_test.go ├── encoder.go ├── encoder.rs ├── encoder.test.ts ├── encoder.ts ├── encoder_test.go ├── generator ├── golang │ ├── defaults.go │ ├── generator.go │ ├── headers.go │ ├── imports.go │ ├── structs.go │ └── templates │ │ ├── base.templ │ │ ├── decode.templ │ │ ├── decodeMap.templ │ │ ├── encode.templ │ │ ├── encodeMap.templ │ │ ├── enums.templ │ │ ├── errors.templ │ │ ├── headers.templ │ │ ├── imports.templ │ │ ├── messages.templ │ │ ├── structs.templ │ │ └── templates.go ├── rust │ ├── defaults.go │ ├── file.go │ ├── generator.go │ ├── headers.go │ ├── imports.go │ ├── structs.go │ └── templates │ │ ├── base.templ │ │ ├── decode.templ │ │ ├── decodeMap.templ │ │ ├── encode.templ │ │ ├── enums.templ │ │ ├── headers.templ │ │ ├── imports.templ │ │ ├── messages.templ │ │ ├── structs.templ │ │ └── templates.go └── typescript │ ├── defaults.go │ ├── generator.go │ ├── headers.go │ ├── structs.go │ └── templates │ ├── body.templ │ ├── decode.templ │ ├── decodeMap.templ │ ├── encode.templ │ ├── enums.templ │ ├── head.templ │ ├── headers.templ │ ├── imports.templ │ ├── messages.templ │ ├── structs.templ │ └── templates.go ├── go.mod ├── go.sum ├── images └── logo.svg ├── index.ts ├── integration-test-data.json ├── integration.rs ├── integration.test.ts ├── jest.config.json ├── kind.rs ├── kind.ts ├── lib.rs ├── package-lock.json ├── package.json ├── polyglot.h ├── polyglot.h.in ├── polyglot.pc ├── polyglot.pc.in ├── protoc-gen-go-polyglot └── main.go ├── protoc-gen-rs-polyglot └── main.go ├── protoc-gen-ts-polyglot └── main.go ├── tsconfig.json ├── utils └── utils.go ├── v2 ├── benchmarks │ ├── Makefile │ ├── bench.proto │ ├── go.mod │ ├── go.sum │ ├── main_test.go │ ├── polyglot │ │ └── benchmark │ │ │ └── bench.polyglot.go │ ├── vtproto │ │ └── benchmark │ │ │ ├── bench.pb.go │ │ │ └── bench_vtproto.pb.go │ └── vtproto_test.go ├── buffer.go ├── buffer_test.go ├── decode.go ├── decode_test.go ├── decoder.go ├── decoder_test.go ├── encode.go ├── encode_test.go ├── encoder.go ├── encoder_test.go ├── error.go ├── generator │ ├── golang │ │ ├── defaults.go │ │ ├── generator.go │ │ ├── headers.go │ │ ├── imports.go │ │ ├── structs.go │ │ └── templates │ │ │ ├── base.templ │ │ │ ├── decode.templ │ │ │ ├── decodeMap.templ │ │ │ ├── encode.templ │ │ │ ├── encodeMap.templ │ │ │ ├── enums.templ │ │ │ ├── errors.templ │ │ │ ├── headers.templ │ │ │ ├── imports.templ │ │ │ ├── messages.templ │ │ │ ├── structs.templ │ │ │ └── templates.go │ ├── rust │ │ ├── defaults.go │ │ ├── file.go │ │ ├── generator.go │ │ ├── headers.go │ │ ├── imports.go │ │ ├── structs.go │ │ └── templates │ │ │ ├── base.templ │ │ │ ├── decode.templ │ │ │ ├── decodeMap.templ │ │ │ ├── encode.templ │ │ │ ├── enums.templ │ │ │ ├── headers.templ │ │ │ ├── imports.templ │ │ │ ├── messages.templ │ │ │ ├── structs.templ │ │ │ └── templates.go │ └── typescript │ │ ├── defaults.go │ │ ├── generator.go │ │ ├── headers.go │ │ ├── structs.go │ │ └── templates │ │ ├── body.templ │ │ ├── decode.templ │ │ ├── decodeMap.templ │ │ ├── encode.templ │ │ ├── enums.templ │ │ ├── head.templ │ │ ├── headers.templ │ │ ├── imports.templ │ │ ├── messages.templ │ │ ├── structs.templ │ │ └── templates.go ├── go.mod ├── go.sum ├── pool.go ├── pool_test.go ├── protoc-gen-go-polyglot │ └── main.go ├── utils │ └── utils.go └── version │ ├── current_version │ └── version.go └── version ├── current_version └── version.go /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | target 4 | .github 5 | **/*.go 6 | **/*.rs 7 | go.mod 8 | go.sum 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "airbnb-base", 8 | "prettier" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module", 14 | "project": "./tsconfig.json" 15 | }, 16 | "plugins": [ 17 | "eslint-plugin-prettier" 18 | ], 19 | "ignorePatterns": [ 20 | "node_modules" 21 | ], 22 | "rules": { 23 | "prettier/prettier": "error", 24 | "import/export": "off", 25 | "import/prefer-default-export": "off", 26 | "max-classes-per-file": "off", 27 | "no-underscore-dangle": "off", 28 | "no-nested-ternary": "off", 29 | "@typescript-eslint/no-use-before-define": "off", 30 | "no-plusplus": "off", 31 | "prefer-destructuring": "off" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "gomod" # See documentation for possible values 13 | directory: "/v2" # Location of package manifests 14 | schedule: 15 | interval: "weekly" 16 | - package-ecosystem: "cargo" # See documentation for possible values 17 | directory: "/" # Location of package manifests 18 | schedule: 19 | interval: "weekly" 20 | - package-ecosystem: "npm" # See documentation for possible values 21 | directory: "/" # Location of package manifests 22 | schedule: 23 | interval: "weekly" 24 | -------------------------------------------------------------------------------- /.github/workflows/benchmarks.yaml: -------------------------------------------------------------------------------- 1 | name: Benchmarks 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | vtproto-golang: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | - name: Install Go 12 | uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.22" 15 | check-latest: true 16 | cache: true 17 | - name: Run Benchmarks 18 | run: make benchmark-polyglot 19 | working-directory: v2/benchmarks 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | typescript: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | 12 | - name: Setup node 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: "20" 16 | registry-url: "https://registry.npmjs.org" 17 | 18 | - name: Cache the dependency directories 19 | uses: actions/cache@v4 20 | with: 21 | path: node_modules 22 | key: ${{ runner.os }}-${{ hashFiles('*.json') }} 23 | 24 | - name: Install dependencies with npm 25 | run: npm install --save-dev 26 | 27 | rust: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | 33 | - name: Set up Rust 34 | run: | 35 | rustup toolchain install stable --profile minimal --no-self-update 36 | rustup default stable 37 | rustup component add clippy 38 | shell: bash 39 | 40 | - name: Cargo lint 41 | run: cargo fmt --all -- --check 42 | 43 | - name: Cargo clippy 44 | run: cargo clippy --all -- -D warnings 45 | golang: 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v4 50 | 51 | - name: Set up Golang 52 | uses: actions/setup-go@v5 53 | with: 54 | go-version: '1.22' 55 | cache: true 56 | 57 | - name: golangci-lint 58 | uses: golangci/golangci-lint-action@v6 59 | with: 60 | version: latest 61 | working-directory: v2 62 | -------------------------------------------------------------------------------- /.github/workflows/releases.yaml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | cargo: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up Rust 16 | run: | 17 | rustup toolchain install stable --profile minimal --no-self-update 18 | rustup default stable 19 | shell: bash 20 | 21 | - name: Cargo build 22 | run: cargo build --release 23 | 24 | - name: Publish to crates.io 25 | run: cargo publish --token $CARGO_REGISTRY_TOKEN 26 | env: 27 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 28 | npm: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Install Node 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: "20" 38 | registry-url: "https://registry.npmjs.org" 39 | 40 | - name: Cache the Node Modules Directory 41 | uses: actions/cache@v4 42 | with: 43 | path: node_modules 44 | key: ${{ runner.os }}-${{ hashFiles('package.json') }} 45 | 46 | - name: Install Node Dependencies with NPM 47 | run: npm install 48 | 49 | - name: Build with NPM 50 | run: npm run build 51 | 52 | - name: Publish to NPM 53 | run: npm publish --access public 54 | working-directory: ./dist 55 | env: 56 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 57 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | golang: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | 12 | - name: Install Go 13 | uses: actions/setup-go@v5 14 | with: 15 | go-version: "1.22" 16 | check-latest: true 17 | cache: true 18 | 19 | - name: Run Tests (Go v1) 20 | run: go test -v ./... 21 | 22 | - name: Run Tests (Go v2) 23 | run: | 24 | cd v2 25 | go test -v ./... 26 | rust: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | 32 | - name: Set up Rust 33 | run: | 34 | rustup toolchain install stable --profile minimal --no-self-update 35 | rustup default stable 36 | rustup target add wasm32-wasip1 37 | shell: bash 38 | 39 | - name: Cargo fmt 40 | run: cargo fmt --all -- --check 41 | 42 | - name: Cargo check 43 | run: cargo check 44 | 45 | - name: Cargo check wasm32-wasip1 46 | run: cargo check --target wasm32-wasip1 47 | 48 | - name: Run Tests (Rust) 49 | run: cargo test 50 | typescript: 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v4 55 | 56 | - name: Install Node 57 | uses: actions/setup-node@v4 58 | with: 59 | node-version: "20" 60 | registry-url: "https://registry.npmjs.org" 61 | 62 | - name: Cache the Node Modules Directory 63 | uses: actions/cache@v4 64 | with: 65 | path: node_modules 66 | key: ${{ runner.os }}-${{ hashFiles('*.json') }} 67 | 68 | - name: Install Node Dependencies with NPM 69 | run: npm install 70 | 71 | - name: Run Tests (JS) 72 | run: npm run test 73 | c: 74 | runs-on: ubuntu-latest 75 | steps: 76 | - name: Checkout 77 | uses: actions/checkout@v4 78 | 79 | - name: Update APT 80 | run: sudo apt-get update -y 81 | 82 | - name: Install Valgrind 83 | run: sudo apt-get install valgrind -y 84 | 85 | - name: Run Tests 86 | run: make clib_test 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | 17 | target/ 18 | Cargo.lock 19 | 20 | .idea 21 | .DS_Store 22 | 23 | coverage 24 | 25 | node_modules 26 | 27 | dist/ 28 | 29 | *.h-e -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres 6 | to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [v2.0.0] 2024-04-23] 11 | 12 | ### Changes 13 | 14 | - Updated the names of error values in Go to fit with Go's standard code-style conventions 15 | - significant performance improvements for the golang implementation 16 | this update makes necessary changes to the public interface of the library to accomplish this 17 | 18 | ### Fixes 19 | 20 | - Fixed a bug in Polyglot Go where the capacity of the buffer would not grow properly resulting in silent short writes and corrupted data 21 | 22 | ## [v1.1.4] - 2023-10-12 23 | 24 | ### Fixes 25 | 26 | - Fixed a bug where the protoc generators would crash/fail ([#27](https://github.com/loopholelabs/polyglot/issues/27)) 27 | 28 | ## [v1.1.3] - 2023-09-01 29 | 30 | ### Features 31 | 32 | - Added Durable Errors for Decoders ([#28](https://github.com/loopholelabs/polyglot/pull/28)) 33 | 34 | ## [v1.1.2] - 2023-08-26 35 | 36 | ### Fixes 37 | 38 | - Fixes an issue where decoding certain i32 or i64 values would result in an incorrect value being returned. 39 | 40 | ## [v1.1.1] - 2023-06-12 41 | 42 | ### Fixes 43 | 44 | - Fixing the `decode_none` Rust function which would previously crash if it was decoding from a buffer of size 0. 45 | 46 | ### Dependencies 47 | 48 | - Bumping Typescript `@typescript-eslint/eslint-plugin` from `^5.59.10` to `^5.59.11` 49 | - Bumping Typescript `@typescript-eslint/parser` from `^5.59.10` to `^5.59.11` 50 | - Bumping Typescript `@types/node` from `^20.2.5` to `^20.3.0` 51 | - Bumping Rust `serde_json` from `1.0.82` to `1.0.96` 52 | - Bumping Rust `base64` from `0.21.0` to `0.21.2` 53 | 54 | ## [v1.1.0] - 2023-06-07 55 | 56 | ### Changes 57 | 58 | - Merging Typescript, Golang, and Rust implementations into a single repository 59 | 60 | [unreleased]: https://github.com/loopholelabs/scale/compare/v1.2.2...HEAD 61 | [v1.2.2]: https://github.com/loopholelabs/scale/compare/v1.2.2 62 | [v1.2.1]: https://github.com/loopholelabs/scale/compare/v1.2.1 63 | [v1.2.0]: https://github.com/loopholelabs/scale/compare/v1.2.0 64 | [v1.1.4]: https://github.com/loopholelabs/scale/compare/v1.1.4 65 | [v1.1.3]: https://github.com/loopholelabs/scale/compare/v1.1.3 66 | [v1.1.2]: https://github.com/loopholelabs/scale/compare/v1.1.2 67 | [v1.1.1]: https://github.com/loopholelabs/scale/compare/v1.1.1 68 | [v1.1.0]: https://github.com/loopholelabs/scale/compare/v1.1.0 69 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Polyglot Community Code of Conduct 2 | 3 | Polyglot follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Polyglot uses GitHub to manage reviews of pull requests. 4 | 5 | - If you have a trivial fix or improvement, go ahead and create a pull request, 6 | addressing (with `@...`) the maintainer of this repository (see 7 | [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. 8 | 9 | - If you plan to do something more involved, first discuss your ideas 10 | on our [discord](https://loopholelabs.io/discord). 11 | This will avoid unnecessary work and surely give you and us a good deal 12 | of inspiration. 13 | 14 | - Relevant coding style guidelines are the [Go Code Review 15 | Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) 16 | and the _Formatting and style_ section of Peter Bourgon's [Go: Best 17 | Practices for Production 18 | Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). 19 | 20 | - Be sure to sign off on the [DCO](https://github.com/apps/dco) 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polyglot_rs" 3 | version = "2.0.5" 4 | edition = "2021" 5 | description="A high-performance serialization framework used for encoding and decoding arbitrary datastructures across languages." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/loopholelabs/polyglot" 8 | readme = "README.md" 9 | keywords = ["polyglot", "serialization", "deserialization", "encoding", "decoding"] 10 | exclude = [ 11 | "**/*.go", 12 | "**/*.templ", 13 | "go.mod", 14 | "go.sum", 15 | "*/**.md", 16 | "*/**.json", 17 | "*/**.ts", 18 | "node_modules", 19 | ] 20 | 21 | [lib] 22 | path = "lib.rs" 23 | 24 | [dependencies] 25 | byteorder = "1" 26 | 27 | [dev-dependencies] 28 | serde = { version = "1.0", features = ["derive"] } 29 | serde_json = "1.0.107" 30 | base64 = "0.22.1" 31 | num_enum = "0.7.0" 32 | 33 | [profile.release] 34 | opt-level = 3 35 | lto = true 36 | codegen-units = 1 37 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | - Shivansh Vij @shivanshvij 2 | - Alex Sørlie Glomsaas @supermanifolds 3 | - Felicitas Pojtinger @pojntfx 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 2 | VERSION=$(shell cat $(ROOT_DIR)/version/current_version) 3 | VERSION_MAJOR=$(shell echo $(VERSION) | cut -c2) 4 | VERSION_MINOR=$(shell echo $(VERSION) | cut -c4) 5 | VERSION_MICRO=$(shell echo $(VERSION) | cut -c6) 6 | CLIB_SO=libpolyglot 7 | CLIB_SO_DEV=$(CLIB_SO).so 8 | CLIB_SO_MAN=$(CLIB_SO_DEV).$(VERSION_MAJOR) 9 | CLIB_SO_FULL=$(CLIB_SO_DEV).$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_MICRO) 10 | CLIB_HEADER=polyglot.h 11 | CLIB_SO_DEV_RELEASE=c_bindings/target/release/$(CLIB_SO_DEV) 12 | CLIB_SO_DEV_DEBUG=c_bindings/target/debug/$(CLIB_SO_DEV) 13 | CLIB_PKG_CONFIG=polyglot.pc 14 | PREFIX ?= /usr/local 15 | 16 | OS_NAME := $(shell uname -s | tr A-Z a-z) 17 | 18 | CPU_BITS = $(shell getconf LONG_BIT) 19 | ifeq ($(CPU_BITS), 32) 20 | LIBDIR ?= $(PREFIX)/lib 21 | else 22 | LIBDIR ?= $(PREFIX)/lib$(CPU_BITS) 23 | endif 24 | 25 | INCLUDE_DIR ?= $(PREFIX)/include 26 | PKG_CONFIG_LIBDIR ?= $(LIBDIR)/pkgconfig 27 | MAN_DIR ?= $(PREFIX)/share/man 28 | 29 | RELEASE ?=0 30 | 31 | .PHONY: clib_debug 32 | clib_debug: 33 | cd c_bindings && cargo build --all && ([ -f ./target/debug/$(CLIB_SO).dylib ] && (echo "DYLIB exists, copying" && cp ./target/debug/$(CLIB_SO).dylib ./target/debug/$(CLIB_SO_DEV)) || echo "DYLIB does not exist, ignoring") && ln -sfv target/debug/$(CLIB_SO_DEV) target/debug/$(CLIB_SO_FULL) && ln -sfv target/debug/$(CLIB_SO_DEV) target/debug/$(CLIB_SO_MAN) && cd - 34 | 35 | $(CLIB_SO_DEV_RELEASE): 36 | cd c_bindings && cargo build --all --release && ([ -f ./target/release/$(CLIB_SO).dylib ] && (echo "DYLIB exists, copying" && cp ./target/release/$(CLIB_SO).dylib ./target/release/$(CLIB_SO_DEV)) || echo "DYLIB does not exist, ignoring") && cd - 37 | 38 | $(CLIB_SO_DEV_DEBUG): clib_debug 39 | 40 | clib: $(CLIB_HEADER) $(CLIB_SO_DEV_RELEASE) $(CLIB_PKG_CONFIG) 41 | 42 | .PHONY: $(CLIB_HEADER) 43 | $(CLIB_HEADER): $(CLIB_HEADER).in 44 | cp $(CLIB_HEADER).in $(CLIB_HEADER) 45 | sed -i -e 's/@_VERSION_MAJOR@/$(VERSION_MAJOR)/' \ 46 | $(CLIB_HEADER) 47 | sed -i -e 's/@_VERSION_MINOR@/$(VERSION_MINOR)/' \ 48 | $(CLIB_HEADER) 49 | sed -i -e 's/@_VERSION_MICRO@/$(VERSION_MICRO)/' \ 50 | $(CLIB_HEADER) 51 | 52 | .PHONY: $(CLIB_PKG_CONFIG) 53 | $(CLIB_PKG_CONFIG): $(CLIB_PKG_CONFIG).in 54 | cp $(CLIB_PKG_CONFIG).in $(CLIB_PKG_CONFIG) 55 | sed -i -e 's|@_VERSION_MAJOR@|$(VERSION_MAJOR)|' $(CLIB_PKG_CONFIG) 56 | sed -i -e 's|@_VERSION_MINOR@|$(VERSION_MINOR)|' $(CLIB_PKG_CONFIG) 57 | sed -i -e 's|@_VERSION_MICRO@|$(VERSION_MICRO)|' $(CLIB_PKG_CONFIG) 58 | sed -i -e 's|@PREFIX@|$(PREFIX)|' $(CLIB_PKG_CONFIG) 59 | sed -i -e 's|@LIBDIR@|$(LIBDIR)|' $(CLIB_PKG_CONFIG) 60 | sed -i -e 's|@INCLUDE_DIR@|$(INCLUDE_DIR)|' $(CLIB_PKG_CONFIG) 61 | 62 | .PHONY: clib_test 63 | clib_test: $(CLIB_SO_DEV_DEBUG) $(CLIB_HEADER) 64 | $(eval TMPDIR := $(shell mktemp -d)) 65 | cp $(CLIB_SO_DEV_DEBUG) $(TMPDIR)/$(CLIB_SO_FULL) 66 | ln -sfv $(TMPDIR)/$(CLIB_SO_FULL) $(TMPDIR)/$(CLIB_SO_MAN) 67 | ln -sfv $(TMPDIR)/$(CLIB_SO_FULL) $(TMPDIR)/$(CLIB_SO_DEV) 68 | cp $(CLIB_HEADER) $(TMPDIR)/$(shell basename $(CLIB_HEADER)) 69 | gcc -g -Wall -Wextra -L$(TMPDIR) -I$(TMPDIR) -o $(TMPDIR)/polyglot_test c_bindings/tests/polyglot_test.c -lpolyglot 70 | if [ $(OS_NAME) = "darwin" ]; then (echo "Using Leaks on Darwin" && LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(TMPDIR) leaks --atExit -- $(TMPDIR)/polyglot_test); else (echo "Using Valgrind on Unix" && LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(TMPDIR) valgrind --trace-children=yes --leak-check=full --error-exitcode=1 $(TMPDIR)/polyglot_test); fi 71 | rm -rf $(TMPDIR) 72 | 73 | install: clib 74 | mkdir -p $(DESTDIR)$(LIBDIR)/$(CLIB_SO_FULL) 75 | install -p -m755 $(CLIB_SO_DEV_RELEASE) $(DESTDIR)$(LIBDIR)/$(CLIB_SO_FULL) 76 | ln -sfv $(CLIB_SO_FULL) $(DESTDIR)$(LIBDIR)/$(CLIB_SO_MAN) 77 | ln -sfv $(CLIB_SO_FULL) $(DESTDIR)$(LIBDIR)/$(CLIB_SO_DEV) 78 | mkdir -p $(DESTDIR)$(INCLUDE_DIR)/$(shell basename $(CLIB_HEADER)) 79 | install -p -v -m644 $(CLIB_HEADER) $(DESTDIR)$(INCLUDE_DIR)/$(shell basename $(CLIB_HEADER)) 80 | mkdir -p $(DESTDIR)$(PKG_CONFIG_LIBDIR)/$(shell basename $(CLIB_PKG_CONFIG)) 81 | install -p -v -m644 $(CLIB_PKG_CONFIG) $(DESTDIR)$(PKG_CONFIG_LIBDIR)/$(shell basename $(CLIB_PKG_CONFIG)) 82 | 83 | uninstall: 84 | - rm -rfv $(DESTDIR)$(LIBDIR)/$(CLIB_SO_DEV) 85 | - rm -rfv $(DESTDIR)$(LIBDIR)/$(CLIB_SO_MAN) 86 | - rm -rfv $(DESTDIR)$(LIBDIR)/$(CLIB_SO_FULL) 87 | - rm -rfv $(DESTDIR)$(INCLUDE_DIR)/$(shell basename $(CLIB_HEADER)) 88 | - rm -rfv $(DESTDIR)$(INCLUDE_DIR)/$(shell basename $(CLIB_PKG_CONFIG)) 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Logo 5 | 6 |

Polyglot

7 |

8 | A high-performance serialization framework used for encoding and decoding arbitrary datastructures across languages. 9 |

10 | 11 | [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-brightgreen.svg)](https://www.apache.org/licenses/LICENSE-2.0) 12 | [![Discord](https://dcbadge.vercel.app/api/server/JYmFhtdPeu?style=flat)](https://loopholelabs.io/discord) 13 | [![Tests](https://github.com/loopholelabs/polyglot/actions/workflows/tests.yaml/badge.svg)](https://github.com/loopholelabs/polyglot/actions/workflows/tests.yaml) 14 |
15 | 16 | ## Contributing 17 | 18 | Bug reports and pull requests are welcome on GitHub at [https://github.com/loopholelabs/polyglot][gitrepo]. For more 19 | contribution information check 20 | out [the contribution guide](https://github.com/loopholelabs/polyglot/blob/main/CONTRIBUTING.md). 21 | 22 | ## License 23 | 24 | The Polyglot project is available as open source under the terms of 25 | the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). 26 | 27 | ## Code of Conduct 28 | 29 | Everyone interacting in the Polyglot project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 30 | 31 | ## Project Managed By: 32 | 33 | [![https://loopholelabs.io][loopholelabs]](https://loopholelabs.io) 34 | 35 | [gitrepo]: https://github.com/loopholelabs/polyglot 36 | [loopholelabs]: https://cdn.loopholelabs.io/loopholelabs/LoopholeLabsLogo.svg 37 | [loophomepage]: https://loopholelabs.io 38 | -------------------------------------------------------------------------------- /buffer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | const ( 20 | defaultSize = 512 21 | ) 22 | 23 | type Buffer []byte 24 | 25 | func (buf *Buffer) Reset() { 26 | *buf = (*buf)[:0] 27 | } 28 | 29 | func (buf *Buffer) Write(b []byte) int { 30 | if cap(*buf)-len(*buf) < len(b) { 31 | *buf = append((*buf)[:len(*buf)], b...) 32 | } else { 33 | *buf = (*buf)[:len(*buf)+copy((*buf)[len(*buf):cap(*buf)], b)] 34 | } 35 | return len(b) 36 | } 37 | 38 | func NewBuffer() *Buffer { 39 | c := make(Buffer, 0, defaultSize) 40 | return &c 41 | } 42 | 43 | func (buf *Buffer) Bytes() []byte { 44 | return *buf 45 | } 46 | 47 | func (buf *Buffer) Len() int { 48 | return len(*buf) 49 | } 50 | -------------------------------------------------------------------------------- /c_bindings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "c_bindings" 3 | version = "2.0.2" 4 | edition = "2021" 5 | description="A high-performance serialization framework used for encoding and decoding arbitrary datastructures across languages." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/loopholelabs/polyglot" 8 | readme = "README.md" 9 | keywords = ["polyglot", "serialization", "deserialization", "encoding", "decoding"] 10 | 11 | [lib] 12 | path = "lib.rs" 13 | name = "polyglot" 14 | crate-type = ["cdylib"] 15 | 16 | [dependencies] 17 | polyglot_rs = { path = "../" } 18 | 19 | [profile.release] 20 | opt-level = 3 21 | lto = true 22 | codegen-units = 1 23 | -------------------------------------------------------------------------------- /c_bindings/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pub mod types; 18 | pub mod encoder; 19 | pub mod decoder; -------------------------------------------------------------------------------- /c_bindings/tests/polyglot_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | int main(void) { 28 | polyglot_status_t status = POLYGLOT_STATUS_PASS; 29 | 30 | polyglot_encoder_t* encoder = polyglot_new_encoder(&status); 31 | assert(status == POLYGLOT_STATUS_PASS); 32 | assert(encoder != NULL); 33 | 34 | uint32_t buffer_size = polyglot_encoder_size(&status, encoder); 35 | assert(status == POLYGLOT_STATUS_PASS); 36 | assert(buffer_size == 0); 37 | 38 | polyglot_encode_none(&status, encoder); 39 | assert(status == POLYGLOT_STATUS_PASS); 40 | 41 | buffer_size = polyglot_encoder_size(&status, encoder); 42 | assert(status == POLYGLOT_STATUS_PASS); 43 | assert(buffer_size == 1); 44 | 45 | char *input_string_pointer = "Hello, World!"; 46 | polyglot_encode_string(&status, encoder, input_string_pointer); 47 | assert(status == POLYGLOT_STATUS_PASS); 48 | 49 | buffer_size = polyglot_encoder_size(&status, encoder); 50 | assert(status == POLYGLOT_STATUS_PASS); 51 | assert(buffer_size == 1 + (1 + 15)); 52 | 53 | polyglot_encode_array(&status, encoder, 8, POLYGLOT_KIND_STRING); 54 | assert(status == POLYGLOT_STATUS_PASS); 55 | 56 | buffer_size = polyglot_encoder_size(&status, encoder); 57 | assert(status == POLYGLOT_STATUS_PASS); 58 | assert(buffer_size == 1 + (1 + 15) + (1 + 1 + 1 + 1)); 59 | 60 | uint8_t *input_buffer_pointer = malloc(32); 61 | uint8_t *current_input_buffer_pointer = input_buffer_pointer; 62 | for (uint8_t i = 0; i < 32; i++) { 63 | *current_input_buffer_pointer = i; 64 | current_input_buffer_pointer++; 65 | } 66 | 67 | polyglot_encode_bytes(&status, encoder, input_buffer_pointer, 32); 68 | assert(status == POLYGLOT_STATUS_PASS); 69 | 70 | buffer_size = polyglot_encoder_size(&status, encoder); 71 | assert(status == POLYGLOT_STATUS_PASS); 72 | assert(buffer_size == 1 + (1 + 15) + (1 + 1 + 1 + 1 ) + (1 + 1 + 1 + 32)); 73 | 74 | uint8_t *buffer_pointer = malloc(buffer_size); 75 | polyglot_encoder_buffer(&status, encoder, buffer_pointer, buffer_size); 76 | assert(status == POLYGLOT_STATUS_PASS); 77 | polyglot_free_encoder(encoder); 78 | 79 | // uint8_t *current_buffer_pointer = buffer_pointer; 80 | // printf("polyglot_encoder_buffer contents: "); 81 | // for(uint32_t i = 0; i < buffer_size; i++) { 82 | // printf("%d ", *current_buffer_pointer); 83 | // current_buffer_pointer++; 84 | // } 85 | // printf("\n"); 86 | 87 | polyglot_decoder_t *decoder = polyglot_new_decoder(&status, buffer_pointer, buffer_size); 88 | assert(status == POLYGLOT_STATUS_PASS); 89 | assert(decoder != NULL); 90 | 91 | bool decode_none_success = polyglot_decode_none(&status, decoder); 92 | assert(status == POLYGLOT_STATUS_PASS); 93 | assert(decode_none_success == true); 94 | 95 | char *output_string_pointer = polyglot_decode_string(&status, decoder); 96 | assert(status == POLYGLOT_STATUS_PASS); 97 | assert(output_string_pointer != NULL); 98 | assert(strcmp(input_string_pointer, output_string_pointer) == 0); 99 | polyglot_free_decode_string(output_string_pointer); 100 | 101 | uint32_t output_array_size = polyglot_decode_array(&status, decoder, POLYGLOT_KIND_STRING); 102 | assert(status == POLYGLOT_STATUS_PASS); 103 | assert(output_array_size == 8); 104 | 105 | polyglot_buffer_t *output_buffer_pointer = polyglot_decode_bytes(&status, decoder); 106 | assert(status == POLYGLOT_STATUS_PASS); 107 | assert(output_buffer_pointer != NULL); 108 | assert(output_buffer_pointer->length == 32); 109 | assert(memcmp(input_buffer_pointer, output_buffer_pointer->data, 32) == 0); 110 | free(input_buffer_pointer); 111 | polyglot_free_decode_bytes(output_buffer_pointer); 112 | 113 | polyglot_free_decoder(decoder); 114 | free(buffer_pointer); 115 | 116 | printf("polyglot_test.c: PASS\n"); 117 | } -------------------------------------------------------------------------------- /c_bindings/types.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use std::fmt::{Debug, Display, Formatter}; 18 | 19 | #[repr(u8)] 20 | #[derive(Debug)] 21 | pub enum Status { 22 | Pass, 23 | Fail, 24 | NullPointer, 25 | } 26 | 27 | impl Status { 28 | pub fn check_not_null(status: *mut Status) { 29 | if status.is_null() { 30 | panic!("status pointer is null"); 31 | } 32 | } 33 | } 34 | 35 | #[repr(u8)] 36 | #[derive(Debug)] 37 | pub enum Kind { 38 | None = 0x00, 39 | Array = 0x01, 40 | Map = 0x02, 41 | Any = 0x03, 42 | Bytes = 0x04, 43 | String = 0x05, 44 | Error = 0x06, 45 | Bool = 0x07, 46 | U8 = 0x08, 47 | U16 = 0x09, 48 | U32 = 0x0a, 49 | U64 = 0x0b, 50 | I32 = 0x0c, 51 | I64 = 0x0d, 52 | F32 = 0x0e, 53 | F64 = 0x0f, 54 | Unknown, 55 | } 56 | 57 | impl Kind { 58 | pub fn into(self) -> polyglot_rs::Kind { 59 | match self { 60 | Kind::None => polyglot_rs::Kind::None, 61 | Kind::Array => polyglot_rs::Kind::Array, 62 | Kind::Map => polyglot_rs::Kind::Map, 63 | Kind::Any => polyglot_rs::Kind::Any, 64 | Kind::Bytes => polyglot_rs::Kind::Bytes, 65 | Kind::String => polyglot_rs::Kind::String, 66 | Kind::Error => polyglot_rs::Kind::Error, 67 | Kind::Bool => polyglot_rs::Kind::Bool, 68 | Kind::U8 => polyglot_rs::Kind::U8, 69 | Kind::U16 => polyglot_rs::Kind::U16, 70 | Kind::U32 => polyglot_rs::Kind::U32, 71 | Kind::U64 => polyglot_rs::Kind::U64, 72 | Kind::I32 => polyglot_rs::Kind::I32, 73 | Kind::I64 => polyglot_rs::Kind::I64, 74 | Kind::F32 => polyglot_rs::Kind::F32, 75 | Kind::F64 => polyglot_rs::Kind::F64, 76 | Kind::Unknown => polyglot_rs::Kind::Unknown, 77 | } 78 | } 79 | } 80 | 81 | #[repr(C)] 82 | #[derive(Debug)] 83 | pub struct Buffer { 84 | pub(crate) data: *mut u8, 85 | pub(crate) length: u32, 86 | } 87 | 88 | impl Buffer { 89 | pub fn new_raw(data: *mut u8, length: u32) -> *mut Self { 90 | Box::into_raw(Box::new(Buffer { 91 | data, 92 | length, 93 | })) 94 | } 95 | } 96 | 97 | pub(crate) struct StringError(pub(crate) String); 98 | 99 | impl Debug for StringError { 100 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 101 | Debug::fmt(&self.0, f) 102 | } 103 | } 104 | 105 | impl Display for StringError { 106 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 107 | Display::fmt(&self.0, f) 108 | } 109 | } 110 | 111 | impl std::error::Error for StringError { 112 | fn description(&self) -> &str { 113 | &self.0 114 | } 115 | } -------------------------------------------------------------------------------- /decoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | import ( 20 | "sync" 21 | ) 22 | 23 | var decoderPool sync.Pool 24 | 25 | type Decoder struct { 26 | b []byte 27 | } 28 | 29 | func GetDecoder(b []byte) (d *Decoder) { 30 | v := decoderPool.Get() 31 | if v == nil { 32 | d = &Decoder{ 33 | b: b, 34 | } 35 | } else { 36 | d = v.(*Decoder) 37 | d.b = b 38 | } 39 | return 40 | } 41 | 42 | func ReturnDecoder(d *Decoder) { 43 | if d != nil { 44 | d.b = nil 45 | decoderPool.Put(d) 46 | } 47 | } 48 | 49 | func (d *Decoder) Return() { 50 | ReturnDecoder(d) 51 | } 52 | 53 | func (d *Decoder) Nil() (value bool) { 54 | d.b, value = decodeNil(d.b) 55 | return 56 | } 57 | 58 | func (d *Decoder) Map(keyKind, valueKind Kind) (size uint32, err error) { 59 | d.b, size, err = decodeMap(d.b, keyKind, valueKind) 60 | return 61 | } 62 | 63 | func (d *Decoder) Slice(kind Kind) (size uint32, err error) { 64 | d.b, size, err = decodeSlice(d.b, kind) 65 | return 66 | } 67 | 68 | func (d *Decoder) Bytes(b []byte) (value []byte, err error) { 69 | d.b, value, err = decodeBytes(d.b, b) 70 | return 71 | } 72 | 73 | func (d *Decoder) String() (value string, err error) { 74 | d.b, value, err = decodeString(d.b) 75 | return 76 | } 77 | 78 | func (d *Decoder) Error() (value, err error) { 79 | d.b, value, err = decodeError(d.b) 80 | return 81 | } 82 | 83 | func (d *Decoder) Bool() (value bool, err error) { 84 | d.b, value, err = decodeBool(d.b) 85 | return 86 | } 87 | 88 | func (d *Decoder) Uint8() (value uint8, err error) { 89 | d.b, value, err = decodeUint8(d.b) 90 | return 91 | } 92 | 93 | func (d *Decoder) Uint16() (value uint16, err error) { 94 | d.b, value, err = decodeUint16(d.b) 95 | return 96 | } 97 | 98 | func (d *Decoder) Uint32() (value uint32, err error) { 99 | d.b, value, err = decodeUint32(d.b) 100 | return 101 | } 102 | 103 | func (d *Decoder) Uint64() (value uint64, err error) { 104 | d.b, value, err = decodeUint64(d.b) 105 | return 106 | } 107 | 108 | func (d *Decoder) Int32() (value int32, err error) { 109 | d.b, value, err = decodeInt32(d.b) 110 | return 111 | } 112 | 113 | func (d *Decoder) Int64() (value int64, err error) { 114 | d.b, value, err = decodeInt64(d.b) 115 | return 116 | } 117 | 118 | func (d *Decoder) Float32() (value float32, err error) { 119 | d.b, value, err = decodeFloat32(d.b) 120 | return 121 | } 122 | 123 | func (d *Decoder) Float64() (value float64, err error) { 124 | d.b, value, err = decodeFloat64(d.b) 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | import ( 20 | "math" 21 | "reflect" 22 | "unsafe" 23 | ) 24 | 25 | type Kind []byte 26 | 27 | var ( 28 | NilKind = Kind([]byte{0}) 29 | SliceKind = Kind([]byte{1}) 30 | MapKind = Kind([]byte{2}) 31 | AnyKind = Kind([]byte{3}) 32 | BytesKind = Kind([]byte{4}) 33 | StringKind = Kind([]byte{5}) 34 | ErrorKind = Kind([]byte{6}) 35 | BoolKind = Kind([]byte{7}) 36 | Uint8Kind = Kind([]byte{8}) 37 | Uint16Kind = Kind([]byte{9}) 38 | Uint32Kind = Kind([]byte{10}) 39 | Uint64Kind = Kind([]byte{11}) 40 | Int32Kind = Kind([]byte{12}) 41 | Int64Kind = Kind([]byte{13}) 42 | Float32Kind = Kind([]byte{14}) 43 | Float64Kind = Kind([]byte{15}) 44 | ) 45 | 46 | type Error string 47 | 48 | func (e Error) Error() string { 49 | return string(e) 50 | } 51 | 52 | func (e Error) Is(err error) bool { 53 | return e.Error() == err.Error() 54 | } 55 | 56 | var ( 57 | falseBool = byte(0) 58 | trueBool = byte(1) 59 | ) 60 | 61 | func encodeNil(b *Buffer) { 62 | b.Write(NilKind) 63 | } 64 | 65 | func encodeMap(b *Buffer, size uint32, keyKind, valueKind Kind) { 66 | b.Write(MapKind) 67 | b.Write(keyKind) 68 | b.Write(valueKind) 69 | encodeUint32(b, size) 70 | } 71 | 72 | func encodeSlice(b *Buffer, size uint32, kind Kind) { 73 | b.Write(SliceKind) 74 | b.Write(kind) 75 | encodeUint32(b, size) 76 | } 77 | 78 | func encodeBytes(b *Buffer, value []byte) { 79 | b.Write(BytesKind) 80 | encodeUint32(b, uint32(len(value))) 81 | b.Write(value) 82 | } 83 | 84 | func encodeString(b *Buffer, value string) { 85 | var nb []byte 86 | /* #nosec G103 */ 87 | bh := (*reflect.SliceHeader)(unsafe.Pointer(&nb)) 88 | /* #nosec G103 */ 89 | sh := (*reflect.StringHeader)(unsafe.Pointer(&value)) 90 | bh.Data = sh.Data 91 | bh.Cap = sh.Len 92 | bh.Len = sh.Len 93 | b.Write(StringKind) 94 | encodeUint32(b, uint32(len(nb))) 95 | b.Write(nb) 96 | } 97 | 98 | func encodeError(b *Buffer, err error) { 99 | b.Write(ErrorKind) 100 | encodeString(b, err.Error()) 101 | } 102 | 103 | func encodeBool(b *Buffer, value bool) { 104 | b.Write(BoolKind) 105 | if value { 106 | *b = append(*b, trueBool) 107 | } else { 108 | *b = append(*b, falseBool) 109 | } 110 | } 111 | 112 | func encodeUint8(b *Buffer, value uint8) { 113 | b.Write(Uint8Kind) 114 | *b = append(*b, value) 115 | } 116 | 117 | // Variable integer encoding with the same format as binary.varint 118 | // (https://developers.google.com/protocol-buffers/docs/encoding#varints) 119 | func encodeUint16(b *Buffer, value uint16) { 120 | b.Write(Uint16Kind) 121 | for value >= continuation { 122 | // Append the lower 7 bits of the value, then shift the value to the right by 7 bits. 123 | *b = append(*b, byte(value)|continuation) 124 | value >>= 7 125 | } 126 | *b = append(*b, byte(value)) 127 | } 128 | 129 | func encodeUint32(b *Buffer, value uint32) { 130 | b.Write(Uint32Kind) 131 | for value >= continuation { 132 | // Append the lower 7 bits of the value, then shift the value to the right by 7 bits. 133 | *b = append(*b, byte(value)|continuation) 134 | value >>= 7 135 | } 136 | *b = append(*b, byte(value)) 137 | } 138 | 139 | func encodeUint64(b *Buffer, value uint64) { 140 | b.Write(Uint64Kind) 141 | for value >= continuation { 142 | // Append the lower 7 bits of the value, then shift the value to the right by 7 bits. 143 | *b = append(*b, byte(value)|continuation) 144 | value >>= 7 145 | } 146 | *b = append(*b, byte(value)) 147 | } 148 | 149 | func encodeInt32(b *Buffer, value int32) { 150 | b.Write(Int32Kind) 151 | // Shift the value to the left by 1 bit, then flip the bits if the value is negative. 152 | castValue := uint32(value) << 1 153 | if value < 0 { 154 | castValue = ^castValue 155 | } 156 | for castValue >= continuation { 157 | // Append the lower 7 bits of the value, then shift the value to the right by 7 bits. 158 | *b = append(*b, byte(castValue)|continuation) 159 | castValue >>= 7 160 | } 161 | *b = append(*b, byte(castValue)) 162 | } 163 | 164 | func encodeInt64(b *Buffer, value int64) { 165 | b.Write(Int64Kind) 166 | // Shift the value to the left by 1 bit, then flip the bits if the value is negative. 167 | castValue := uint64(value) << 1 168 | if value < 0 { 169 | castValue = ^castValue 170 | } 171 | for castValue >= continuation { 172 | // Append the lower 7 bits of the value, then shift the value to the right by 7 bits. 173 | *b = append(*b, byte(castValue)|continuation) 174 | castValue >>= 7 175 | } 176 | *b = append(*b, byte(castValue)) 177 | } 178 | 179 | func encodeFloat32(b *Buffer, value float32) { 180 | b.Write(Float32Kind) 181 | castValue := math.Float32bits(value) 182 | *b = append(*b, byte(castValue>>24), byte(castValue>>16), byte(castValue>>8), byte(castValue)) 183 | } 184 | 185 | func encodeFloat64(b *Buffer, value float64) { 186 | b.Write(Float64Kind) 187 | castValue := math.Float64bits(value) 188 | *b = append(*b, byte(castValue>>56), byte(castValue>>48), byte(castValue>>40), byte(castValue>>32), byte(castValue>>24), byte(castValue>>16), byte(castValue>>8), byte(castValue)) 189 | } 190 | -------------------------------------------------------------------------------- /encoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | type BufferEncoder Buffer 20 | 21 | func Encoder(b *Buffer) *BufferEncoder { 22 | return (*BufferEncoder)(b) 23 | } 24 | 25 | func (e *BufferEncoder) Nil() *BufferEncoder { 26 | encodeNil((*Buffer)(e)) 27 | return e 28 | } 29 | 30 | func (e *BufferEncoder) Map(size uint32, keyKind, valueKind Kind) *BufferEncoder { 31 | encodeMap((*Buffer)(e), size, keyKind, valueKind) 32 | return e 33 | } 34 | 35 | func (e *BufferEncoder) Slice(size uint32, kind Kind) *BufferEncoder { 36 | encodeSlice((*Buffer)(e), size, kind) 37 | return e 38 | } 39 | 40 | func (e *BufferEncoder) Bytes(value []byte) *BufferEncoder { 41 | encodeBytes((*Buffer)(e), value) 42 | return e 43 | } 44 | 45 | func (e *BufferEncoder) String(value string) *BufferEncoder { 46 | encodeString((*Buffer)(e), value) 47 | return e 48 | } 49 | 50 | func (e *BufferEncoder) Error(value error) *BufferEncoder { 51 | encodeError((*Buffer)(e), value) 52 | return e 53 | } 54 | 55 | func (e *BufferEncoder) Bool(value bool) *BufferEncoder { 56 | encodeBool((*Buffer)(e), value) 57 | return e 58 | } 59 | 60 | func (e *BufferEncoder) Uint8(value uint8) *BufferEncoder { 61 | encodeUint8((*Buffer)(e), value) 62 | return e 63 | } 64 | 65 | func (e *BufferEncoder) Uint16(value uint16) *BufferEncoder { 66 | encodeUint16((*Buffer)(e), value) 67 | return e 68 | } 69 | 70 | func (e *BufferEncoder) Uint32(value uint32) *BufferEncoder { 71 | encodeUint32((*Buffer)(e), value) 72 | return e 73 | } 74 | 75 | func (e *BufferEncoder) Uint64(value uint64) *BufferEncoder { 76 | encodeUint64((*Buffer)(e), value) 77 | return e 78 | } 79 | 80 | func (e *BufferEncoder) Int32(value int32) *BufferEncoder { 81 | encodeInt32((*Buffer)(e), value) 82 | return e 83 | } 84 | 85 | func (e *BufferEncoder) Int64(value int64) *BufferEncoder { 86 | encodeInt64((*Buffer)(e), value) 87 | return e 88 | } 89 | 90 | func (e *BufferEncoder) Float32(value float32) *BufferEncoder { 91 | encodeFloat32((*Buffer)(e), value) 92 | return e 93 | } 94 | 95 | func (e *BufferEncoder) Float64(value float64) *BufferEncoder { 96 | encodeFloat64((*Buffer)(e), value) 97 | return e 98 | } 99 | -------------------------------------------------------------------------------- /generator/golang/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | const ( 20 | Extension = ".polyglot.go" 21 | Pointer = "*" 22 | MapSuffix = "Map" 23 | Slice = "[]" 24 | PolyglotAnyKind = "polyglot.AnyKind" 25 | ) 26 | -------------------------------------------------------------------------------- /generator/golang/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/generator/golang/templates" 21 | "github.com/loopholelabs/polyglot/utils" 22 | "github.com/loopholelabs/polyglot/version" 23 | 24 | "google.golang.org/protobuf/compiler/protogen" 25 | "google.golang.org/protobuf/proto" 26 | "google.golang.org/protobuf/types/pluginpb" 27 | 28 | "text/template" 29 | ) 30 | 31 | type Generator struct { 32 | options *protogen.Options 33 | templ *template.Template 34 | CustomFields func() string 35 | CustomEncode func() string 36 | CustomDecode func() string 37 | } 38 | 39 | func New() *Generator { 40 | var g *Generator 41 | templ := template.Must(template.New("main").Funcs(template.FuncMap{ 42 | "CamelCase": utils.CamelCaseFullName, 43 | "CamelCaseName": utils.CamelCaseName, 44 | "MakeIterable": utils.MakeIterable, 45 | "Counter": utils.Counter, 46 | "FirstLowerCase": utils.FirstLowerCase, 47 | "FirstLowerCaseName": utils.FirstLowerCaseName, 48 | "FindValue": FindValue, 49 | "GetKind": GetKind, 50 | "GetLUTEncoder": GetLUTEncoder, 51 | "GetLUTDecoder": GetLUTDecoder, 52 | "GetEncodingFields": GetEncodingFields, 53 | "GetDecodingFields": GetDecodingFields, 54 | "GetKindLUT": GetKindLUT, 55 | "CustomFields": func() string { 56 | return g.CustomFields() 57 | }, 58 | "CustomEncode": func() string { 59 | return g.CustomEncode() 60 | }, 61 | "CustomDecode": func() string { 62 | return g.CustomDecode() 63 | }, 64 | }).ParseFS(templates.FS, "*")) 65 | g = &Generator{ 66 | options: &protogen.Options{ 67 | ParamFunc: func(name string, value string) error { return nil }, 68 | ImportRewriteFunc: func(path protogen.GoImportPath) protogen.GoImportPath { return path }, 69 | }, 70 | templ: templ, 71 | CustomEncode: func() string { return "" }, 72 | CustomDecode: func() string { return "" }, 73 | CustomFields: func() string { return "" }, 74 | } 75 | return g 76 | } 77 | 78 | func (*Generator) UnmarshalRequest(buf []byte) (*pluginpb.CodeGeneratorRequest, error) { 79 | req := new(pluginpb.CodeGeneratorRequest) 80 | return req, proto.Unmarshal(buf, req) 81 | } 82 | 83 | func (*Generator) MarshalResponse(res *pluginpb.CodeGeneratorResponse) ([]byte, error) { 84 | return proto.Marshal(res) 85 | } 86 | 87 | func (g *Generator) Generate(req *pluginpb.CodeGeneratorRequest) (res *pluginpb.CodeGeneratorResponse, err error) { 88 | plugin, err := g.options.New(req) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | for _, f := range plugin.Files { 94 | if !f.Generate { 95 | continue 96 | } 97 | genFile := plugin.NewGeneratedFile(FileName(f.GeneratedFilenamePrefix), f.GoImportPath) 98 | 99 | packageName := string(f.Desc.Package().Name()) 100 | if packageName == "" { 101 | packageName = string(f.GoPackageName) 102 | } 103 | 104 | err = g.ExecuteTemplate(genFile, f, packageName, true) 105 | if err != nil { 106 | return nil, err 107 | } 108 | } 109 | 110 | return plugin.Response(), nil 111 | } 112 | 113 | func (g *Generator) ExecuteTemplate( 114 | genFile *protogen.GeneratedFile, 115 | protoFile *protogen.File, 116 | packageName string, 117 | header bool, 118 | ) error { 119 | return g.templ.ExecuteTemplate(genFile, "base.templ", map[string]interface{}{ 120 | "pluginVersion": version.Version(), 121 | "sourcePath": protoFile.Desc.Path(), 122 | "package": packageName, 123 | "requiredImports": RequiredImports, 124 | "enums": protoFile.Desc.Enums(), 125 | "messages": protoFile.Desc.Messages(), 126 | "header": header, 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /generator/golang/headers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/utils" 21 | ) 22 | 23 | func FileName(name string) string { 24 | return utils.AppendString(name, Extension) 25 | } 26 | -------------------------------------------------------------------------------- /generator/golang/imports.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | var ( 20 | RequiredImports = []string{ 21 | "github.com/loopholelabs/polyglot", 22 | "errors", 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /generator/golang/templates/base.templ: -------------------------------------------------------------------------------- 1 | {{ if .header -}} 2 | {{template "headers" .}} 3 | 4 | {{template "imports" .}} 5 | {{ end -}} 6 | 7 | {{template "errors" .}} 8 | 9 | {{template "enums" .}} 10 | 11 | {{template "messages" .}} 12 | -------------------------------------------------------------------------------- /generator/golang/templates/decode.templ: -------------------------------------------------------------------------------- 1 | {{define "decode"}} 2 | func (x *{{ CamelCase .FullName }}) Decode (b []byte) error { 3 | if x == nil { 4 | return NilDecode 5 | } 6 | d := polyglot.GetDecoder(b) 7 | defer d.Return() 8 | return x.decode(d) 9 | } 10 | {{end}} 11 | 12 | {{define "internalDecode"}} 13 | func (x *{{CamelCase .FullName}}) decode(d *polyglot.Decoder) error { 14 | if d.Nil() { 15 | return nil 16 | } 17 | 18 | {{ $decoding := GetDecodingFields .Fields -}} 19 | {{ $customDecode := CustomDecode -}} 20 | {{ if or $customDecode $decoding.Other $decoding.SliceFields $decoding.MessageFields -}} 21 | var err error 22 | {{ end -}} 23 | {{ $customDecode }} 24 | {{ range $field := $decoding.Other -}} 25 | {{ $decoder := GetLUTDecoder $field.Kind -}} 26 | {{ if eq $field.Kind 12 -}} {{/* protoreflect.BytesKind */ -}} 27 | x.{{ CamelCaseName $field.Name }}, err = d{{ $decoder }}(nil) 28 | {{ else if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 29 | var {{ CamelCaseName $field.Name }}Temp uint32 30 | {{ CamelCaseName $field.Name }}Temp, err = d{{ $decoder }}() 31 | x.{{ CamelCaseName $field.Name }} = {{ FindValue $field }}({{ CamelCaseName $field.Name }}Temp) 32 | {{ else -}} 33 | x.{{ CamelCaseName $field.Name }}, err = d{{ $decoder }}() 34 | {{end -}} 35 | if err != nil { 36 | return err 37 | } 38 | {{end -}} 39 | 40 | {{ if $decoding.SliceFields -}} 41 | var sliceSize uint32 42 | {{end -}} 43 | {{ range $field := $decoding.SliceFields -}} 44 | {{ $kind := GetKind $field.Kind -}} 45 | sliceSize, err = d.Slice({{ $kind }}) 46 | if err != nil { 47 | return err 48 | } 49 | if uint32(len(x.{{ CamelCaseName $field.Name }})) != sliceSize { 50 | x.{{ CamelCaseName $field.Name }} = make({{ FindValue $field }}, sliceSize) 51 | } 52 | for i := uint32(0); i < sliceSize; i++ { 53 | {{ $decoder := GetLUTDecoder $field.Kind -}} 54 | {{ if eq $field.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 55 | if x.{{ CamelCaseName $field.Name }}[i] == nil { 56 | x.{{ CamelCaseName $field.Name }}[i] = New{{ CamelCase $field.Message.FullName }}() 57 | } 58 | err = x.{{ CamelCaseName $field.Name }}[i].decode(d) 59 | {{ else -}} 60 | x.{{ CamelCaseName $field.Name }}[i], err = d{{ $decoder }}() 61 | {{end -}} 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | {{end -}} 67 | {{ range $field := $decoding.MessageFields -}} 68 | {{ if $field.IsMap -}} 69 | if !d.Nil() { 70 | {{ $keyKind := GetKind $field.MapKey.Kind -}} 71 | {{ $valKind := GetKind $field.MapValue.Kind -}} 72 | 73 | {{ CamelCaseName $field.Name }}Size, err := d.Map({{ $keyKind }}, {{ $valKind }}) 74 | if err != nil { 75 | return err 76 | } 77 | x.{{ CamelCaseName $field.Name }} = New{{ CamelCase $field.FullName }}Map({{ CamelCaseName $field.Name }}Size) 78 | err = x.{{ CamelCaseName $field.Name }}.decode(d, {{ CamelCaseName $field.Name }}Size) 79 | if err != nil { 80 | return err 81 | } 82 | } 83 | {{ else -}} 84 | if x.{{ CamelCaseName $field.Name }} == nil { 85 | x.{{ CamelCaseName $field.Name }} = New{{ CamelCase $field.Message.FullName }}() 86 | } 87 | err = x.{{ CamelCaseName $field.Name }}.decode(d) 88 | if err != nil { 89 | return err 90 | } 91 | {{end -}} 92 | {{end -}} 93 | return nil 94 | } 95 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/decodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "decodeMap"}} 2 | func (x {{CamelCase .FullName}}Map) decode(d *polyglot.Decoder, size uint32) error { 3 | if size == 0 { 4 | return nil 5 | } 6 | var k {{ FindValue .MapKey }} 7 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 8 | var {{ CamelCase .MapKey.Name }}Temp uint32 9 | {{end -}} 10 | var v {{ FindValue .MapValue }} 11 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 12 | var {{ CamelCaseName .MapValue.Name }}Temp uint32 13 | {{end -}} 14 | var err error 15 | for i := uint32(0); i < size; i++ { 16 | {{ $keyDecoder := GetLUTDecoder .MapKey.Kind -}} 17 | {{ if and (eq $keyDecoder "") (eq .MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 18 | k = New{{ CamelCase .MapKey.Message.FullName }}() 19 | err = k.decode(d) 20 | {{else -}} 21 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 22 | {{ CamelCase .MapKey.Name }}Temp, err = d{{$keyDecoder}}() 23 | k = {{ FindValue .MapKey }}({{ CamelCase .MapKey.Name }}Temp) 24 | {{else -}} 25 | k, err = d{{$keyDecoder}}() 26 | {{end -}} 27 | {{end -}} 28 | if err != nil { 29 | return err 30 | } 31 | {{ $valDecoder := GetLUTDecoder .MapValue.Kind -}} 32 | {{ if and (eq $valDecoder "") (eq .MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 33 | v = New{{ CamelCase .MapValue.Message.FullName }}() 34 | err = v.decode(d) 35 | {{else -}} 36 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 37 | {{CamelCaseName .MapValue.Name}}Temp, err = d{{$valDecoder}}() 38 | v = {{ FindValue .MapValue }}({{ CamelCaseName .MapValue.Name }}Temp) 39 | {{else -}} 40 | v, err = d{{$valDecoder}}() 41 | {{end -}} 42 | {{end -}} 43 | 44 | if err != nil { 45 | return err 46 | } 47 | x[k] = v 48 | } 49 | return nil 50 | } 51 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/encode.templ: -------------------------------------------------------------------------------- 1 | {{define "encode"}} 2 | func (x *{{ CamelCase .FullName }}) Encode (b *polyglot.Buffer) { 3 | if x == nil { 4 | polyglot.Encoder(b).Nil() 5 | } else { 6 | {{ CustomEncode }} 7 | {{ $encoding := GetEncodingFields .Fields -}} 8 | {{ if $encoding.Values -}} 9 | polyglot.Encoder(b){{ range $val := $encoding.Values -}}{{ $val -}}{{end -}} 10 | {{ end -}} 11 | {{ if $encoding.SliceFields -}} 12 | {{template "encodeSlices" $encoding -}} 13 | {{end -}} 14 | {{ if $encoding.MessageFields -}} 15 | {{template "encodeMessages" $encoding -}} 16 | {{end -}} 17 | } 18 | } 19 | {{end}} 20 | 21 | {{define "encodeSlices"}} 22 | {{ range $field := .SliceFields -}} 23 | {{ $encoder := GetLUTEncoder $field.Kind -}} 24 | {{ if and (eq $encoder "") (eq $field.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 25 | polyglot.Encoder(b).Slice(uint32(len(x.{{ CamelCaseName $field.Name }})), polyglot.AnyKind) 26 | for _, v := range x.{{CamelCaseName $field.Name}} { 27 | v.Encode(b) 28 | } 29 | {{else -}} 30 | polyglot.Encoder(b).Slice(uint32(len(x.{{ CamelCaseName $field.Name }})), {{ GetKindLUT $field.Kind }}) 31 | for _, v := range x.{{ CamelCaseName $field.Name }} { 32 | polyglot.Encoder(b){{$encoder}}(v) 33 | } 34 | {{end -}} 35 | {{end -}} 36 | {{end}} 37 | 38 | {{define "encodeMessages"}} 39 | {{ range $field := .MessageFields -}} 40 | x.{{ CamelCaseName $field.Name }}.Encode(b) 41 | {{end -}} 42 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/encodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "encodeMap"}} 2 | func (x {{ CamelCase .FullName }}Map) Encode (b *polyglot.Buffer) { 3 | {{ $keyKind := GetKind .MapKey.Kind -}} 4 | {{ $valKind := GetKind .MapValue.Kind -}} 5 | 6 | if x == nil { 7 | polyglot.Encoder(b).Map(0, {{$keyKind}}, {{$valKind}}) 8 | } else { 9 | polyglot.Encoder(b).Map(uint32(len(x)), {{$keyKind}}, {{$valKind}}) 10 | for k, v := range x { 11 | {{ $keyEncoder := GetLUTEncoder .MapKey.Kind -}} 12 | {{ if and (eq $keyEncoder "") (eq .MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 13 | k.Encode(b) 14 | {{else -}} 15 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 16 | polyglot.Encoder(b) {{$keyEncoder}} (uint32(k)) 17 | {{else -}} 18 | polyglot.Encoder(b) {{$keyEncoder}} (k) 19 | {{end -}} 20 | {{end -}} 21 | {{ $valEncoder := GetLUTEncoder .MapValue.Kind -}} 22 | {{ if and (eq $valEncoder "") (eq .MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 23 | v.Encode(b) 24 | {{else -}} 25 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 26 | polyglot.Encoder(b) {{$valEncoder}} (uint32(v)) 27 | {{else -}} 28 | polyglot.Encoder(b) {{$valEncoder}} (v) 29 | {{end -}} 30 | {{end -}} 31 | } 32 | } 33 | } 34 | {{end}} 35 | -------------------------------------------------------------------------------- /generator/golang/templates/enums.templ: -------------------------------------------------------------------------------- 1 | {{define "enums"}} 2 | {{range $i, $e := (MakeIterable .enums.Len) -}} 3 | {{ $enum := ($.enums.Get $i) }} 4 | {{template "enum" $enum}} 5 | {{end -}} 6 | {{end}} 7 | 8 | {{define "enum"}} 9 | {{ $enumName := (CamelCase $.FullName) }} 10 | type {{ $enumName }} uint32 11 | 12 | const ( 13 | {{range $i, $v := (MakeIterable $.Values.Len) -}} 14 | {{ $val := ($.Values.Get $i) -}} 15 | {{CamelCase $val.FullName}} = {{ $enumName }}({{ $i }}) 16 | {{end -}} 17 | ) 18 | {{end}} 19 | 20 | -------------------------------------------------------------------------------- /generator/golang/templates/errors.templ: -------------------------------------------------------------------------------- 1 | {{define "errors"}} 2 | var ( 3 | NilDecode = errors.New("cannot decode into a nil root struct") 4 | ) 5 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/headers.templ: -------------------------------------------------------------------------------- 1 | {{define "headers"}} 2 | // Code generated by polyglot {{ .pluginVersion }}, DO NOT EDIT. 3 | // source: {{ .sourcePath }} 4 | 5 | package {{ .package }} 6 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/imports.templ: -------------------------------------------------------------------------------- 1 | {{define "imports"}} 2 | import ( 3 | {{range $im := .requiredImports -}} 4 | "{{$im}}" 5 | {{end -}} 6 | ) 7 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/messages.templ: -------------------------------------------------------------------------------- 1 | {{define "messages"}} 2 | {{range $i, $e := (MakeIterable .messages.Len) -}} 3 | {{ $message := $.messages.Get $i }} 4 | {{range $i, $e := (MakeIterable $message.Enums.Len) -}} 5 | {{ $enum := ($message.Enums.Get $i) }} 6 | {{template "enum" $enum}} 7 | {{end}} 8 | {{template "structs" $message}} 9 | {{end}} 10 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/structs.templ: -------------------------------------------------------------------------------- 1 | {{define "structs"}} 2 | {{ range $i, $v := (MakeIterable $.Messages.Len) }} 3 | {{ $message := $.Messages.Get $i }} 4 | {{ if not $message.IsMapEntry }} 5 | {{template "structs" $message}} 6 | {{end}} 7 | {{end}} 8 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 9 | {{ $field := $.Fields.Get $i }} 10 | {{ if $field.IsMap }} 11 | {{ $mapKeyValue := FindValue $field.MapKey }} 12 | {{ $mapValueValue := FindValue $field.MapValue }} 13 | type {{ CamelCase $field.FullName }}Map map[{{ $mapKeyValue }}]{{ $mapValueValue }} 14 | func New{{ CamelCase $field.FullName }}Map (size uint32) map[{{ $mapKeyValue }}]{{$mapValueValue}} { 15 | return make(map[{{ $mapKeyValue }}]{{ $mapValueValue }}, size) 16 | } 17 | 18 | {{template "encodeMap" $field}} 19 | {{template "decodeMap" $field}} 20 | {{end}} 21 | {{end -}} 22 | type {{ CamelCase .FullName }} struct { 23 | {{ CustomFields }} 24 | 25 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 26 | {{ $field := $.Fields.Get $i -}} 27 | {{ $value := FindValue $field -}} 28 | {{ CamelCaseName $field.Name }} {{ $value }} 29 | {{end -}} 30 | } 31 | 32 | {{template "getFunc" .}} 33 | {{template "error" .}} 34 | {{template "encode" .}} 35 | {{template "decode" .}} 36 | {{template "internalDecode" .}} 37 | {{end}} 38 | 39 | {{define "getFunc"}} 40 | func New{{ CamelCase .FullName }}() *{{ CamelCase .FullName }} { 41 | return &{{ CamelCase .FullName }}{ 42 | {{ range $i, $v := (MakeIterable .Fields.Len) -}} 43 | {{ $field := $.Fields.Get $i -}} 44 | {{ if and (eq $field.Kind 11) (ne $field.Cardinality 3) -}} {{/* protoreflect.MessageKind protoreflect.Repeated */ -}} 45 | {{ CamelCaseName $field.Name }}: New{{ CamelCase $field.Message.FullName }}(), 46 | {{end -}} 47 | {{end -}} 48 | } 49 | } 50 | {{end}} 51 | 52 | {{define "error"}} 53 | func (x *{{CamelCase .FullName}}) Error(b *polyglot.Buffer, err error) { 54 | polyglot.Encoder(b).Error(err) 55 | } 56 | {{end}} -------------------------------------------------------------------------------- /generator/golang/templates/templates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | import ( 20 | "embed" 21 | ) 22 | 23 | //go:embed * 24 | var FS embed.FS 25 | -------------------------------------------------------------------------------- /generator/rust/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | const ( 20 | extension = "_polyglot.rs" 21 | polyglotAnyKind = "Kind::Any" 22 | ) 23 | -------------------------------------------------------------------------------- /generator/rust/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | type File interface { 20 | P(v ...interface{}) 21 | } 22 | -------------------------------------------------------------------------------- /generator/rust/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/generator/rust/templates" 21 | "github.com/loopholelabs/polyglot/utils" 22 | "github.com/loopholelabs/polyglot/version" 23 | 24 | "google.golang.org/protobuf/compiler/protogen" 25 | "google.golang.org/protobuf/proto" 26 | "google.golang.org/protobuf/types/pluginpb" 27 | 28 | "bytes" 29 | "flag" 30 | "os/exec" 31 | "text/template" 32 | ) 33 | 34 | type GeneratedFieldPrivacy string 35 | 36 | const ( 37 | GeneratedFieldPrivacyPrivate = "private" 38 | GeneratedFieldPrivacyPublic = "public" 39 | GeneratedFieldPrivacyCrate = "crate" 40 | ) 41 | 42 | type Generator struct { 43 | options *protogen.Options 44 | templ *template.Template 45 | CustomFields func() string 46 | CustomEncode func() string 47 | CustomDecode func() string 48 | } 49 | 50 | func New() *Generator { 51 | var g *Generator 52 | 53 | var flags flag.FlagSet 54 | privacy := flags.String("privacy", GeneratedFieldPrivacyPrivate, "Privacy of generated fields (private, public, crate)") 55 | 56 | templ := template.Must(template.New("main").Funcs(template.FuncMap{ 57 | "CamelCase": utils.CamelCaseFullName, 58 | "CamelCaseName": utils.CamelCaseName, 59 | "MakeIterable": utils.MakeIterable, 60 | "Counter": utils.Counter, 61 | "FirstLowerCase": utils.FirstLowerCase, 62 | "FirstLowerCaseName": utils.FirstLowerCaseName, 63 | "FindValue": findValue, 64 | "GetKind": getKind, 65 | "GetLUTEncoder": getLUTEncoder, 66 | "GetLUTDecoder": getLUTDecoder, 67 | "GetEncodingFields": getEncodingFields, 68 | "GetDecodingFields": getDecodingFields, 69 | "GetKindLUT": getKindLUT, 70 | "SnakeCase": utils.SnakeCase, 71 | "SnakeCaseName": utils.SnakeCaseName, 72 | "CustomFields": func() string { 73 | return g.CustomFields() 74 | }, 75 | "CustomEncode": func() string { 76 | return g.CustomEncode() 77 | }, 78 | "CustomDecode": func() string { 79 | return g.CustomDecode() 80 | }, 81 | "GeneratedFieldPrivacy": func() GeneratedFieldPrivacy { 82 | return GeneratedFieldPrivacy(*privacy) 83 | }, 84 | }).ParseFS(templates.FS, "*")) 85 | 86 | g = &Generator{ 87 | options: &protogen.Options{ 88 | ParamFunc: flags.Set, 89 | ImportRewriteFunc: func(path protogen.GoImportPath) protogen.GoImportPath { return path }, 90 | }, 91 | templ: templ, 92 | CustomEncode: func() string { return "" }, 93 | CustomDecode: func() string { return "" }, 94 | CustomFields: func() string { return "" }, 95 | } 96 | return g 97 | } 98 | 99 | func (*Generator) UnmarshalRequest(buf []byte) (*pluginpb.CodeGeneratorRequest, error) { 100 | req := new(pluginpb.CodeGeneratorRequest) 101 | return req, proto.Unmarshal(buf, req) 102 | } 103 | 104 | func (*Generator) MarshalResponse(res *pluginpb.CodeGeneratorResponse) ([]byte, error) { 105 | return proto.Marshal(res) 106 | } 107 | 108 | func (g *Generator) Generate(req *pluginpb.CodeGeneratorRequest) (res *pluginpb.CodeGeneratorResponse, err error) { 109 | plugin, err := g.options.New(req) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | for _, f := range plugin.Files { 115 | if !f.Generate { 116 | continue 117 | } 118 | genFile := plugin.NewGeneratedFile(FileName(f.GeneratedFilenamePrefix), f.GoImportPath) 119 | 120 | packageName := string(f.Desc.Package().Name()) 121 | if packageName == "" { 122 | packageName = string(f.GoPackageName) 123 | } 124 | 125 | err = g.ExecuteTemplate(genFile, f, packageName, true) 126 | if err != nil { 127 | return nil, err 128 | } 129 | } 130 | 131 | return plugin.Response(), nil 132 | } 133 | 134 | func (g *Generator) ExecuteTemplate( 135 | genFile *protogen.GeneratedFile, 136 | protoFile *protogen.File, 137 | packageName string, 138 | header bool, 139 | ) error { 140 | var buf bytes.Buffer 141 | deps := DependencyAnalysis(protoFile) 142 | 143 | err := g.templ.ExecuteTemplate(&buf, "base.templ", map[string]interface{}{ 144 | "pluginVersion": version.Version(), 145 | "sourcePath": protoFile.Desc.Path(), 146 | "package": packageName, 147 | "enums": protoFile.Desc.Enums(), 148 | "messages": protoFile.Desc.Messages(), 149 | "header": header, 150 | "dependencies": deps, 151 | }) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | cmd := exec.Command("rustfmt") 157 | cmd.Stdin = bytes.NewReader(buf.Bytes()) 158 | output, err := cmd.CombinedOutput() 159 | if err != nil { 160 | println(string(output)) 161 | return err 162 | } 163 | _, err = genFile.Write(output) 164 | return err 165 | } 166 | -------------------------------------------------------------------------------- /generator/rust/headers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/utils" 21 | ) 22 | 23 | func FileName(name string) string { 24 | return utils.AppendString(name, extension) 25 | } 26 | -------------------------------------------------------------------------------- /generator/rust/imports.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | import ( 20 | "google.golang.org/protobuf/compiler/protogen" 21 | "google.golang.org/protobuf/reflect/protoreflect" 22 | ) 23 | 24 | type Dependencies struct { 25 | Enums bool 26 | Maps bool 27 | } 28 | 29 | func DependencyAnalysis(file *protogen.File) *Dependencies { 30 | dependencies := &Dependencies{ 31 | Enums: false, 32 | Maps: false, 33 | } 34 | 35 | if len(file.Enums) > 0 { 36 | dependencies.Enums = true 37 | } 38 | for _, message := range file.Messages { 39 | for _, field := range message.Fields { 40 | if field.Desc.Kind() == protoreflect.MessageKind { 41 | if field.Desc.Message().IsMapEntry() { 42 | dependencies.Maps = true 43 | } 44 | if field.Desc.Message().Fields().Len() > 0 { 45 | dependencies = traverseFields(field.Message, dependencies) 46 | } 47 | } 48 | } 49 | } 50 | return dependencies 51 | } 52 | 53 | func traverseFields(message *protogen.Message, dependencies *Dependencies) *Dependencies { 54 | for _, field := range message.Fields { 55 | if field.Desc.Kind() == protoreflect.MessageKind { 56 | if field.Desc.Message().IsMapEntry() { 57 | dependencies.Maps = true 58 | } 59 | if field.Desc.Message().Fields().Len() > 0 { 60 | dependencies = traverseFields(field.Message, dependencies) 61 | } 62 | } 63 | } 64 | return dependencies 65 | } 66 | -------------------------------------------------------------------------------- /generator/rust/templates/base.templ: -------------------------------------------------------------------------------- 1 | {{template "headers" .}} 2 | 3 | {{template "imports" .}} 4 | 5 | {{template "enums" .}} 6 | 7 | {{template "messages" .}} 8 | -------------------------------------------------------------------------------- /generator/rust/templates/decode.templ: -------------------------------------------------------------------------------- 1 | {{define "decode"}} 2 | 3 | impl Decode for {{ CamelCase .FullName }} { 4 | fn decode (b: &mut Cursor<&mut Vec>) -> Result, Box> { 5 | if b.decode_none() { 6 | return Ok(None); 7 | } 8 | 9 | if let Ok(error) = b.decode_error() { 10 | return Err(error); 11 | } 12 | 13 | 14 | {{ $decoding := GetDecodingFields .Fields -}} 15 | {{ range $field := $decoding.SliceFields -}} 16 | {{ $val := FindValue $field }} 17 | fn {{ SnakeCaseName .Name }}_decode(b: &mut Cursor<&mut Vec>) -> Result, Box> { 18 | {{ $kind := GetKind $field.Kind -}} 19 | {{ $decoder := GetLUTDecoder $field.Kind -}} 20 | 21 | let {{ SnakeCaseName $field.Name }}_size = b.decode_array({{ $kind }})?; 22 | let mut temp = Vec::with_capacity({{ SnakeCaseName $field.Name }}_size); 23 | for _ in 0..{{ SnakeCaseName $field.Name }}_size { 24 | {{ if eq $field.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 25 | temp.push({{ CamelCase $field.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidArray)?); 26 | {{ else -}} 27 | temp.push(b{{ $decoder }}()?); 28 | {{ end -}} 29 | } 30 | Ok(Some(temp)) 31 | } 32 | {{ end -}} 33 | {{ range $field := $decoding.MessageFields -}} 34 | {{ if $field.IsMap -}} 35 | {{ template "decodeMap" $field -}} 36 | {{ end -}} 37 | {{ end -}} 38 | Ok(Some({{ CamelCase .FullName }}{ 39 | {{ range $field := $decoding.Other -}} 40 | {{ $decoder := GetLUTDecoder $field.Kind -}} 41 | {{ if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 42 | {{ SnakeCaseName $field.Name }}: {{ FindValue $field }}::try_from(b.decode_u32()?).ok().ok_or(DecodingError::InvalidEnum)?, 43 | {{ else -}} 44 | {{ SnakeCaseName $field.Name }}: b{{ $decoder }}()?, 45 | {{end -}} 46 | {{end -}} 47 | {{ range $field := $decoding.SliceFields -}} 48 | {{ SnakeCaseName $field.Name }}: {{ SnakeCaseName $field.Name }}_decode(b)?.ok_or(DecodingError::InvalidArray)?, 49 | {{ end -}} 50 | {{ range $field := $decoding.MessageFields -}} 51 | {{ if $field.Message.IsMapEntry -}} 52 | {{ SnakeCaseName $field.Name }}: {{ SnakeCaseName $field.Name }}_decode(b)?.ok_or(DecodingError::InvalidMap)?, 53 | {{ else -}} 54 | {{ SnakeCaseName $field.Name }}: {{ CamelCase $field.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidStruct)?, 55 | {{ end -}} 56 | {{ end -}} 57 | })) 58 | } 59 | } 60 | {{end}} 61 | -------------------------------------------------------------------------------- /generator/rust/templates/decodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "decodeMap"}} 2 | {{ $mapKeyValue := FindValue .MapKey }} 3 | {{ $mapValueValue := FindValue .MapValue }} 4 | fn {{ SnakeCaseName .Name }}_decode(b: &mut Cursor<&mut Vec>) -> Result>, Box> { 5 | if b.decode_none() { 6 | return Ok(None); 7 | } 8 | 9 | {{ $keyDecoder := GetLUTDecoder .MapKey.Kind -}} 10 | {{ $valDecoder := GetLUTDecoder .MapValue.Kind -}} 11 | 12 | {{ $keyKind := GetKind .MapKey.Kind -}} 13 | {{ $valKind := GetKind .MapValue.Kind -}} 14 | let size = b.decode_map({{ $keyKind }}, {{ $valKind }}) 15 | .ok().ok_or(DecodingError::InvalidU32)?; 16 | let mut map = HashMap::new(); 17 | for _ in 0..size { 18 | {{ if and (eq $keyDecoder "") (eq .MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 19 | let k = {{ CamelCase .MapKey.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidMap)?'; 20 | {{else -}} 21 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 22 | let k = {{ $mapKeyValue }}::try_from(b{{$keyDecoder}}()?).ok().ok_or(DecodingError::InvalidMap)?; 23 | {{else -}} 24 | let k = b{{$keyDecoder}}()?; 25 | {{end -}} 26 | {{end -}} 27 | {{ if and (eq $valDecoder "") (eq .MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 28 | let v = {{ CamelCase .MapValue.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidMap)?; 29 | {{else -}} 30 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 31 | let v = {{ $mapValueValue }}::try_from(b{{$valDecoder}}()?).ok().ok_or(DecodingError::InvalidMap)?; 32 | {{else -}} 33 | let v = b{{$valDecoder}}()?; 34 | {{end -}} 35 | {{end -}} 36 | map.insert(k, v); 37 | } 38 | Ok(Some(map)) 39 | } 40 | {{end}} -------------------------------------------------------------------------------- /generator/rust/templates/encode.templ: -------------------------------------------------------------------------------- 1 | {{define "encode"}} 2 | impl Encode for {{ CamelCase .FullName }} { 3 | fn encode<'a> (&'a self, b: &'a mut Cursor>) -> Result<&mut Cursor>, Box> { 4 | {{ $encoding := GetEncodingFields .Fields -}} 5 | {{ if $encoding.Values -}} 6 | b{{ range $val := $encoding.Values -}}{{ $val -}}?{{end -}}; 7 | {{ end -}} 8 | {{ if $encoding.SliceFields -}} 9 | {{template "encodeSlices" $encoding -}} 10 | {{end -}} 11 | {{ if $encoding.MessageFields -}} 12 | {{template "encodeMessages" $encoding -}} 13 | {{end -}} 14 | Ok(b) 15 | } 16 | } 17 | {{end}} 18 | 19 | {{define "encodeSlices"}} 20 | {{ range $field := .SliceFields -}} 21 | {{ $encoder := GetLUTEncoder $field.Kind -}} 22 | 23 | {{ if and (eq $encoder "") (eq $field.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 24 | b.encode_array(self.{{ SnakeCaseName $field.Name}}.len(), Kind::Any)?; 25 | for item in &self.{{ SnakeCaseName $field.Name}} { 26 | item.encode(b)?; 27 | } 28 | {{else -}} 29 | b.encode_array(self.{{ SnakeCaseName $field.Name}}.len(), {{ GetKindLUT $field.Kind }})?; 30 | for item in &self.{{ SnakeCaseName $field.Name}} { 31 | {{ if eq $field.Kind 9 -}} {{/* protoreflect.StringKind */ -}} 32 | b{{$encoder}}(&item)?; 33 | {{ else -}} 34 | b{{$encoder}}(item)?; 35 | {{ end -}} 36 | } 37 | {{end -}} 38 | {{end -}} 39 | {{end}} 40 | 41 | 42 | {{define "encodeMessages"}} 43 | {{ range $field := .MessageFields -}} 44 | {{ if $field.IsMap -}} 45 | {{ $keyKind := GetKind $field.MapKey.Kind -}} 46 | {{ $valKind := GetKind $field.MapValue.Kind -}} 47 | b.encode_map(self.{{ SnakeCaseName $field.Name }}.len(), {{ $keyKind }}, {{ $valKind }})?; 48 | for (k, v) in &self.{{ SnakeCaseName $field.Name }} { 49 | {{ $keyEncoder := GetLUTEncoder $field.MapKey.Kind -}} 50 | {{ if and (eq $keyEncoder "") (eq $field.MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 51 | k.encode(b)?; 52 | {{else -}} 53 | {{ if eq $field.MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 54 | b.encode_u32(k as u32)?; 55 | {{ else if eq $field.MapKey.Kind 9 -}} {{/* protoreflect.StringsKind */ -}} 56 | b{{$keyEncoder}}(&k)?; 57 | {{else -}} 58 | b{{$keyEncoder}}(k)?; 59 | {{end -}} 60 | {{end -}} 61 | {{ $valEncoder := GetLUTEncoder $field.MapValue.Kind -}} 62 | {{ if and (eq $valEncoder "") (eq $field.MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 63 | v.encode(b)?; 64 | {{else -}} 65 | {{ if eq $field.MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 66 | b.encode_u32(*v as u32)?; 67 | {{else -}} 68 | b{{$valEncoder}}(*v)?; 69 | {{end -}} 70 | {{end -}} 71 | } 72 | {{else -}} 73 | self.{{ SnakeCaseName $field.Name }}.encode(b)?; 74 | {{end -}} 75 | {{end -}} 76 | {{end}} -------------------------------------------------------------------------------- /generator/rust/templates/enums.templ: -------------------------------------------------------------------------------- 1 | {{define "enums"}} 2 | {{range $i, $e := (MakeIterable .enums.Len) -}} 3 | {{ $enum := ($.enums.Get $i) -}} 4 | {{template "enum" $enum}} 5 | {{end -}} 6 | {{end}} 7 | 8 | {{define "enum"}} 9 | {{ $enumName := (CamelCase $.FullName) -}} 10 | #[derive(Debug, Eq, PartialEq, TryFromPrimitive, Copy, Clone)] 11 | #[repr(u32)] 12 | pub enum {{ $enumName }} { 13 | {{range $i, $v := (MakeIterable $.Values.Len) -}} 14 | {{ $val := ($.Values.Get $i) -}} 15 | {{$val.Name}} = {{ $i }}, 16 | {{end -}} 17 | } 18 | {{end}} 19 | 20 | -------------------------------------------------------------------------------- /generator/rust/templates/headers.templ: -------------------------------------------------------------------------------- 1 | {{define "headers"}} 2 | // Code generated by polyglot {{ .pluginVersion }}, DO NOT EDIT. 3 | // source: {{ .sourcePath }} 4 | {{end}} -------------------------------------------------------------------------------- /generator/rust/templates/imports.templ: -------------------------------------------------------------------------------- 1 | {{define "imports"}} 2 | use std::io::Cursor; 3 | use polyglot_rs::{DecodingError, Encoder, Decoder, Kind}; 4 | {{ if .dependencies.Enums -}} 5 | use num_enum::TryFromPrimitive; 6 | use std::convert::TryFrom; 7 | {{ end -}} 8 | {{ if .dependencies.Maps -}} 9 | use std::collections::HashMap; 10 | {{ end -}} 11 | {{end}} -------------------------------------------------------------------------------- /generator/rust/templates/messages.templ: -------------------------------------------------------------------------------- 1 | {{define "messages"}} 2 | pub trait Encode { 3 | fn encode<'a> (&'a self, b: &'a mut Cursor>) -> Result<&mut Cursor>, Box>; 4 | } 5 | 6 | pub trait Decode { 7 | fn decode (b: &mut Cursor<&mut Vec>) -> Result, Box> where Self: Sized; 8 | } 9 | {{range $i, $e := (MakeIterable .messages.Len) -}} 10 | {{ $message := $.messages.Get $i }} 11 | {{range $i, $e := (MakeIterable $message.Enums.Len) -}} 12 | {{ $enum := ($message.Enums.Get $i) }} 13 | {{template "enum" $enum}} 14 | {{end}} 15 | {{template "structs" $message}} 16 | {{end}} 17 | {{end}} -------------------------------------------------------------------------------- /generator/rust/templates/structs.templ: -------------------------------------------------------------------------------- 1 | {{define "structs"}} 2 | {{ range $i, $v := (MakeIterable $.Messages.Len) }} 3 | {{ $message := $.Messages.Get $i }} 4 | {{ if not $message.IsMapEntry }} 5 | {{template "structs" $message}} 6 | {{end}} 7 | {{end}} 8 | pub struct {{ CamelCase .FullName }} { 9 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 10 | {{ $field := $.Fields.Get $i -}} 11 | {{ $value := FindValue $field -}} 12 | 13 | {{ $privacy := GeneratedFieldPrivacy -}} 14 | {{ if eq $privacy "private" -}} 15 | {{ SnakeCaseName $field.Name }}: {{ $value }}, 16 | {{ else if eq $privacy "public" -}} 17 | pub {{ SnakeCaseName $field.Name }}: {{ $value }}, 18 | {{ else -}} 19 | pub(crate) {{ SnakeCaseName $field.Name }}: {{ $value }}, 20 | {{ end -}} 21 | {{end -}} 22 | } 23 | 24 | {{template "encode" .}} 25 | {{template "decode" .}} 26 | {{end}} 27 | -------------------------------------------------------------------------------- /generator/rust/templates/templates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | import "embed" 20 | 21 | //go:embed * 22 | var FS embed.FS 23 | -------------------------------------------------------------------------------- /generator/typescript/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package typescript 18 | 19 | const ( 20 | extension = ".polyglot.ts" 21 | polyglotAnyKind = "Kind.Any" 22 | ) 23 | -------------------------------------------------------------------------------- /generator/typescript/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package typescript 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/generator/typescript/templates" 21 | "github.com/loopholelabs/polyglot/utils" 22 | "github.com/loopholelabs/polyglot/version" 23 | 24 | "google.golang.org/protobuf/compiler/protogen" 25 | "google.golang.org/protobuf/proto" 26 | "google.golang.org/protobuf/reflect/protoreflect" 27 | "google.golang.org/protobuf/types/pluginpb" 28 | 29 | "bytes" 30 | "strings" 31 | "text/template" 32 | ) 33 | 34 | type Generator struct { 35 | options *protogen.Options 36 | templ *template.Template 37 | CustomFields func() string 38 | CustomEncode func() string 39 | CustomDecode func() string 40 | 41 | dependencies map[string]struct{} 42 | } 43 | 44 | func New() *Generator { 45 | var g *Generator 46 | templ := template.Must(template.New("main").Funcs(template.FuncMap{ 47 | "CamelCase": utils.CamelCaseFullName, 48 | "CamelCaseName": utils.CamelCaseName, 49 | "CamelCaseFullName": utils.CamelCaseFullName, 50 | "MakeIterable": utils.MakeIterable, 51 | "Counter": utils.Counter, 52 | "FirstLowerCase": utils.FirstLowerCase, 53 | "FirstLowerCaseName": utils.FirstLowerCaseName, 54 | "FindValue": findValue, 55 | "GetKind": func(kind protoreflect.Kind) string { 56 | return getKind(g.dependencies, kind) 57 | }, 58 | "GetLUTEncoder": func(kind protoreflect.Kind) string { 59 | return getLUTEncoder(g.trackDependency, kind) 60 | }, 61 | "GetLUTDecoder": func(kind protoreflect.Kind) string { 62 | return getLUTDecoder(g.trackDependency, kind) 63 | }, 64 | "GetEncodingFields": func(fields protoreflect.FieldDescriptors) encodingFields { 65 | return getEncodingFields(g.trackDependency, fields) 66 | }, 67 | "GetDecodingFields": func(fields protoreflect.FieldDescriptors) decodingFields { 68 | return getDecodingFields(g.trackDependency, fields) 69 | }, 70 | "GetKindLUT": func(kind protoreflect.Kind) string { 71 | return getKindLUT(g.trackDependency, kind) 72 | }, 73 | "LowercaseCamelCase": utils.LowercaseCamelCase, 74 | "LowercaseCamelCaseName": utils.LowercaseCamelCaseName, 75 | "CustomFields": func() string { 76 | return g.CustomFields() 77 | }, 78 | "CustomEncode": func() string { 79 | return g.CustomEncode() 80 | }, 81 | "CustomDecode": func() string { 82 | return g.CustomDecode() 83 | }, 84 | "TrimSuffix": strings.TrimSuffix, 85 | "TrackDependency": func(dep string) string { 86 | return g.trackDependency(dep) 87 | }, 88 | }).ParseFS(templates.FS, "*")) 89 | g = &Generator{ 90 | options: &protogen.Options{ 91 | ParamFunc: func(name string, value string) error { return nil }, 92 | ImportRewriteFunc: func(path protogen.GoImportPath) protogen.GoImportPath { return path }, 93 | }, 94 | templ: templ, 95 | CustomEncode: func() string { return "" }, 96 | CustomDecode: func() string { return "" }, 97 | CustomFields: func() string { return "" }, 98 | 99 | dependencies: map[string]struct{}{}, 100 | } 101 | return g 102 | } 103 | 104 | func (*Generator) UnmarshalRequest(buf []byte) (*pluginpb.CodeGeneratorRequest, error) { 105 | req := new(pluginpb.CodeGeneratorRequest) 106 | return req, proto.Unmarshal(buf, req) 107 | } 108 | 109 | func (*Generator) MarshalResponse(res *pluginpb.CodeGeneratorResponse) ([]byte, error) { 110 | return proto.Marshal(res) 111 | } 112 | 113 | func (g *Generator) Generate(req *pluginpb.CodeGeneratorRequest) (res *pluginpb.CodeGeneratorResponse, err error) { 114 | plugin, err := g.options.New(req) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | for _, f := range plugin.Files { 120 | if !f.Generate { 121 | continue 122 | } 123 | genFile := plugin.NewGeneratedFile(FileName(f.GeneratedFilenamePrefix), f.GoImportPath) 124 | 125 | packageName := string(f.Desc.Package().Name()) 126 | if packageName == "" { 127 | packageName = string(f.GoPackageName) 128 | } 129 | 130 | err = g.ExecuteTemplate(genFile, f, packageName, true) 131 | if err != nil { 132 | return nil, err 133 | } 134 | } 135 | 136 | return plugin.Response(), nil 137 | } 138 | 139 | func (g *Generator) ExecuteTemplate( 140 | genFile *protogen.GeneratedFile, 141 | protoFile *protogen.File, 142 | packageName string, 143 | header bool, 144 | ) error { 145 | var bodyBuf bytes.Buffer 146 | if err := g.templ.ExecuteTemplate(&bodyBuf, "body.templ", map[string]interface{}{ 147 | "enums": protoFile.Desc.Enums(), 148 | "messages": protoFile.Desc.Messages(), 149 | }); err != nil { 150 | return err 151 | } 152 | 153 | var headBuf bytes.Buffer 154 | if err := g.templ.ExecuteTemplate(&headBuf, "head.templ", map[string]interface{}{ 155 | "pluginVersion": version.Version(), 156 | "sourcePath": protoFile.Desc.Path(), 157 | "package": packageName, 158 | "header": header, 159 | "dependencies": g.dependencies, 160 | }); err != nil { 161 | return err 162 | } 163 | 164 | _, err := genFile.Write(append(headBuf.Bytes(), bodyBuf.Bytes()...)) 165 | return err 166 | } 167 | 168 | func (g *Generator) trackDependency(dep string) string { 169 | g.dependencies[dep] = struct{}{} 170 | 171 | return dep 172 | } 173 | -------------------------------------------------------------------------------- /generator/typescript/headers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package typescript 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/utils" 21 | ) 22 | 23 | func FileName(name string) string { 24 | return utils.AppendString(name, extension) 25 | } 26 | -------------------------------------------------------------------------------- /generator/typescript/templates/body.templ: -------------------------------------------------------------------------------- 1 | {{template "enums" .}} 2 | 3 | {{template "messages" .}} 4 | -------------------------------------------------------------------------------- /generator/typescript/templates/decode.templ: -------------------------------------------------------------------------------- 1 | {{define "decode"}} 2 | static decode(buf: Uint8Array): { buf: Uint8Array, value: {{ CamelCase .FullName }}} { 3 | let decoded = buf 4 | 5 | {{ $decoding := GetDecodingFields .Fields -}} 6 | {{ range $field := $decoding.SliceFields -}} 7 | {{ $val := FindValue $field }} 8 | {{ $kind := GetKind $field.Kind -}} 9 | {{ $decoder := GetLUTDecoder $field.Kind -}} 10 | 11 | let {{ LowercaseCamelCaseName $field.Name }} = {{ TrackDependency "decodeArray" }}(decoded) 12 | decoded = {{ LowercaseCamelCaseName $field.Name }}.buf 13 | const {{ LowercaseCamelCaseName $field.Name }}Temp: { value: {{ $val }} } = { value: [] } 14 | for (let i = 0; i < {{ LowercaseCamelCaseName $field.Name }}.size; i++) { 15 | {{ if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 16 | let elementEnum = {{ TrackDependency "decodeUint32" }}(decoded) 17 | const element = { value: elementEnum as {{ $val }} } 18 | decoded = element.bufEnum 19 | {{ else if eq $field.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 20 | let element = {{ TrimSuffix $val "[]" }}.decode(decoded) 21 | decoded = element.buf 22 | {{ else -}} 23 | let element = {{ $decoder }}(decoded) 24 | decoded = element.buf 25 | {{end -}} 26 | {{ LowercaseCamelCaseName $field.Name }}Temp.value.push(element.value) 27 | } 28 | {{ end -}} 29 | 30 | {{ range $field := $decoding.MessageFields -}} 31 | {{ $val := FindValue $field }} 32 | {{ if $field.IsMap -}} 33 | {{ template "decodeMap" $field -}} 34 | {{ else -}} 35 | let {{ LowercaseCamelCaseName $field.Name }}Temp = {{ $val }}.decode(decoded) 36 | decoded = {{ LowercaseCamelCaseName $field.Name }}Temp.buf 37 | {{ end -}} 38 | {{ end -}} 39 | 40 | {{ range $field := $decoding.Other -}} 41 | {{ $val := FindValue $field }} 42 | {{ $decoder := GetLUTDecoder $field.Kind -}} 43 | {{ if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 44 | let {{ LowercaseCamelCaseName $field.Name }}U32 = {{ TrackDependency "decodeUint32" }}(decoded) 45 | const {{ LowercaseCamelCaseName $field.Name }}Temp = { value: {{ LowercaseCamelCaseName $field.Name }}U32 as {{ $val }} } 46 | decoded = {{ LowercaseCamelCaseName $field.Name }}U32.buf 47 | {{ else -}} 48 | let {{ LowercaseCamelCaseName $field.Name }}Temp = {{ $decoder }}(decoded) 49 | decoded = {{ LowercaseCamelCaseName $field.Name }}Temp.buf 50 | {{end -}} 51 | {{end}} 52 | 53 | return { buf: decoded, value: new {{ CamelCase .FullName }}( 54 | {{ range $field := $decoding.Other -}} 55 | {{ LowercaseCamelCaseName $field.Name }}Temp.value, 56 | {{end -}} 57 | {{ range $field := $decoding.SliceFields -}} 58 | {{ LowercaseCamelCaseName $field.Name }}Temp.value, 59 | {{ end -}} 60 | {{ range $field := $decoding.MessageFields -}} 61 | {{ LowercaseCamelCaseName $field.Name }}Temp.value, 62 | {{ end -}} 63 | )} 64 | } 65 | {{end}} 66 | -------------------------------------------------------------------------------- /generator/typescript/templates/decodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "decodeMap"}} 2 | {{ $mapKeyValue := FindValue .MapKey }} 3 | {{ $mapValueValue := FindValue .MapValue }} 4 | {{ $keyDecoder := GetLUTDecoder .MapKey.Kind -}} 5 | {{ $valDecoder := GetLUTDecoder .MapValue.Kind -}} 6 | 7 | let {{ LowercaseCamelCaseName .Name }} = {{ TrackDependency "decodeMap" }}(decoded) 8 | decoded = {{ LowercaseCamelCaseName .Name }}.buf 9 | const {{ LowercaseCamelCaseName .Name }}Temp: { value: Map<{{ $mapKeyValue }},{{ $mapValueValue }}> } = { value: new Map() } 10 | for (let i = 0; i < {{ LowercaseCamelCaseName .Name }}.size; i++) { 11 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 12 | let keyEnum = {{ TrackDependency "decodeUint32" }}(decoded) 13 | const key = { value: keyEnum as {{ $mapKeyValue }} } 14 | decoded = keyEnum.buf 15 | {{ else if eq .MapKey.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 16 | let key = {{ TrimSuffix $mapKeyValue "[]" }}.decode(decoded) 17 | decoded = key.buf 18 | {{ else -}} 19 | let key = {{ $keyDecoder }}(decoded) 20 | decoded = key.buf 21 | {{end}} 22 | 23 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 24 | let valueEnum = {{ TrackDependency "decodeUint32" }}(decoded) 25 | const value = { value: valueEnum as {{ $mapValueValue }} } 26 | decoded = valueEnum.buf 27 | {{ else if eq .MapValue.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 28 | let value = {{ TrimSuffix $mapValueValue "[]" }}.decode(decoded) 29 | decoded = value.buf 30 | {{ else -}} 31 | let value = {{ $valDecoder }}(decoded) 32 | decoded = value.buf 33 | {{end}} 34 | 35 | {{ LowercaseCamelCaseName .Name }}.value.set(key, value) 36 | } 37 | {{end}} -------------------------------------------------------------------------------- /generator/typescript/templates/encode.templ: -------------------------------------------------------------------------------- 1 | {{define "encode"}} 2 | encode(buf: Uint8Array): Uint8Array { 3 | let encoded = buf 4 | 5 | {{ $encoding := GetEncodingFields .Fields -}} 6 | 7 | {{ range $val := $encoding.Values}} 8 | encoded = {{ $val -}} 9 | {{end }} 10 | 11 | {{ if $encoding.SliceFields -}} 12 | {{template "encodeSlices" $encoding -}} 13 | {{end -}} 14 | {{ if $encoding.MessageFields -}} 15 | {{template "encodeMessages" $encoding -}} 16 | {{end}} 17 | 18 | return encoded 19 | } 20 | {{end}} 21 | 22 | {{define "encodeSlices"}} 23 | {{ range $field := .SliceFields -}} 24 | {{ $encoder := GetLUTEncoder $field.Kind -}} 25 | 26 | {{ if and (eq $encoder "") (eq $field.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 27 | encoded = {{ TrackDependency "encodeArray" }}(encoded, this._{{ LowercaseCamelCaseName $field.Name}}.length, Kind.Any) 28 | this._{{ LowercaseCamelCaseName $field.Name}}.forEach((field) => { 29 | encoded = field.encode(encoded) 30 | }) 31 | {{else -}} 32 | encoded = {{ TrackDependency "encodeArray" }}(this._{{ LowercaseCamelCaseName $field.Name}}.length, {{ GetKindLUT $field.Kind }}) 33 | this._{{ LowercaseCamelCaseName $field.Name}}.forEach((field) => { 34 | encoded = {{$encoder}}(encoded, field) 35 | }) 36 | {{end -}} 37 | {{end -}} 38 | {{end}} 39 | 40 | 41 | {{define "encodeMessages"}} 42 | {{ range $field := .MessageFields -}} 43 | {{ if $field.IsMap -}} 44 | {{ $keyKind := GetKind $field.MapKey.Kind -}} 45 | {{ $valKind := GetKind $field.MapValue.Kind -}} 46 | encoded = {{ TrackDependency "encodeMap" }}(encoded, this._{{ LowercaseCamelCaseName $field.Name }}.size, {{ $keyKind }}, {{ $valKind }}) 47 | this._{{ LowercaseCamelCaseName $field.Name }}.forEach((v, k) => { 48 | {{ $keyEncoder := GetLUTEncoder $field.MapKey.Kind -}} 49 | {{ if and (eq $keyEncoder "") (eq $field.MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 50 | encoded = k.encode(encoded) 51 | {{else -}} 52 | {{ if eq $field.MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 53 | encoded = {{ TrackDependency "encodeUint32" }}(encoded, k as number) 54 | {{else -}} 55 | encoded = {{$keyEncoder}}(encoded, k) 56 | {{end -}} 57 | {{end -}} 58 | {{ $valEncoder := GetLUTEncoder $field.MapValue.Kind -}} 59 | {{ if and (eq $valEncoder "") (eq $field.MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 60 | encoded = v.encode(encoded) 61 | {{else -}} 62 | {{ if eq $field.MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 63 | encoded = {{ TrackDependency "encodeUint32" }}(encoded, v as number) 64 | {{else -}} 65 | encoded = {{$valEncoder}}(encoded, v) 66 | {{end -}} 67 | {{end -}} 68 | }) 69 | {{else -}} 70 | encoded = this._{{ LowercaseCamelCaseName $field.Name }}.encode(encoded); 71 | {{end -}} 72 | {{end -}} 73 | {{end}} -------------------------------------------------------------------------------- /generator/typescript/templates/enums.templ: -------------------------------------------------------------------------------- 1 | {{define "enums"}} 2 | {{range $i, $e := (MakeIterable .enums.Len) -}} 3 | {{ $enum := ($.enums.Get $i) -}} 4 | {{template "enum" $enum}} 5 | {{end -}} 6 | {{end}} 7 | 8 | {{define "enum"}} 9 | {{ $enumName := (CamelCaseFullName $.FullName) -}} 10 | enum {{ $enumName }} { 11 | {{range $i, $v := (MakeIterable $.Values.Len) -}} 12 | {{ $val := ($.Values.Get $i) -}} 13 | {{$val.Name}} = {{ $i }}, 14 | {{end -}} 15 | } 16 | {{end}} 17 | 18 | -------------------------------------------------------------------------------- /generator/typescript/templates/head.templ: -------------------------------------------------------------------------------- 1 | {{template "headers" .}} 2 | 3 | {{template "imports" .}} -------------------------------------------------------------------------------- /generator/typescript/templates/headers.templ: -------------------------------------------------------------------------------- 1 | {{define "headers"}} 2 | // Code generated by polyglot {{ .pluginVersion }}, DO NOT EDIT. 3 | // source: {{ .sourcePath }} 4 | {{end}} -------------------------------------------------------------------------------- /generator/typescript/templates/imports.templ: -------------------------------------------------------------------------------- 1 | {{define "imports"}} 2 | import { {{ range $key, $_ := .dependencies -}}{{ $key }},{{ end -}} } from "polyglot" 3 | {{end}} -------------------------------------------------------------------------------- /generator/typescript/templates/messages.templ: -------------------------------------------------------------------------------- 1 | {{define "messages"}} 2 | {{range $i, $e := (MakeIterable .messages.Len) -}} 3 | {{ $message := $.messages.Get $i }} 4 | {{range $i, $e := (MakeIterable $message.Enums.Len) -}} 5 | {{ $enum := ($message.Enums.Get $i) }} 6 | {{template "enum" $enum}} 7 | {{end}} 8 | {{template "structs" $message}} 9 | {{end}} 10 | {{end}} -------------------------------------------------------------------------------- /generator/typescript/templates/structs.templ: -------------------------------------------------------------------------------- 1 | {{define "structs"}} 2 | {{ range $i, $v := (MakeIterable $.Messages.Len) }} 3 | {{ $message := $.Messages.Get $i }} 4 | {{ if not $message.IsMapEntry }} 5 | {{template "structs" $message}} 6 | {{end}} 7 | {{end}} 8 | export class {{ CamelCase .FullName }} { 9 | constructor( 10 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 11 | {{ $field := $.Fields.Get $i -}} 12 | {{ $value := FindValue $field -}} 13 | {{ LowercaseCamelCaseName $field.Name }}: {{ $value }}, 14 | {{end -}} 15 | ) { 16 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 17 | {{ $field := $.Fields.Get $i -}} 18 | this._{{ LowercaseCamelCaseName $field.Name }} = {{ LowercaseCamelCaseName $field.Name }} 19 | {{end -}} 20 | } 21 | 22 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 23 | {{ $field := $.Fields.Get $i -}} 24 | {{ $value := FindValue $field -}} 25 | private _{{ LowercaseCamelCaseName $field.Name }}: {{ $value }} 26 | 27 | get {{ LowercaseCamelCaseName $field.Name }}(): {{ $value }} { 28 | return this._{{ LowercaseCamelCaseName $field.Name }} 29 | } 30 | 31 | set {{ LowercaseCamelCaseName $field.Name }}({{ LowercaseCamelCaseName $field.Name }}: {{ $value }}) { 32 | this._{{ LowercaseCamelCaseName $field.Name }} = {{ LowercaseCamelCaseName $field.Name }} 33 | } 34 | 35 | {{end -}} 36 | 37 | {{template "encode" .}} 38 | 39 | {{template "decode" .}} 40 | } 41 | {{end}} 42 | -------------------------------------------------------------------------------- /generator/typescript/templates/templates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | import "embed" 20 | 21 | //go:embed * 22 | var FS embed.FS 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/loopholelabs/polyglot 2 | 3 | go 1.21 4 | 5 | toolchain go1.22.6 6 | 7 | require ( 8 | github.com/stretchr/testify v1.10.0 9 | google.golang.org/protobuf v1.36.5 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 4 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 10 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 11 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 12 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export * from "./decoder"; 18 | export * from "./encoder"; 19 | export * from "./kind"; 20 | -------------------------------------------------------------------------------- /integration-test-data.json: -------------------------------------------------------------------------------- 1 | [{"name":"None","kind":0,"decodedValue":null,"encodedValue":"AA=="},{"name":"true Bool","kind":7,"decodedValue":true,"encodedValue":"BwE="},{"name":"false Bool","kind":7,"decodedValue":false,"encodedValue":"BwA="},{"name":"U8","kind":8,"decodedValue":32,"encodedValue":"CCA="},{"name":"U16","kind":9,"decodedValue":1024,"encodedValue":"CYAI"},{"name":"U32","kind":10,"decodedValue":4294967290,"encodedValue":"Cvr///8P"},{"name":"U64","kind":11,"decodedValue":18446744073709551610,"encodedValue":"C/r//////////wE="},{"name":"I32","kind":12,"decodedValue":-2147483648,"encodedValue":"DP////8P"},{"name":"I64","kind":13,"decodedValue":-9223372036854775808,"encodedValue":"Df///////////wE="},{"name":"F32","kind":14,"decodedValue":-214648.34432,"encodedValue":"DshRnhY="},{"name":"F64","kind":15,"decodedValue":-922337203685.2345,"encodedValue":"D8Jq1/KavKeB"},{"name":"Array","kind":1,"decodedValue":["1","2","3"],"encodedValue":"AQUKAwUKATEFCgEyBQoBMw=="},{"name":"Map","kind":2,"decodedValue":{"1":1,"2":2,"3":3},"encodedValue":"AgUKCgMFCgExCgEFCgEyCgIFCgEzCgM="},{"name":"nil or empty Map","kind":2,"decodedValue":{},"encodedValue":"AgUKCgA="},{"name":"Bytes","kind":4,"decodedValue":"VGVzdCBTdHJpbmc=","encodedValue":"BAoLVGVzdCBTdHJpbmc="},{"name":"String","kind":5,"decodedValue":"Test String","encodedValue":"BQoLVGVzdCBTdHJpbmc="},{"name":"Error","kind":6,"decodedValue":"Test String","encodedValue":"BgUKC1Rlc3QgU3RyaW5n"}] -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "clearMocks": true, 3 | "collectCoverage": true, 4 | "coverageDirectory": "coverage", 5 | "preset": "ts-jest", 6 | "testEnvironment": "jsdom" 7 | } 8 | -------------------------------------------------------------------------------- /kind.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pub enum Kind { 18 | None = 0x00, 19 | Array = 0x01, 20 | Map = 0x02, 21 | Any = 0x03, 22 | Bytes = 0x04, 23 | String = 0x05, 24 | Error = 0x06, 25 | Bool = 0x07, 26 | U8 = 0x08, 27 | U16 = 0x09, 28 | U32 = 0x0a, 29 | U64 = 0x0b, 30 | I32 = 0x0c, 31 | I64 = 0x0d, 32 | F32 = 0x0e, 33 | F64 = 0x0f, 34 | 35 | Unknown, 36 | } 37 | 38 | impl From for Kind { 39 | fn from(orig: u8) -> Self { 40 | match orig { 41 | 0x00 => Kind::None, 42 | 0x01 => Kind::Array, 43 | 0x02 => Kind::Map, 44 | 0x03 => Kind::Any, 45 | 0x04 => Kind::Bytes, 46 | 0x05 => Kind::String, 47 | 0x06 => Kind::Error, 48 | 0x07 => Kind::Bool, 49 | 0x08 => Kind::U8, 50 | 0x09 => Kind::U16, 51 | 0x0a => Kind::U32, 52 | 0x0b => Kind::U64, 53 | 0x0c => Kind::I32, 54 | 0x0d => Kind::I64, 55 | 0x0e => Kind::F32, 56 | 0x0f => Kind::F64, 57 | 58 | _ => Kind::Unknown, 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /kind.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export enum Kind { 18 | Null = 0x00, 19 | Array = 0x01, 20 | Map = 0x02, 21 | Any = 0x03, 22 | Uint8Array = 0x04, 23 | String = 0x05, 24 | Error = 0x06, 25 | Boolean = 0x07, 26 | Uint8 = 0x08, 27 | Uint16 = 0x09, 28 | Uint32 = 0x0a, 29 | Uint64 = 0x0b, 30 | Int32 = 0x0c, 31 | Int64 = 0x0d, 32 | Float32 = 0x0e, 33 | Float64 = 0x0f, 34 | } 35 | -------------------------------------------------------------------------------- /lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | extern crate core; 18 | 19 | mod decoder; 20 | mod encoder; 21 | mod integration; 22 | mod kind; 23 | 24 | pub use decoder::Decoder; 25 | pub use decoder::DecodingError; 26 | pub use encoder::Encoder; 27 | pub use encoder::EncodingError; 28 | pub use kind::Kind; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@loopholelabs/polyglot", 3 | "version": "2.0.5", 4 | "license": "Apache-2.0", 5 | "description": "A high-performance serialization framework used for encoding and decoding arbitrary datastructures across languages.", 6 | "homepage": "https://github.com/loopholelabs/polyglot", 7 | "source": "index.ts", 8 | "types": "types.d.ts", 9 | "scripts": { 10 | "build": "rm -rf dist && tsc --outDir dist && cp package.json dist", 11 | "lint": "eslint . --ext .ts", 12 | "docs": "typedoc index.ts", 13 | "test": "jest" 14 | }, 15 | "keywords": [ 16 | "polyglot", 17 | "serialization", 18 | "deserialization", 19 | "encoding", 20 | "decoding" 21 | ], 22 | "dependencies": {}, 23 | "devDependencies": { 24 | "@types/jest": "^29.5.2", 25 | "@types/json-bigint": "^1.0.1", 26 | "@types/node": "^22.5.2", 27 | "@typescript-eslint/eslint-plugin": "^8.13.0", 28 | "@typescript-eslint/parser": "^8.13.0", 29 | "eslint": "^8.56.0", 30 | "eslint-config-airbnb-base": "^15.0.0", 31 | "eslint-config-prettier": "^9.0.0", 32 | "eslint-plugin-import": "^2.27.5", 33 | "eslint-plugin-prettier": "^5.0.0", 34 | "jest": "^29.5.0", 35 | "jest-environment-jsdom": "^29.5.0", 36 | "json-bigint": "^1.0.0", 37 | "prettier": "^3.0.3", 38 | "process": "^0.11.10", 39 | "ts-jest": "^29.1.0", 40 | "typedoc": "^0.26.3", 41 | "typescript": "^5.2.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /polyglot.pc: -------------------------------------------------------------------------------- 1 | prefix=/usr/local 2 | exec_prefix=${prefix} 3 | libdir=/usr/local/lib64 4 | includedir=/usr/local/include/ 5 | 6 | Name: polyglot 7 | Version: 2.0.2 8 | Description: Polyglot C/C++ Bindings 9 | Requires: 10 | Libs: -L${libdir} -lpolyglot 11 | Cflags: -I${includedir} 12 | -------------------------------------------------------------------------------- /polyglot.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=@LIBDIR@ 4 | includedir=@INCLUDE_DIR@/ 5 | 6 | Name: polyglot 7 | Version: @_VERSION_MAJOR@.@_VERSION_MINOR@.@_VERSION_MICRO@ 8 | Description: Polyglot C/C++ Bindings 9 | Requires: 10 | Libs: -L${libdir} -lpolyglot 11 | Cflags: -I${includedir} -------------------------------------------------------------------------------- /protoc-gen-go-polyglot/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/generator/golang" 21 | 22 | "io" 23 | "os" 24 | ) 25 | 26 | func main() { 27 | gen := golang.New() 28 | 29 | data, err := io.ReadAll(os.Stdin) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | req, err := gen.UnmarshalRequest(data) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | res, err := gen.Generate(req) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | data, err = gen.MarshalResponse(res) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | _, err = os.Stdout.Write(data) 50 | if err != nil { 51 | panic(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /protoc-gen-rs-polyglot/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/generator/rust" 21 | 22 | "io" 23 | "os" 24 | ) 25 | 26 | func main() { 27 | gen := rust.New() 28 | 29 | data, err := io.ReadAll(os.Stdin) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | req, err := gen.UnmarshalRequest(data) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | res, err := gen.Generate(req) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | data, err = gen.MarshalResponse(res) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | _, err = os.Stdout.Write(data) 50 | if err != nil { 51 | panic(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /protoc-gen-ts-polyglot/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/generator/typescript" 21 | 22 | "io" 23 | "os" 24 | ) 25 | 26 | func main() { 27 | gen := typescript.New() 28 | 29 | data, err := io.ReadAll(os.Stdin) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | req, err := gen.UnmarshalRequest(data) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | res, err := gen.Generate(req) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | data, err = gen.MarshalResponse(res) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | _, err = os.Stdout.Write(data) 50 | if err != nil { 51 | panic(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.ts"], 3 | "exclude": ["node_modules", "dist", "target", "**/*.go", "**/*.rs", "*.go", "*.rs"], 4 | "compilerOptions": { 5 | "target": "es2020", 6 | "module": "commonjs", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "resolveJsonModule": true, 12 | "sourceMap": true, 13 | "declaration": true, 14 | "paths": { 15 | "polyglot": ["./"] 16 | }, 17 | "types": ["node", "jest"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "bytes" 21 | "google.golang.org/protobuf/reflect/protoreflect" 22 | 23 | "strings" 24 | "unicode" 25 | "unicode/utf8" 26 | ) 27 | 28 | // CamelCase returns the CamelCased name. 29 | // If there is an interior underscore followed by a lower case letter, 30 | // drop the underscore and convert the letter to upper case. 31 | // There is a remote possibility of this rewrite causing a name collision, 32 | // but it's so remote we're prepared to pretend it's nonexistent - since the 33 | // C++ generator lowercases names, it's extremely unlikely to have two fields 34 | // with different capitalizations. 35 | // In short, _my_field_name_2 becomes XMyFieldName_2. 36 | func CamelCase(s string) string { 37 | if s == "" { 38 | return "" 39 | } 40 | t := make([]byte, 0, 32) 41 | i := 0 42 | if s[0] == '_' { // Keep the initial _ if it exists 43 | i++ 44 | } 45 | // Invariant: if the next letter is lower case, it must be converted 46 | // to upper case. 47 | // That is, we process a word at a time, where words are marked by _ or 48 | // upper case letter. Digits are treated as words. 49 | for ; i < len(s); i++ { 50 | c := s[i] 51 | if c == '_' && i+1 < len(s) && 'a' <= s[i+1] && s[i+1] <= 'z' { 52 | continue // Skip the underscore in s. 53 | } 54 | if c == '.' { 55 | continue 56 | } 57 | if '0' <= c && c <= '9' { 58 | t = append(t, c) 59 | continue 60 | } 61 | // Assume we have a letter now - if not, it's a bogus identifier. 62 | // The next word is a sequence of characters that must start upper case. 63 | if 'a' <= c && c <= 'z' { 64 | c ^= ' ' // Make it a capital letter. 65 | } 66 | t = append(t, c) // Guaranteed not lower case. 67 | // Accept lower case sequence that follows. 68 | for i+1 < len(s) && 'a' <= s[i+1] && s[i+1] <= 'z' { 69 | i++ 70 | t = append(t, s[i]) 71 | } 72 | } 73 | return string(t) 74 | } 75 | 76 | func SnakeCase(s string) string { 77 | var b bytes.Buffer 78 | var consc bool 79 | for _, c := range s { 80 | if 'A' <= c && c <= 'Z' { 81 | if b.Len() > 0 && !consc { 82 | b.WriteRune('_') 83 | } 84 | b.WriteRune(c - 'A' + 'a') 85 | consc = true 86 | } else { 87 | b.WriteRune(c) 88 | consc = false 89 | } 90 | } 91 | return b.String() 92 | } 93 | 94 | func SnakeCaseName(name protoreflect.Name) string { 95 | return SnakeCase(string(name)) 96 | } 97 | 98 | func CamelCaseFullName(name protoreflect.FullName) string { 99 | return CamelCase(string(name)) 100 | } 101 | 102 | func CamelCaseName(name protoreflect.Name) string { 103 | return CamelCase(string(name)) 104 | } 105 | 106 | func AppendString(inputs ...string) string { 107 | builder := new(strings.Builder) 108 | for _, s := range inputs { 109 | builder.WriteString(s) 110 | } 111 | 112 | return builder.String() 113 | } 114 | 115 | func FirstLowerCase(s string) string { 116 | if s == "" { 117 | return "" 118 | } 119 | r, n := utf8.DecodeRuneInString(s) 120 | return string(unicode.ToLower(r)) + s[n:] 121 | } 122 | 123 | func FirstLowerCaseName(name protoreflect.Name) string { 124 | return FirstLowerCase(string(name)) 125 | } 126 | 127 | func LowercaseCamelCase(s string) string { 128 | if len(s) == 0 { 129 | return s 130 | } 131 | 132 | if len(s) == 1 { 133 | return string(unicode.ToLower(rune(s[0]))) 134 | } 135 | 136 | return string(unicode.ToLower(rune(s[0]))) + s[1:] 137 | } 138 | 139 | func LowercaseCamelCaseName(name protoreflect.Name) string { 140 | return LowercaseCamelCase(string(name)) 141 | } 142 | 143 | func MakeIterable(length int) []struct{} { 144 | return make([]struct{}, length) 145 | } 146 | 147 | func Counter(initial int) func() int { 148 | i := initial 149 | return func() int { 150 | i++ 151 | return i 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /v2/benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | BENCHCOUNT ?= 10 2 | 3 | install: 4 | - go install ../protoc-gen-go-polyglot 5 | - go install github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto@latest 6 | - go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 7 | 8 | generate: 9 | - mkdir -p polyglot 10 | - protoc --go-polyglot_out=polyglot bench.proto 11 | - mkdir -p vtproto 12 | - protoc --go_out=vtproto --go-vtproto_out=vtproto bench.proto 13 | 14 | benchmark-polyglot: benchmark-polyglot-cmp 15 | - go run -mod=mod golang.org/x/perf/cmd/benchstat bench.txt 16 | - go mod tidy 17 | - rm -rf bench.txt 18 | 19 | benchmark-polyglot-cmp: 20 | - go test -bench=. -timeout=24h -count=$(BENCHCOUNT) ./... -test.short | tee bench.txt 21 | 22 | benchmark-polyglot-long: 23 | - go test -bench=. -timeout=24h -count=$(BENCHCOUNT) ./... | tee bench.txt 24 | - go run -mod=mod golang.org/x/perf/cmd/benchstat bench.txt 25 | - go mod tidy 26 | - rm -rf bench.txt 27 | 28 | benchmark-vtproto: benchmark-vtproto-cmp 29 | - go run -mod=mod golang.org/x/perf/cmd/benchstat bench.txt 30 | - go mod tidy 31 | - rm -rf bench.txt 32 | 33 | benchmark-vtproto-cmp: 34 | - go test -bench=. -timeout=24h -count=$(BENCHCOUNT) ./... -tags=vtproto -test.short | tee bench.txt 35 | 36 | benchmark-vtproto-long: 37 | - go test -bench=. -timeout=24h -count=$(BENCHCOUNT) ./... -tags=vtproto | tee bench.txt 38 | - go run -mod=mod golang.org/x/perf/cmd/benchstat bench.txt 39 | - go mod tidy 40 | - rm -rf bench.txt 41 | 42 | leaks: 43 | - go test -bench=. -gcflags="-m=2" ./... -------------------------------------------------------------------------------- /v2/benchmarks/bench.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./benchmark"; 4 | 5 | message BytesData { 6 | bytes bytes = 1; 7 | } 8 | 9 | message I32Data { 10 | int32 i32 = 1; 11 | } 12 | 13 | message U32Data { 14 | uint32 u32 = 1; 15 | } 16 | 17 | message I64Data { 18 | int64 i64 = 1; 19 | } 20 | 21 | message U64Data { 22 | uint64 u64 = 1; 23 | } 24 | 25 | service Benchmark {} 26 | -------------------------------------------------------------------------------- /v2/benchmarks/go.mod: -------------------------------------------------------------------------------- 1 | module benchmark 2 | 3 | go 1.21.4 4 | toolchain go1.24.1 5 | 6 | replace github.com/loopholelabs/polyglot/v2 => ../ 7 | 8 | require ( 9 | github.com/loopholelabs/polyglot/v2 v2.0.2 10 | google.golang.org/grpc v1.66.0 11 | google.golang.org/protobuf v1.36.5 12 | ) 13 | 14 | require ( 15 | golang.org/x/net v0.36.0 // indirect 16 | golang.org/x/sys v0.30.0 // indirect 17 | golang.org/x/text v0.22.0 // indirect; indirect.. 18 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /v2/benchmarks/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 4 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= 10 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 11 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 12 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 13 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 14 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= 16 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 17 | google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= 18 | google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 19 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 20 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 21 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 22 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 23 | -------------------------------------------------------------------------------- /v2/benchmarks/polyglot/benchmark/bench.polyglot.go: -------------------------------------------------------------------------------- 1 | // Code generated by polyglot v1.1.4, DO NOT EDIT. 2 | // source: bench.proto 3 | 4 | package benchmark 5 | 6 | import ( 7 | "errors" 8 | "github.com/loopholelabs/polyglot/v2" 9 | ) 10 | 11 | var ( 12 | NilDecode = errors.New("cannot decode into a nil root struct") 13 | ) 14 | 15 | type BytesData struct { 16 | Bytes []byte 17 | } 18 | 19 | func NewBytesData() *BytesData { 20 | return &BytesData{} 21 | } 22 | 23 | func (x *BytesData) Error(b *polyglot.Buffer, err error) { 24 | polyglot.Encoder(b).Error(err) 25 | } 26 | 27 | func (x *BytesData) Encode(b *polyglot.Buffer) { 28 | if x == nil { 29 | polyglot.Encoder(b).Nil() 30 | } else { 31 | 32 | polyglot.Encoder(b).Bytes(x.Bytes) 33 | } 34 | } 35 | 36 | func (x *BytesData) Decode(b []byte) error { 37 | if x == nil { 38 | return NilDecode 39 | } 40 | return x.decode(polyglot.Decoder(b)) 41 | } 42 | 43 | func (x *BytesData) decode(d *polyglot.BufferDecoder) error { 44 | if d.Nil() { 45 | return nil 46 | } 47 | 48 | var err error 49 | 50 | x.Bytes, err = d.Bytes(x.Bytes) 51 | if err != nil { 52 | return err 53 | } 54 | return nil 55 | } 56 | 57 | type I32Data struct { 58 | I32 int32 59 | } 60 | 61 | func NewI32Data() *I32Data { 62 | return &I32Data{} 63 | } 64 | 65 | func (x *I32Data) Error(b *polyglot.Buffer, err error) { 66 | polyglot.Encoder(b).Error(err) 67 | } 68 | 69 | func (x *I32Data) Encode(b *polyglot.Buffer) { 70 | if x == nil { 71 | polyglot.Encoder(b).Nil() 72 | } else { 73 | 74 | polyglot.Encoder(b).Int32(x.I32) 75 | } 76 | } 77 | 78 | func (x *I32Data) Decode(b []byte) error { 79 | if x == nil { 80 | return NilDecode 81 | } 82 | return x.decode(polyglot.Decoder(b)) 83 | } 84 | 85 | func (x *I32Data) decode(d *polyglot.BufferDecoder) error { 86 | if d.Nil() { 87 | return nil 88 | } 89 | 90 | var err error 91 | 92 | x.I32, err = d.Int32() 93 | if err != nil { 94 | return err 95 | } 96 | return nil 97 | } 98 | 99 | type U32Data struct { 100 | U32 uint32 101 | } 102 | 103 | func NewU32Data() *U32Data { 104 | return &U32Data{} 105 | } 106 | 107 | func (x *U32Data) Error(b *polyglot.Buffer, err error) { 108 | polyglot.Encoder(b).Error(err) 109 | } 110 | 111 | func (x *U32Data) Encode(b *polyglot.Buffer) { 112 | if x == nil { 113 | polyglot.Encoder(b).Nil() 114 | } else { 115 | 116 | polyglot.Encoder(b).Uint32(x.U32) 117 | } 118 | } 119 | 120 | func (x *U32Data) Decode(b []byte) error { 121 | if x == nil { 122 | return NilDecode 123 | } 124 | return x.decode(polyglot.Decoder(b)) 125 | } 126 | 127 | func (x *U32Data) decode(d *polyglot.BufferDecoder) error { 128 | if d.Nil() { 129 | return nil 130 | } 131 | 132 | var err error 133 | 134 | x.U32, err = d.Uint32() 135 | if err != nil { 136 | return err 137 | } 138 | return nil 139 | } 140 | 141 | type I64Data struct { 142 | I64 int64 143 | } 144 | 145 | func NewI64Data() *I64Data { 146 | return &I64Data{} 147 | } 148 | 149 | func (x *I64Data) Error(b *polyglot.Buffer, err error) { 150 | polyglot.Encoder(b).Error(err) 151 | } 152 | 153 | func (x *I64Data) Encode(b *polyglot.Buffer) { 154 | if x == nil { 155 | polyglot.Encoder(b).Nil() 156 | } else { 157 | 158 | polyglot.Encoder(b).Int64(x.I64) 159 | } 160 | } 161 | 162 | func (x *I64Data) Decode(b []byte) error { 163 | if x == nil { 164 | return NilDecode 165 | } 166 | return x.decode(polyglot.Decoder(b)) 167 | } 168 | 169 | func (x *I64Data) decode(d *polyglot.BufferDecoder) error { 170 | if d.Nil() { 171 | return nil 172 | } 173 | 174 | var err error 175 | 176 | x.I64, err = d.Int64() 177 | if err != nil { 178 | return err 179 | } 180 | return nil 181 | } 182 | 183 | type U64Data struct { 184 | U64 uint64 185 | } 186 | 187 | func NewU64Data() *U64Data { 188 | return &U64Data{} 189 | } 190 | 191 | func (x *U64Data) Error(b *polyglot.Buffer, err error) { 192 | polyglot.Encoder(b).Error(err) 193 | } 194 | 195 | func (x *U64Data) Encode(b *polyglot.Buffer) { 196 | if x == nil { 197 | polyglot.Encoder(b).Nil() 198 | } else { 199 | 200 | polyglot.Encoder(b).Uint64(x.U64) 201 | } 202 | } 203 | 204 | func (x *U64Data) Decode(b []byte) error { 205 | if x == nil { 206 | return NilDecode 207 | } 208 | return x.decode(polyglot.Decoder(b)) 209 | } 210 | 211 | func (x *U64Data) decode(d *polyglot.BufferDecoder) error { 212 | if d.Nil() { 213 | return nil 214 | } 215 | 216 | var err error 217 | 218 | x.U64, err = d.Uint64() 219 | if err != nil { 220 | return err 221 | } 222 | return nil 223 | } 224 | -------------------------------------------------------------------------------- /v2/buffer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | const ( 20 | defaultSize = 512 21 | ) 22 | 23 | type Buffer struct { 24 | b []byte 25 | offset int 26 | } 27 | 28 | func NewBuffer() *Buffer { 29 | return &Buffer{ 30 | b: make([]byte, defaultSize), 31 | offset: 0, 32 | } 33 | } 34 | 35 | func NewBufferSize(size int) *Buffer { 36 | return &Buffer{ 37 | b: make([]byte, size), 38 | offset: 0, 39 | } 40 | } 41 | 42 | func NewBufferFromBytes(b []byte) *Buffer { 43 | return &Buffer{ 44 | b: b, 45 | offset: 0, 46 | } 47 | } 48 | 49 | func (buf *Buffer) Reset() { 50 | buf.offset = 0 51 | } 52 | 53 | func (buf *Buffer) MoveOffset(offset int) { 54 | buf.offset += offset 55 | } 56 | 57 | func (buf *Buffer) Grow(n int) { 58 | if cap(buf.b)-buf.offset < n { 59 | if cap(buf.b) < n { 60 | buf.b = append(buf.b[:buf.offset], make([]byte, n+cap(buf.b)-buf.offset)...) 61 | } else { 62 | buf.b = append(buf.b[:buf.offset], make([]byte, cap(buf.b)*2-buf.offset)...) 63 | } 64 | // Grow buffer length to match its new capacity. 65 | buf.b = buf.b[:cap(buf.b)] 66 | } 67 | } 68 | 69 | func (buf *Buffer) Write(b []byte) int { 70 | buf.Grow(len(b)) 71 | buf.offset += copy(buf.b[buf.offset:cap(buf.b)], b) 72 | return len(b) 73 | } 74 | 75 | func (buf *Buffer) Bytes() []byte { 76 | return buf.b[:buf.offset] 77 | } 78 | 79 | func (buf *Buffer) Len() int { 80 | return buf.offset 81 | } 82 | 83 | func (buf *Buffer) Cap() int { 84 | return cap(buf.b) 85 | } 86 | -------------------------------------------------------------------------------- /v2/decoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | type BufferDecoder []byte 20 | 21 | func Decoder(b []byte) *BufferDecoder { 22 | c := (BufferDecoder)(b) 23 | return &c 24 | } 25 | 26 | func (d *BufferDecoder) Nil() (value bool) { 27 | *d, value = decodeNil(*d) 28 | return 29 | } 30 | 31 | func (d *BufferDecoder) Map(keyKind, valueKind Kind) (size uint32, err error) { 32 | *d, size, err = decodeMap(*d, keyKind, valueKind) 33 | return 34 | } 35 | 36 | func (d *BufferDecoder) Slice(kind Kind) (size uint32, err error) { 37 | *d, size, err = decodeSlice(*d, kind) 38 | return 39 | } 40 | 41 | func (d *BufferDecoder) Bytes(b []byte) (value []byte, err error) { 42 | *d, value, err = decodeBytes(*d, b) 43 | return 44 | } 45 | 46 | func (d *BufferDecoder) String() (value string, err error) { 47 | *d, value, err = decodeString(*d) 48 | return 49 | } 50 | 51 | func (d *BufferDecoder) Error() (value, err error) { 52 | *d, value, err = decodeError(*d) 53 | return 54 | } 55 | 56 | func (d *BufferDecoder) Bool() (value bool, err error) { 57 | *d, value, err = decodeBool(*d) 58 | return 59 | } 60 | 61 | func (d *BufferDecoder) Uint8() (value uint8, err error) { 62 | *d, value, err = decodeUint8(*d) 63 | return 64 | } 65 | 66 | func (d *BufferDecoder) Uint16() (value uint16, err error) { 67 | *d, value, err = decodeUint16(*d) 68 | return 69 | } 70 | 71 | func (d *BufferDecoder) Uint32() (value uint32, err error) { 72 | *d, value, err = decodeUint32(*d) 73 | return 74 | } 75 | 76 | func (d *BufferDecoder) Uint64() (value uint64, err error) { 77 | *d, value, err = decodeUint64(*d) 78 | return 79 | } 80 | 81 | func (d *BufferDecoder) Int32() (value int32, err error) { 82 | *d, value, err = decodeInt32(*d) 83 | return 84 | } 85 | 86 | func (d *BufferDecoder) Int64() (value int64, err error) { 87 | *d, value, err = decodeInt64(*d) 88 | return 89 | } 90 | 91 | func (d *BufferDecoder) Float32() (value float32, err error) { 92 | *d, value, err = decodeFloat32(*d) 93 | return 94 | } 95 | 96 | func (d *BufferDecoder) Float64() (value float64, err error) { 97 | *d, value, err = decodeFloat64(*d) 98 | return 99 | } 100 | -------------------------------------------------------------------------------- /v2/encoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | type BufferEncoder Buffer 20 | 21 | func Encoder(b *Buffer) *BufferEncoder { 22 | return (*BufferEncoder)(b) 23 | } 24 | 25 | func (e *BufferEncoder) Nil() *BufferEncoder { 26 | encodeNil((*Buffer)(e)) 27 | return e 28 | } 29 | 30 | func (e *BufferEncoder) Map(size uint32, keyKind, valueKind Kind) *BufferEncoder { 31 | encodeMap((*Buffer)(e), size, keyKind, valueKind) 32 | return e 33 | } 34 | 35 | func (e *BufferEncoder) Slice(size uint32, kind Kind) *BufferEncoder { 36 | encodeSlice((*Buffer)(e), size, kind) 37 | return e 38 | } 39 | 40 | func (e *BufferEncoder) Bytes(value []byte) *BufferEncoder { 41 | encodeBytes((*Buffer)(e), value) 42 | return e 43 | } 44 | 45 | func (e *BufferEncoder) String(value string) *BufferEncoder { 46 | encodeString((*Buffer)(e), value) 47 | return e 48 | } 49 | 50 | func (e *BufferEncoder) Error(value error) *BufferEncoder { 51 | encodeError((*Buffer)(e), value) 52 | return e 53 | } 54 | 55 | func (e *BufferEncoder) Bool(value bool) *BufferEncoder { 56 | encodeBool((*Buffer)(e), value) 57 | return e 58 | } 59 | 60 | func (e *BufferEncoder) Uint8(value uint8) *BufferEncoder { 61 | encodeUint8((*Buffer)(e), value) 62 | return e 63 | } 64 | 65 | func (e *BufferEncoder) Uint16(value uint16) *BufferEncoder { 66 | encodeUint16((*Buffer)(e), value) 67 | return e 68 | } 69 | 70 | func (e *BufferEncoder) Uint32(value uint32) *BufferEncoder { 71 | encodeUint32((*Buffer)(e), value) 72 | return e 73 | } 74 | 75 | func (e *BufferEncoder) Uint64(value uint64) *BufferEncoder { 76 | encodeUint64((*Buffer)(e), value) 77 | return e 78 | } 79 | 80 | func (e *BufferEncoder) Int32(value int32) *BufferEncoder { 81 | encodeInt32((*Buffer)(e), value) 82 | return e 83 | } 84 | 85 | func (e *BufferEncoder) Int64(value int64) *BufferEncoder { 86 | encodeInt64((*Buffer)(e), value) 87 | return e 88 | } 89 | 90 | func (e *BufferEncoder) Float32(value float32) *BufferEncoder { 91 | encodeFloat32((*Buffer)(e), value) 92 | return e 93 | } 94 | 95 | func (e *BufferEncoder) Float64(value float64) *BufferEncoder { 96 | encodeFloat64((*Buffer)(e), value) 97 | return e 98 | } 99 | -------------------------------------------------------------------------------- /v2/error.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | type Error string 20 | 21 | func (e Error) Error() string { 22 | return string(e) 23 | } 24 | 25 | func (e Error) Is(err error) bool { 26 | return e.Error() == err.Error() 27 | } 28 | -------------------------------------------------------------------------------- /v2/generator/golang/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | const ( 20 | Extension = ".polyglot.go" 21 | Pointer = "*" 22 | MapSuffix = "Map" 23 | Slice = "[]" 24 | PolyglotAnyKind = "polyglot.AnyKind" 25 | ) 26 | -------------------------------------------------------------------------------- /v2/generator/golang/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/v2/generator/golang/templates" 21 | "github.com/loopholelabs/polyglot/v2/utils" 22 | "github.com/loopholelabs/polyglot/v2/version" 23 | 24 | "google.golang.org/protobuf/compiler/protogen" 25 | "google.golang.org/protobuf/proto" 26 | "google.golang.org/protobuf/types/pluginpb" 27 | 28 | "text/template" 29 | ) 30 | 31 | type Generator struct { 32 | options *protogen.Options 33 | templ *template.Template 34 | CustomFields func() string 35 | CustomEncode func() string 36 | CustomDecode func() string 37 | } 38 | 39 | func New() *Generator { 40 | var g *Generator 41 | templ := template.Must(template.New("main").Funcs(template.FuncMap{ 42 | "CamelCase": utils.CamelCaseFullName, 43 | "CamelCaseName": utils.CamelCaseName, 44 | "MakeIterable": utils.MakeIterable, 45 | "Counter": utils.Counter, 46 | "FirstLowerCase": utils.FirstLowerCase, 47 | "FirstLowerCaseName": utils.FirstLowerCaseName, 48 | "FindValue": FindValue, 49 | "GetKind": GetKind, 50 | "GetLUTEncoder": GetLUTEncoder, 51 | "GetLUTDecoder": GetLUTDecoder, 52 | "GetEncodingFields": GetEncodingFields, 53 | "GetDecodingFields": GetDecodingFields, 54 | "GetKindLUT": GetKindLUT, 55 | "CustomFields": func() string { 56 | return g.CustomFields() 57 | }, 58 | "CustomEncode": func() string { 59 | return g.CustomEncode() 60 | }, 61 | "CustomDecode": func() string { 62 | return g.CustomDecode() 63 | }, 64 | }).ParseFS(templates.FS, "*")) 65 | g = &Generator{ 66 | options: &protogen.Options{ 67 | ParamFunc: func(name string, value string) error { return nil }, 68 | ImportRewriteFunc: func(path protogen.GoImportPath) protogen.GoImportPath { return path }, 69 | }, 70 | templ: templ, 71 | CustomEncode: func() string { return "" }, 72 | CustomDecode: func() string { return "" }, 73 | CustomFields: func() string { return "" }, 74 | } 75 | return g 76 | } 77 | 78 | func (*Generator) UnmarshalRequest(buf []byte) (*pluginpb.CodeGeneratorRequest, error) { 79 | req := new(pluginpb.CodeGeneratorRequest) 80 | return req, proto.Unmarshal(buf, req) 81 | } 82 | 83 | func (*Generator) MarshalResponse(res *pluginpb.CodeGeneratorResponse) ([]byte, error) { 84 | return proto.Marshal(res) 85 | } 86 | 87 | func (g *Generator) Generate(req *pluginpb.CodeGeneratorRequest) (res *pluginpb.CodeGeneratorResponse, err error) { 88 | plugin, err := g.options.New(req) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | for _, f := range plugin.Files { 94 | if !f.Generate { 95 | continue 96 | } 97 | genFile := plugin.NewGeneratedFile(FileName(f.GeneratedFilenamePrefix), f.GoImportPath) 98 | 99 | packageName := string(f.Desc.Package().Name()) 100 | if packageName == "" { 101 | packageName = string(f.GoPackageName) 102 | } 103 | 104 | err = g.ExecuteTemplate(genFile, f, packageName, true) 105 | if err != nil { 106 | return nil, err 107 | } 108 | } 109 | 110 | return plugin.Response(), nil 111 | } 112 | 113 | func (g *Generator) ExecuteTemplate( 114 | genFile *protogen.GeneratedFile, 115 | protoFile *protogen.File, 116 | packageName string, 117 | header bool, 118 | ) error { 119 | return g.templ.ExecuteTemplate(genFile, "base.templ", map[string]interface{}{ 120 | "pluginVersion": version.Version(), 121 | "sourcePath": protoFile.Desc.Path(), 122 | "package": packageName, 123 | "requiredImports": RequiredImports, 124 | "enums": protoFile.Desc.Enums(), 125 | "messages": protoFile.Desc.Messages(), 126 | "header": header, 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /v2/generator/golang/headers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/v2/utils" 21 | ) 22 | 23 | func FileName(name string) string { 24 | return utils.AppendString(name, Extension) 25 | } 26 | -------------------------------------------------------------------------------- /v2/generator/golang/imports.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package golang 18 | 19 | var ( 20 | RequiredImports = []string{ 21 | "github.com/loopholelabs/polyglot/v2", 22 | "errors", 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /v2/generator/golang/templates/base.templ: -------------------------------------------------------------------------------- 1 | {{ if .header -}} 2 | {{template "headers" .}} 3 | 4 | {{template "imports" .}} 5 | {{ end -}} 6 | 7 | {{template "errors" .}} 8 | 9 | {{template "enums" .}} 10 | 11 | {{template "messages" .}} 12 | -------------------------------------------------------------------------------- /v2/generator/golang/templates/decode.templ: -------------------------------------------------------------------------------- 1 | {{define "decode"}} 2 | func (x *{{ CamelCase .FullName }}) Decode (b []byte) error { 3 | if x == nil { 4 | return ErrDecodeNil 5 | } 6 | return x.decode(polyglot.Decoder(b)) 7 | } 8 | {{end}} 9 | 10 | {{define "internalDecode"}} 11 | func (x *{{CamelCase .FullName}}) decode(d *polyglot.BufferDecoder) error { 12 | if d.Nil() { 13 | return nil 14 | } 15 | 16 | {{ $decoding := GetDecodingFields .Fields -}} 17 | {{ $customDecode := CustomDecode -}} 18 | {{ if or $customDecode $decoding.Other $decoding.SliceFields $decoding.MessageFields -}} 19 | var err error 20 | {{ end -}} 21 | {{ $customDecode }} 22 | {{ range $field := $decoding.Other -}} 23 | {{ $decoder := GetLUTDecoder $field.Kind -}} 24 | {{ if eq $field.Kind 12 -}} {{/* protoreflect.BytesKind */ -}} 25 | x.{{ CamelCaseName $field.Name }}, err = d{{ $decoder }}(x.{{ CamelCaseName $field.Name }}) 26 | {{ else if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 27 | var {{ CamelCaseName $field.Name }}Temp uint32 28 | {{ CamelCaseName $field.Name }}Temp, err = d{{ $decoder }}() 29 | x.{{ CamelCaseName $field.Name }} = {{ FindValue $field }}({{ CamelCaseName $field.Name }}Temp) 30 | {{ else -}} 31 | x.{{ CamelCaseName $field.Name }}, err = d{{ $decoder }}() 32 | {{end -}} 33 | if err != nil { 34 | return err 35 | } 36 | {{end -}} 37 | 38 | {{ if $decoding.SliceFields -}} 39 | var sliceSize uint32 40 | {{end -}} 41 | {{ range $field := $decoding.SliceFields -}} 42 | {{ $kind := GetKind $field.Kind -}} 43 | sliceSize, err = d.Slice({{ $kind }}) 44 | if err != nil { 45 | return err 46 | } 47 | if uint32(len(x.{{ CamelCaseName $field.Name }})) != sliceSize { 48 | x.{{ CamelCaseName $field.Name }} = make({{ FindValue $field }}, sliceSize) 49 | } 50 | for i := uint32(0); i < sliceSize; i++ { 51 | {{ $decoder := GetLUTDecoder $field.Kind -}} 52 | {{ if eq $field.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 53 | if x.{{ CamelCaseName $field.Name }}[i] == nil { 54 | x.{{ CamelCaseName $field.Name }}[i] = New{{ CamelCase $field.Message.FullName }}() 55 | } 56 | err = x.{{ CamelCaseName $field.Name }}[i].decode(d) 57 | {{ else -}} 58 | x.{{ CamelCaseName $field.Name }}[i], err = d{{ $decoder }}() 59 | {{end -}} 60 | if err != nil { 61 | return err 62 | } 63 | } 64 | {{end -}} 65 | {{ range $field := $decoding.MessageFields -}} 66 | {{ if $field.IsMap -}} 67 | if !d.Nil() { 68 | {{ $keyKind := GetKind $field.MapKey.Kind -}} 69 | {{ $valKind := GetKind $field.MapValue.Kind -}} 70 | 71 | {{ CamelCaseName $field.Name }}Size, err := d.Map({{ $keyKind }}, {{ $valKind }}) 72 | if err != nil { 73 | return err 74 | } 75 | x.{{ CamelCaseName $field.Name }} = New{{ CamelCase $field.FullName }}Map({{ CamelCaseName $field.Name }}Size) 76 | err = x.{{ CamelCaseName $field.Name }}.decode(d, {{ CamelCaseName $field.Name }}Size) 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | {{ else -}} 82 | if !d.Nil() { 83 | x.{{ CamelCaseName $field.Name }} = New{{ CamelCase $field.Message.FullName }}() 84 | err = x.{{ CamelCaseName $field.Name }}.decode(d) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | {{end -}} 90 | {{end -}} 91 | return nil 92 | } 93 | {{end}} 94 | -------------------------------------------------------------------------------- /v2/generator/golang/templates/decodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "decodeMap"}} 2 | func (x {{CamelCase .FullName}}Map) decode(d *polyglot.BufferDecoder, size uint32) error { 3 | if size == 0 { 4 | return nil 5 | } 6 | var k {{ FindValue .MapKey }} 7 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 8 | var {{ CamelCase .MapKey.Name }}Temp uint32 9 | {{end -}} 10 | var v {{ FindValue .MapValue }} 11 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 12 | var {{ CamelCaseName .MapValue.Name }}Temp uint32 13 | {{end -}} 14 | var err error 15 | for i := uint32(0); i < size; i++ { 16 | {{ $keyDecoder := GetLUTDecoder .MapKey.Kind -}} 17 | {{ if and (eq $keyDecoder "") (eq .MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 18 | k = New{{ CamelCase .MapKey.Message.FullName }}() 19 | err = k.decode(d) 20 | {{else -}} 21 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 22 | {{ CamelCase .MapKey.Name }}Temp, err = d{{$keyDecoder}}() 23 | k = {{ FindValue .MapKey }}({{ CamelCase .MapKey.Name }}Temp) 24 | {{else -}} 25 | k, err = d{{$keyDecoder}}() 26 | {{end -}} 27 | {{end -}} 28 | if err != nil { 29 | return err 30 | } 31 | {{ $valDecoder := GetLUTDecoder .MapValue.Kind -}} 32 | {{ if and (eq $valDecoder "") (eq .MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 33 | v = New{{ CamelCase .MapValue.Message.FullName }}() 34 | err = v.decode(d) 35 | {{else -}} 36 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 37 | {{CamelCaseName .MapValue.Name}}Temp, err = d{{$valDecoder}}() 38 | v = {{ FindValue .MapValue }}({{ CamelCaseName .MapValue.Name }}Temp) 39 | {{else -}} 40 | v, err = d{{$valDecoder}}() 41 | {{end -}} 42 | {{end -}} 43 | 44 | if err != nil { 45 | return err 46 | } 47 | x[k] = v 48 | } 49 | return nil 50 | } 51 | {{end}} -------------------------------------------------------------------------------- /v2/generator/golang/templates/encode.templ: -------------------------------------------------------------------------------- 1 | {{define "encode"}} 2 | func (x *{{ CamelCase .FullName }}) Encode (b *polyglot.Buffer) { 3 | if x == nil { 4 | polyglot.Encoder(b).Nil() 5 | } else { 6 | {{ CustomEncode }} 7 | {{ $encoding := GetEncodingFields .Fields -}} 8 | {{ if $encoding.Values -}} 9 | polyglot.Encoder(b){{ range $val := $encoding.Values -}}{{ $val -}}{{end -}} 10 | {{ end -}} 11 | {{ if $encoding.SliceFields -}} 12 | {{template "encodeSlices" $encoding -}} 13 | {{end -}} 14 | {{ if $encoding.MessageFields -}} 15 | {{template "encodeMessages" $encoding -}} 16 | {{end -}} 17 | } 18 | } 19 | {{end}} 20 | 21 | {{define "encodeSlices"}} 22 | {{ range $field := .SliceFields -}} 23 | {{ $encoder := GetLUTEncoder $field.Kind -}} 24 | {{ if and (eq $encoder "") (eq $field.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 25 | polyglot.Encoder(b).Slice(uint32(len(x.{{ CamelCaseName $field.Name }})), polyglot.AnyKind) 26 | for _, v := range x.{{CamelCaseName $field.Name}} { 27 | v.Encode(b) 28 | } 29 | {{else -}} 30 | polyglot.Encoder(b).Slice(uint32(len(x.{{ CamelCaseName $field.Name }})), {{ GetKindLUT $field.Kind }}) 31 | for _, v := range x.{{ CamelCaseName $field.Name }} { 32 | polyglot.Encoder(b){{$encoder}}(v) 33 | } 34 | {{end -}} 35 | {{end -}} 36 | {{end}} 37 | 38 | {{define "encodeMessages"}} 39 | {{ range $field := .MessageFields -}} 40 | x.{{ CamelCaseName $field.Name }}.Encode(b) 41 | {{end -}} 42 | {{end}} -------------------------------------------------------------------------------- /v2/generator/golang/templates/encodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "encodeMap"}} 2 | func (x {{ CamelCase .FullName }}Map) Encode (b *polyglot.Buffer) { 3 | {{ $keyKind := GetKind .MapKey.Kind -}} 4 | {{ $valKind := GetKind .MapValue.Kind -}} 5 | 6 | if x == nil { 7 | polyglot.Encoder(b).Map(0, {{$keyKind}}, {{$valKind}}) 8 | } else { 9 | polyglot.Encoder(b).Map(uint32(len(x)), {{$keyKind}}, {{$valKind}}) 10 | for k, v := range x { 11 | {{ $keyEncoder := GetLUTEncoder .MapKey.Kind -}} 12 | {{ if and (eq $keyEncoder "") (eq .MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 13 | k.Encode(b) 14 | {{else -}} 15 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 16 | polyglot.Encoder(b) {{$keyEncoder}} (uint32(k)) 17 | {{else -}} 18 | polyglot.Encoder(b) {{$keyEncoder}} (k) 19 | {{end -}} 20 | {{end -}} 21 | {{ $valEncoder := GetLUTEncoder .MapValue.Kind -}} 22 | {{ if and (eq $valEncoder "") (eq .MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 23 | v.Encode(b) 24 | {{else -}} 25 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 26 | polyglot.Encoder(b) {{$valEncoder}} (uint32(v)) 27 | {{else -}} 28 | polyglot.Encoder(b) {{$valEncoder}} (v) 29 | {{end -}} 30 | {{end -}} 31 | } 32 | } 33 | } 34 | {{end}} 35 | -------------------------------------------------------------------------------- /v2/generator/golang/templates/enums.templ: -------------------------------------------------------------------------------- 1 | {{define "enums"}} 2 | {{range $i, $e := (MakeIterable .enums.Len) -}} 3 | {{ $enum := ($.enums.Get $i) }} 4 | {{template "enum" $enum}} 5 | {{end -}} 6 | {{end}} 7 | 8 | {{define "enum"}} 9 | {{ $enumName := (CamelCase $.FullName) }} 10 | type {{ $enumName }} uint32 11 | 12 | const ( 13 | {{range $i, $v := (MakeIterable $.Values.Len) -}} 14 | {{ $val := ($.Values.Get $i) -}} 15 | {{CamelCase $val.FullName}} = {{ $enumName }}({{ $i }}) 16 | {{end -}} 17 | ) 18 | {{end}} 19 | 20 | -------------------------------------------------------------------------------- /v2/generator/golang/templates/errors.templ: -------------------------------------------------------------------------------- 1 | {{define "errors"}} 2 | var ( 3 | ErrDecodeNil = errors.New("cannot decode into a nil root struct") 4 | ) 5 | {{end}} 6 | -------------------------------------------------------------------------------- /v2/generator/golang/templates/headers.templ: -------------------------------------------------------------------------------- 1 | {{define "headers"}} 2 | // Code generated by polyglot {{ .pluginVersion }}, DO NOT EDIT. 3 | // source: {{ .sourcePath }} 4 | 5 | package {{ .package }} 6 | {{end}} -------------------------------------------------------------------------------- /v2/generator/golang/templates/imports.templ: -------------------------------------------------------------------------------- 1 | {{define "imports"}} 2 | import ( 3 | {{range $im := .requiredImports -}} 4 | "{{$im}}" 5 | {{end -}} 6 | ) 7 | {{end}} -------------------------------------------------------------------------------- /v2/generator/golang/templates/messages.templ: -------------------------------------------------------------------------------- 1 | {{define "messages"}} 2 | {{range $i, $e := (MakeIterable .messages.Len) -}} 3 | {{ $message := $.messages.Get $i }} 4 | {{range $i, $e := (MakeIterable $message.Enums.Len) -}} 5 | {{ $enum := ($message.Enums.Get $i) }} 6 | {{template "enum" $enum}} 7 | {{end}} 8 | {{template "structs" $message}} 9 | {{end}} 10 | {{end}} -------------------------------------------------------------------------------- /v2/generator/golang/templates/structs.templ: -------------------------------------------------------------------------------- 1 | {{define "structs"}} 2 | {{ range $i, $v := (MakeIterable $.Messages.Len) }} 3 | {{ $message := $.Messages.Get $i }} 4 | {{ if not $message.IsMapEntry }} 5 | {{template "structs" $message}} 6 | {{end}} 7 | {{end}} 8 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 9 | {{ $field := $.Fields.Get $i }} 10 | {{ if $field.IsMap }} 11 | {{ $mapKeyValue := FindValue $field.MapKey }} 12 | {{ $mapValueValue := FindValue $field.MapValue }} 13 | type {{ CamelCase $field.FullName }}Map map[{{ $mapKeyValue }}]{{ $mapValueValue }} 14 | func New{{ CamelCase $field.FullName }}Map (size uint32) map[{{ $mapKeyValue }}]{{$mapValueValue}} { 15 | return make(map[{{ $mapKeyValue }}]{{ $mapValueValue }}, size) 16 | } 17 | 18 | {{template "encodeMap" $field}} 19 | {{template "decodeMap" $field}} 20 | {{end}} 21 | {{end -}} 22 | type {{ CamelCase .FullName }} struct { 23 | {{ CustomFields }} 24 | 25 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 26 | {{ $field := $.Fields.Get $i -}} 27 | {{ $value := FindValue $field -}} 28 | {{ CamelCaseName $field.Name }} {{ $value }} 29 | {{end -}} 30 | } 31 | 32 | {{template "getFunc" .}} 33 | {{template "error" .}} 34 | {{template "encode" .}} 35 | {{template "decode" .}} 36 | {{template "internalDecode" .}} 37 | {{end}} 38 | 39 | {{define "getFunc"}} 40 | func New{{ CamelCase .FullName }}() *{{ CamelCase .FullName }} { 41 | return &{{ CamelCase .FullName }}{} 42 | } 43 | {{end}} 44 | 45 | {{define "error"}} 46 | func (x *{{CamelCase .FullName}}) Error(b *polyglot.Buffer, err error) { 47 | polyglot.Encoder(b).Error(err) 48 | } 49 | {{end}} 50 | -------------------------------------------------------------------------------- /v2/generator/golang/templates/templates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | import ( 20 | "embed" 21 | ) 22 | 23 | //go:embed * 24 | var FS embed.FS 25 | -------------------------------------------------------------------------------- /v2/generator/rust/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | const ( 20 | extension = "_polyglot.rs" 21 | polyglotAnyKind = "Kind::Any" 22 | ) 23 | -------------------------------------------------------------------------------- /v2/generator/rust/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | type File interface { 20 | P(v ...interface{}) 21 | } 22 | -------------------------------------------------------------------------------- /v2/generator/rust/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/v2/generator/rust/templates" 21 | "github.com/loopholelabs/polyglot/v2/utils" 22 | "github.com/loopholelabs/polyglot/v2/version" 23 | 24 | "google.golang.org/protobuf/compiler/protogen" 25 | "google.golang.org/protobuf/proto" 26 | "google.golang.org/protobuf/types/pluginpb" 27 | 28 | "bytes" 29 | "flag" 30 | "os/exec" 31 | "text/template" 32 | ) 33 | 34 | type GeneratedFieldPrivacy string 35 | 36 | const ( 37 | GeneratedFieldPrivacyPrivate = "private" 38 | GeneratedFieldPrivacyPublic = "public" 39 | GeneratedFieldPrivacyCrate = "crate" 40 | ) 41 | 42 | type Generator struct { 43 | options *protogen.Options 44 | templ *template.Template 45 | CustomFields func() string 46 | CustomEncode func() string 47 | CustomDecode func() string 48 | } 49 | 50 | func New() *Generator { 51 | var g *Generator 52 | 53 | var flags flag.FlagSet 54 | privacy := flags.String("privacy", GeneratedFieldPrivacyPrivate, "Privacy of generated fields (private, public, crate)") 55 | 56 | templ := template.Must(template.New("main").Funcs(template.FuncMap{ 57 | "CamelCase": utils.CamelCaseFullName, 58 | "CamelCaseName": utils.CamelCaseName, 59 | "MakeIterable": utils.MakeIterable, 60 | "Counter": utils.Counter, 61 | "FirstLowerCase": utils.FirstLowerCase, 62 | "FirstLowerCaseName": utils.FirstLowerCaseName, 63 | "FindValue": findValue, 64 | "GetKind": getKind, 65 | "GetLUTEncoder": getLUTEncoder, 66 | "GetLUTDecoder": getLUTDecoder, 67 | "GetEncodingFields": getEncodingFields, 68 | "GetDecodingFields": getDecodingFields, 69 | "GetKindLUT": getKindLUT, 70 | "SnakeCase": utils.SnakeCase, 71 | "SnakeCaseName": utils.SnakeCaseName, 72 | "CustomFields": func() string { 73 | return g.CustomFields() 74 | }, 75 | "CustomEncode": func() string { 76 | return g.CustomEncode() 77 | }, 78 | "CustomDecode": func() string { 79 | return g.CustomDecode() 80 | }, 81 | "GeneratedFieldPrivacy": func() GeneratedFieldPrivacy { 82 | return GeneratedFieldPrivacy(*privacy) 83 | }, 84 | }).ParseFS(templates.FS, "*")) 85 | 86 | g = &Generator{ 87 | options: &protogen.Options{ 88 | ParamFunc: flags.Set, 89 | ImportRewriteFunc: func(path protogen.GoImportPath) protogen.GoImportPath { return path }, 90 | }, 91 | templ: templ, 92 | CustomEncode: func() string { return "" }, 93 | CustomDecode: func() string { return "" }, 94 | CustomFields: func() string { return "" }, 95 | } 96 | return g 97 | } 98 | 99 | func (*Generator) UnmarshalRequest(buf []byte) (*pluginpb.CodeGeneratorRequest, error) { 100 | req := new(pluginpb.CodeGeneratorRequest) 101 | return req, proto.Unmarshal(buf, req) 102 | } 103 | 104 | func (*Generator) MarshalResponse(res *pluginpb.CodeGeneratorResponse) ([]byte, error) { 105 | return proto.Marshal(res) 106 | } 107 | 108 | func (g *Generator) Generate(req *pluginpb.CodeGeneratorRequest) (res *pluginpb.CodeGeneratorResponse, err error) { 109 | plugin, err := g.options.New(req) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | for _, f := range plugin.Files { 115 | if !f.Generate { 116 | continue 117 | } 118 | genFile := plugin.NewGeneratedFile(FileName(f.GeneratedFilenamePrefix), f.GoImportPath) 119 | 120 | packageName := string(f.Desc.Package().Name()) 121 | if packageName == "" { 122 | packageName = string(f.GoPackageName) 123 | } 124 | 125 | err = g.ExecuteTemplate(genFile, f, packageName, true) 126 | if err != nil { 127 | return nil, err 128 | } 129 | } 130 | 131 | return plugin.Response(), nil 132 | } 133 | 134 | func (g *Generator) ExecuteTemplate( 135 | genFile *protogen.GeneratedFile, 136 | protoFile *protogen.File, 137 | packageName string, 138 | header bool, 139 | ) error { 140 | var buf bytes.Buffer 141 | deps := DependencyAnalysis(protoFile) 142 | 143 | err := g.templ.ExecuteTemplate(&buf, "base.templ", map[string]interface{}{ 144 | "pluginVersion": version.Version(), 145 | "sourcePath": protoFile.Desc.Path(), 146 | "package": packageName, 147 | "enums": protoFile.Desc.Enums(), 148 | "messages": protoFile.Desc.Messages(), 149 | "header": header, 150 | "dependencies": deps, 151 | }) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | cmd := exec.Command("rustfmt") 157 | cmd.Stdin = bytes.NewReader(buf.Bytes()) 158 | output, err := cmd.CombinedOutput() 159 | if err != nil { 160 | println(string(output)) 161 | return err 162 | } 163 | _, err = genFile.Write(output) 164 | return err 165 | } 166 | -------------------------------------------------------------------------------- /v2/generator/rust/headers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/v2/utils" 21 | ) 22 | 23 | func FileName(name string) string { 24 | return utils.AppendString(name, extension) 25 | } 26 | -------------------------------------------------------------------------------- /v2/generator/rust/imports.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rust 18 | 19 | import ( 20 | "google.golang.org/protobuf/compiler/protogen" 21 | "google.golang.org/protobuf/reflect/protoreflect" 22 | ) 23 | 24 | type Dependencies struct { 25 | Enums bool 26 | Maps bool 27 | } 28 | 29 | func DependencyAnalysis(file *protogen.File) *Dependencies { 30 | dependencies := &Dependencies{ 31 | Enums: false, 32 | Maps: false, 33 | } 34 | 35 | if len(file.Enums) > 0 { 36 | dependencies.Enums = true 37 | } 38 | for _, message := range file.Messages { 39 | for _, field := range message.Fields { 40 | if field.Desc.Kind() == protoreflect.MessageKind { 41 | if field.Desc.Message().IsMapEntry() { 42 | dependencies.Maps = true 43 | } 44 | if field.Desc.Message().Fields().Len() > 0 { 45 | dependencies = traverseFields(field.Message, dependencies) 46 | } 47 | } 48 | } 49 | } 50 | return dependencies 51 | } 52 | 53 | func traverseFields(message *protogen.Message, dependencies *Dependencies) *Dependencies { 54 | for _, field := range message.Fields { 55 | if field.Desc.Kind() == protoreflect.MessageKind { 56 | if field.Desc.Message().IsMapEntry() { 57 | dependencies.Maps = true 58 | } 59 | if field.Desc.Message().Fields().Len() > 0 { 60 | dependencies = traverseFields(field.Message, dependencies) 61 | } 62 | } 63 | } 64 | return dependencies 65 | } 66 | -------------------------------------------------------------------------------- /v2/generator/rust/templates/base.templ: -------------------------------------------------------------------------------- 1 | {{template "headers" .}} 2 | 3 | {{template "imports" .}} 4 | 5 | {{template "enums" .}} 6 | 7 | {{template "messages" .}} 8 | -------------------------------------------------------------------------------- /v2/generator/rust/templates/decode.templ: -------------------------------------------------------------------------------- 1 | {{define "decode"}} 2 | 3 | impl Decode for {{ CamelCase .FullName }} { 4 | fn decode (b: &mut Cursor<&mut Vec>) -> Result, Box> { 5 | if b.decode_none() { 6 | return Ok(None); 7 | } 8 | 9 | if let Ok(error) = b.decode_error() { 10 | return Err(error); 11 | } 12 | 13 | 14 | {{ $decoding := GetDecodingFields .Fields -}} 15 | {{ range $field := $decoding.SliceFields -}} 16 | {{ $val := FindValue $field }} 17 | fn {{ SnakeCaseName .Name }}_decode(b: &mut Cursor<&mut Vec>) -> Result, Box> { 18 | {{ $kind := GetKind $field.Kind -}} 19 | {{ $decoder := GetLUTDecoder $field.Kind -}} 20 | 21 | let {{ SnakeCaseName $field.Name }}_size = b.decode_array({{ $kind }})?; 22 | let mut temp = Vec::with_capacity({{ SnakeCaseName $field.Name }}_size); 23 | for _ in 0..{{ SnakeCaseName $field.Name }}_size { 24 | {{ if eq $field.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 25 | temp.push({{ CamelCase $field.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidArray)?); 26 | {{ else -}} 27 | temp.push(b{{ $decoder }}()?); 28 | {{ end -}} 29 | } 30 | Ok(Some(temp)) 31 | } 32 | {{ end -}} 33 | {{ range $field := $decoding.MessageFields -}} 34 | {{ if $field.IsMap -}} 35 | {{ template "decodeMap" $field -}} 36 | {{ end -}} 37 | {{ end -}} 38 | Ok(Some({{ CamelCase .FullName }}{ 39 | {{ range $field := $decoding.Other -}} 40 | {{ $decoder := GetLUTDecoder $field.Kind -}} 41 | {{ if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 42 | {{ SnakeCaseName $field.Name }}: {{ FindValue $field }}::try_from(b.decode_u32()?).ok().ok_or(DecodingError::InvalidEnum)?, 43 | {{ else -}} 44 | {{ SnakeCaseName $field.Name }}: b{{ $decoder }}()?, 45 | {{end -}} 46 | {{end -}} 47 | {{ range $field := $decoding.SliceFields -}} 48 | {{ SnakeCaseName $field.Name }}: {{ SnakeCaseName $field.Name }}_decode(b)?.ok_or(DecodingError::InvalidArray)?, 49 | {{ end -}} 50 | {{ range $field := $decoding.MessageFields -}} 51 | {{ if $field.Message.IsMapEntry -}} 52 | {{ SnakeCaseName $field.Name }}: {{ SnakeCaseName $field.Name }}_decode(b)?.ok_or(DecodingError::InvalidMap)?, 53 | {{ else -}} 54 | {{ SnakeCaseName $field.Name }}: {{ CamelCase $field.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidStruct)?, 55 | {{ end -}} 56 | {{ end -}} 57 | })) 58 | } 59 | } 60 | {{end}} 61 | -------------------------------------------------------------------------------- /v2/generator/rust/templates/decodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "decodeMap"}} 2 | {{ $mapKeyValue := FindValue .MapKey }} 3 | {{ $mapValueValue := FindValue .MapValue }} 4 | fn {{ SnakeCaseName .Name }}_decode(b: &mut Cursor<&mut Vec>) -> Result>, Box> { 5 | if b.decode_none() { 6 | return Ok(None); 7 | } 8 | 9 | {{ $keyDecoder := GetLUTDecoder .MapKey.Kind -}} 10 | {{ $valDecoder := GetLUTDecoder .MapValue.Kind -}} 11 | 12 | {{ $keyKind := GetKind .MapKey.Kind -}} 13 | {{ $valKind := GetKind .MapValue.Kind -}} 14 | let size = b.decode_map({{ $keyKind }}, {{ $valKind }}) 15 | .ok().ok_or(DecodingError::InvalidU32)?; 16 | let mut map = HashMap::new(); 17 | for _ in 0..size { 18 | {{ if and (eq $keyDecoder "") (eq .MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 19 | let k = {{ CamelCase .MapKey.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidMap)?'; 20 | {{else -}} 21 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 22 | let k = {{ $mapKeyValue }}::try_from(b{{$keyDecoder}}()?).ok().ok_or(DecodingError::InvalidMap)?; 23 | {{else -}} 24 | let k = b{{$keyDecoder}}()?; 25 | {{end -}} 26 | {{end -}} 27 | {{ if and (eq $valDecoder "") (eq .MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 28 | let v = {{ CamelCase .MapValue.Message.FullName }}::decode(b)?.ok_or(DecodingError::InvalidMap)?; 29 | {{else -}} 30 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 31 | let v = {{ $mapValueValue }}::try_from(b{{$valDecoder}}()?).ok().ok_or(DecodingError::InvalidMap)?; 32 | {{else -}} 33 | let v = b{{$valDecoder}}()?; 34 | {{end -}} 35 | {{end -}} 36 | map.insert(k, v); 37 | } 38 | Ok(Some(map)) 39 | } 40 | {{end}} -------------------------------------------------------------------------------- /v2/generator/rust/templates/encode.templ: -------------------------------------------------------------------------------- 1 | {{define "encode"}} 2 | impl Encode for {{ CamelCase .FullName }} { 3 | fn encode<'a> (&'a self, b: &'a mut Cursor>) -> Result<&mut Cursor>, Box> { 4 | {{ $encoding := GetEncodingFields .Fields -}} 5 | {{ if $encoding.Values -}} 6 | b{{ range $val := $encoding.Values -}}{{ $val -}}?{{end -}}; 7 | {{ end -}} 8 | {{ if $encoding.SliceFields -}} 9 | {{template "encodeSlices" $encoding -}} 10 | {{end -}} 11 | {{ if $encoding.MessageFields -}} 12 | {{template "encodeMessages" $encoding -}} 13 | {{end -}} 14 | Ok(b) 15 | } 16 | } 17 | {{end}} 18 | 19 | {{define "encodeSlices"}} 20 | {{ range $field := .SliceFields -}} 21 | {{ $encoder := GetLUTEncoder $field.Kind -}} 22 | 23 | {{ if and (eq $encoder "") (eq $field.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 24 | b.encode_array(self.{{ SnakeCaseName $field.Name}}.len(), Kind::Any)?; 25 | for item in &self.{{ SnakeCaseName $field.Name}} { 26 | item.encode(b)?; 27 | } 28 | {{else -}} 29 | b.encode_array(self.{{ SnakeCaseName $field.Name}}.len(), {{ GetKindLUT $field.Kind }})?; 30 | for item in &self.{{ SnakeCaseName $field.Name}} { 31 | {{ if eq $field.Kind 9 -}} {{/* protoreflect.StringKind */ -}} 32 | b{{$encoder}}(&item)?; 33 | {{ else -}} 34 | b{{$encoder}}(item)?; 35 | {{ end -}} 36 | } 37 | {{end -}} 38 | {{end -}} 39 | {{end}} 40 | 41 | 42 | {{define "encodeMessages"}} 43 | {{ range $field := .MessageFields -}} 44 | {{ if $field.IsMap -}} 45 | {{ $keyKind := GetKind $field.MapKey.Kind -}} 46 | {{ $valKind := GetKind $field.MapValue.Kind -}} 47 | b.encode_map(self.{{ SnakeCaseName $field.Name }}.len(), {{ $keyKind }}, {{ $valKind }})?; 48 | for (k, v) in &self.{{ SnakeCaseName $field.Name }} { 49 | {{ $keyEncoder := GetLUTEncoder $field.MapKey.Kind -}} 50 | {{ if and (eq $keyEncoder "") (eq $field.MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 51 | k.encode(b)?; 52 | {{else -}} 53 | {{ if eq $field.MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 54 | b.encode_u32(k as u32)?; 55 | {{ else if eq $field.MapKey.Kind 9 -}} {{/* protoreflect.StringsKind */ -}} 56 | b{{$keyEncoder}}(&k)?; 57 | {{else -}} 58 | b{{$keyEncoder}}(k)?; 59 | {{end -}} 60 | {{end -}} 61 | {{ $valEncoder := GetLUTEncoder $field.MapValue.Kind -}} 62 | {{ if and (eq $valEncoder "") (eq $field.MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 63 | v.encode(b)?; 64 | {{else -}} 65 | {{ if eq $field.MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 66 | b.encode_u32(*v as u32)?; 67 | {{else -}} 68 | b{{$valEncoder}}(*v)?; 69 | {{end -}} 70 | {{end -}} 71 | } 72 | {{else -}} 73 | self.{{ SnakeCaseName $field.Name }}.encode(b)?; 74 | {{end -}} 75 | {{end -}} 76 | {{end}} -------------------------------------------------------------------------------- /v2/generator/rust/templates/enums.templ: -------------------------------------------------------------------------------- 1 | {{define "enums"}} 2 | {{range $i, $e := (MakeIterable .enums.Len) -}} 3 | {{ $enum := ($.enums.Get $i) -}} 4 | {{template "enum" $enum}} 5 | {{end -}} 6 | {{end}} 7 | 8 | {{define "enum"}} 9 | {{ $enumName := (CamelCase $.FullName) -}} 10 | #[derive(Debug, Eq, PartialEq, TryFromPrimitive, Copy, Clone)] 11 | #[repr(u32)] 12 | pub enum {{ $enumName }} { 13 | {{range $i, $v := (MakeIterable $.Values.Len) -}} 14 | {{ $val := ($.Values.Get $i) -}} 15 | {{$val.Name}} = {{ $i }}, 16 | {{end -}} 17 | } 18 | {{end}} 19 | 20 | -------------------------------------------------------------------------------- /v2/generator/rust/templates/headers.templ: -------------------------------------------------------------------------------- 1 | {{define "headers"}} 2 | // Code generated by polyglot {{ .pluginVersion }}, DO NOT EDIT. 3 | // source: {{ .sourcePath }} 4 | {{end}} -------------------------------------------------------------------------------- /v2/generator/rust/templates/imports.templ: -------------------------------------------------------------------------------- 1 | {{define "imports"}} 2 | use std::io::Cursor; 3 | use polyglot_rs::{DecodingError, Encoder, Decoder, Kind}; 4 | {{ if .dependencies.Enums -}} 5 | use num_enum::TryFromPrimitive; 6 | use std::convert::TryFrom; 7 | {{ end -}} 8 | {{ if .dependencies.Maps -}} 9 | use std::collections::HashMap; 10 | {{ end -}} 11 | {{end}} -------------------------------------------------------------------------------- /v2/generator/rust/templates/messages.templ: -------------------------------------------------------------------------------- 1 | {{define "messages"}} 2 | pub trait Encode { 3 | fn encode<'a> (&'a self, b: &'a mut Cursor>) -> Result<&mut Cursor>, Box>; 4 | } 5 | 6 | pub trait Decode { 7 | fn decode (b: &mut Cursor<&mut Vec>) -> Result, Box> where Self: Sized; 8 | } 9 | {{range $i, $e := (MakeIterable .messages.Len) -}} 10 | {{ $message := $.messages.Get $i }} 11 | {{range $i, $e := (MakeIterable $message.Enums.Len) -}} 12 | {{ $enum := ($message.Enums.Get $i) }} 13 | {{template "enum" $enum}} 14 | {{end}} 15 | {{template "structs" $message}} 16 | {{end}} 17 | {{end}} -------------------------------------------------------------------------------- /v2/generator/rust/templates/structs.templ: -------------------------------------------------------------------------------- 1 | {{define "structs"}} 2 | {{ range $i, $v := (MakeIterable $.Messages.Len) }} 3 | {{ $message := $.Messages.Get $i }} 4 | {{ if not $message.IsMapEntry }} 5 | {{template "structs" $message}} 6 | {{end}} 7 | {{end}} 8 | pub struct {{ CamelCase .FullName }} { 9 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 10 | {{ $field := $.Fields.Get $i -}} 11 | {{ $value := FindValue $field -}} 12 | 13 | {{ $privacy := GeneratedFieldPrivacy -}} 14 | {{ if eq $privacy "private" -}} 15 | {{ SnakeCaseName $field.Name }}: {{ $value }}, 16 | {{ else if eq $privacy "public" -}} 17 | pub {{ SnakeCaseName $field.Name }}: {{ $value }}, 18 | {{ else -}} 19 | pub(crate) {{ SnakeCaseName $field.Name }}: {{ $value }}, 20 | {{ end -}} 21 | {{end -}} 22 | } 23 | 24 | {{template "encode" .}} 25 | {{template "decode" .}} 26 | {{end}} 27 | -------------------------------------------------------------------------------- /v2/generator/rust/templates/templates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | import "embed" 20 | 21 | //go:embed * 22 | var FS embed.FS 23 | -------------------------------------------------------------------------------- /v2/generator/typescript/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package typescript 18 | 19 | const ( 20 | extension = ".polyglot.ts" 21 | polyglotAnyKind = "Kind.Any" 22 | ) 23 | -------------------------------------------------------------------------------- /v2/generator/typescript/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package typescript 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/v2/generator/typescript/templates" 21 | "github.com/loopholelabs/polyglot/v2/utils" 22 | "github.com/loopholelabs/polyglot/v2/version" 23 | 24 | "google.golang.org/protobuf/compiler/protogen" 25 | "google.golang.org/protobuf/proto" 26 | "google.golang.org/protobuf/reflect/protoreflect" 27 | "google.golang.org/protobuf/types/pluginpb" 28 | 29 | "bytes" 30 | "strings" 31 | "text/template" 32 | ) 33 | 34 | type Generator struct { 35 | options *protogen.Options 36 | templ *template.Template 37 | CustomFields func() string 38 | CustomEncode func() string 39 | CustomDecode func() string 40 | 41 | dependencies map[string]struct{} 42 | } 43 | 44 | func New() *Generator { 45 | var g *Generator 46 | templ := template.Must(template.New("main").Funcs(template.FuncMap{ 47 | "CamelCase": utils.CamelCaseFullName, 48 | "CamelCaseName": utils.CamelCaseName, 49 | "CamelCaseFullName": utils.CamelCaseFullName, 50 | "MakeIterable": utils.MakeIterable, 51 | "Counter": utils.Counter, 52 | "FirstLowerCase": utils.FirstLowerCase, 53 | "FirstLowerCaseName": utils.FirstLowerCaseName, 54 | "FindValue": findValue, 55 | "GetKind": func(kind protoreflect.Kind) string { 56 | return getKind(g.dependencies, kind) 57 | }, 58 | "GetLUTEncoder": func(kind protoreflect.Kind) string { 59 | return getLUTEncoder(g.trackDependency, kind) 60 | }, 61 | "GetLUTDecoder": func(kind protoreflect.Kind) string { 62 | return getLUTDecoder(g.trackDependency, kind) 63 | }, 64 | "GetEncodingFields": func(fields protoreflect.FieldDescriptors) encodingFields { 65 | return getEncodingFields(g.trackDependency, fields) 66 | }, 67 | "GetDecodingFields": func(fields protoreflect.FieldDescriptors) decodingFields { 68 | return getDecodingFields(g.trackDependency, fields) 69 | }, 70 | "GetKindLUT": func(kind protoreflect.Kind) string { 71 | return getKindLUT(g.trackDependency, kind) 72 | }, 73 | "LowercaseCamelCase": utils.LowercaseCamelCase, 74 | "LowercaseCamelCaseName": utils.LowercaseCamelCaseName, 75 | "CustomFields": func() string { 76 | return g.CustomFields() 77 | }, 78 | "CustomEncode": func() string { 79 | return g.CustomEncode() 80 | }, 81 | "CustomDecode": func() string { 82 | return g.CustomDecode() 83 | }, 84 | "TrimSuffix": strings.TrimSuffix, 85 | "TrackDependency": func(dep string) string { 86 | return g.trackDependency(dep) 87 | }, 88 | }).ParseFS(templates.FS, "*")) 89 | g = &Generator{ 90 | options: &protogen.Options{ 91 | ParamFunc: func(name string, value string) error { return nil }, 92 | ImportRewriteFunc: func(path protogen.GoImportPath) protogen.GoImportPath { return path }, 93 | }, 94 | templ: templ, 95 | CustomEncode: func() string { return "" }, 96 | CustomDecode: func() string { return "" }, 97 | CustomFields: func() string { return "" }, 98 | 99 | dependencies: map[string]struct{}{}, 100 | } 101 | return g 102 | } 103 | 104 | func (*Generator) UnmarshalRequest(buf []byte) (*pluginpb.CodeGeneratorRequest, error) { 105 | req := new(pluginpb.CodeGeneratorRequest) 106 | return req, proto.Unmarshal(buf, req) 107 | } 108 | 109 | func (*Generator) MarshalResponse(res *pluginpb.CodeGeneratorResponse) ([]byte, error) { 110 | return proto.Marshal(res) 111 | } 112 | 113 | func (g *Generator) Generate(req *pluginpb.CodeGeneratorRequest) (res *pluginpb.CodeGeneratorResponse, err error) { 114 | plugin, err := g.options.New(req) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | for _, f := range plugin.Files { 120 | if !f.Generate { 121 | continue 122 | } 123 | genFile := plugin.NewGeneratedFile(FileName(f.GeneratedFilenamePrefix), f.GoImportPath) 124 | 125 | packageName := string(f.Desc.Package().Name()) 126 | if packageName == "" { 127 | packageName = string(f.GoPackageName) 128 | } 129 | 130 | err = g.ExecuteTemplate(genFile, f, packageName, true) 131 | if err != nil { 132 | return nil, err 133 | } 134 | } 135 | 136 | return plugin.Response(), nil 137 | } 138 | 139 | func (g *Generator) ExecuteTemplate( 140 | genFile *protogen.GeneratedFile, 141 | protoFile *protogen.File, 142 | packageName string, 143 | header bool, 144 | ) error { 145 | var bodyBuf bytes.Buffer 146 | if err := g.templ.ExecuteTemplate(&bodyBuf, "body.templ", map[string]interface{}{ 147 | "enums": protoFile.Desc.Enums(), 148 | "messages": protoFile.Desc.Messages(), 149 | }); err != nil { 150 | return err 151 | } 152 | 153 | var headBuf bytes.Buffer 154 | if err := g.templ.ExecuteTemplate(&headBuf, "head.templ", map[string]interface{}{ 155 | "pluginVersion": version.Version(), 156 | "sourcePath": protoFile.Desc.Path(), 157 | "package": packageName, 158 | "header": header, 159 | "dependencies": g.dependencies, 160 | }); err != nil { 161 | return err 162 | } 163 | 164 | _, err := genFile.Write(append(headBuf.Bytes(), bodyBuf.Bytes()...)) 165 | return err 166 | } 167 | 168 | func (g *Generator) trackDependency(dep string) string { 169 | g.dependencies[dep] = struct{}{} 170 | 171 | return dep 172 | } 173 | -------------------------------------------------------------------------------- /v2/generator/typescript/headers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package typescript 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/v2/utils" 21 | ) 22 | 23 | func FileName(name string) string { 24 | return utils.AppendString(name, extension) 25 | } 26 | -------------------------------------------------------------------------------- /v2/generator/typescript/templates/body.templ: -------------------------------------------------------------------------------- 1 | {{template "enums" .}} 2 | 3 | {{template "messages" .}} 4 | -------------------------------------------------------------------------------- /v2/generator/typescript/templates/decode.templ: -------------------------------------------------------------------------------- 1 | {{define "decode"}} 2 | static decode(buf: Uint8Array): { buf: Uint8Array, value: {{ CamelCase .FullName }}} { 3 | let decoded = buf 4 | 5 | {{ $decoding := GetDecodingFields .Fields -}} 6 | {{ range $field := $decoding.SliceFields -}} 7 | {{ $val := FindValue $field }} 8 | {{ $kind := GetKind $field.Kind -}} 9 | {{ $decoder := GetLUTDecoder $field.Kind -}} 10 | 11 | let {{ LowercaseCamelCaseName $field.Name }} = {{ TrackDependency "decodeArray" }}(decoded) 12 | decoded = {{ LowercaseCamelCaseName $field.Name }}.buf 13 | const {{ LowercaseCamelCaseName $field.Name }}Temp: { value: {{ $val }} } = { value: [] } 14 | for (let i = 0; i < {{ LowercaseCamelCaseName $field.Name }}.size; i++) { 15 | {{ if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 16 | let elementEnum = {{ TrackDependency "decodeUint32" }}(decoded) 17 | const element = { value: elementEnum as {{ $val }} } 18 | decoded = element.bufEnum 19 | {{ else if eq $field.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 20 | let element = {{ TrimSuffix $val "[]" }}.decode(decoded) 21 | decoded = element.buf 22 | {{ else -}} 23 | let element = {{ $decoder }}(decoded) 24 | decoded = element.buf 25 | {{end -}} 26 | {{ LowercaseCamelCaseName $field.Name }}Temp.value.push(element.value) 27 | } 28 | {{ end -}} 29 | 30 | {{ range $field := $decoding.MessageFields -}} 31 | {{ $val := FindValue $field }} 32 | {{ if $field.IsMap -}} 33 | {{ template "decodeMap" $field -}} 34 | {{ else -}} 35 | let {{ LowercaseCamelCaseName $field.Name }}Temp = {{ $val }}.decode(decoded) 36 | decoded = {{ LowercaseCamelCaseName $field.Name }}Temp.buf 37 | {{ end -}} 38 | {{ end -}} 39 | 40 | {{ range $field := $decoding.Other -}} 41 | {{ $val := FindValue $field }} 42 | {{ $decoder := GetLUTDecoder $field.Kind -}} 43 | {{ if eq $field.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 44 | let {{ LowercaseCamelCaseName $field.Name }}U32 = {{ TrackDependency "decodeUint32" }}(decoded) 45 | const {{ LowercaseCamelCaseName $field.Name }}Temp = { value: {{ LowercaseCamelCaseName $field.Name }}U32 as {{ $val }} } 46 | decoded = {{ LowercaseCamelCaseName $field.Name }}U32.buf 47 | {{ else -}} 48 | let {{ LowercaseCamelCaseName $field.Name }}Temp = {{ $decoder }}(decoded) 49 | decoded = {{ LowercaseCamelCaseName $field.Name }}Temp.buf 50 | {{end -}} 51 | {{end}} 52 | 53 | return { buf: decoded, value: new {{ CamelCase .FullName }}( 54 | {{ range $field := $decoding.Other -}} 55 | {{ LowercaseCamelCaseName $field.Name }}Temp.value, 56 | {{end -}} 57 | {{ range $field := $decoding.SliceFields -}} 58 | {{ LowercaseCamelCaseName $field.Name }}Temp.value, 59 | {{ end -}} 60 | {{ range $field := $decoding.MessageFields -}} 61 | {{ LowercaseCamelCaseName $field.Name }}Temp.value, 62 | {{ end -}} 63 | )} 64 | } 65 | {{end}} 66 | -------------------------------------------------------------------------------- /v2/generator/typescript/templates/decodeMap.templ: -------------------------------------------------------------------------------- 1 | {{define "decodeMap"}} 2 | {{ $mapKeyValue := FindValue .MapKey }} 3 | {{ $mapValueValue := FindValue .MapValue }} 4 | {{ $keyDecoder := GetLUTDecoder .MapKey.Kind -}} 5 | {{ $valDecoder := GetLUTDecoder .MapValue.Kind -}} 6 | 7 | let {{ LowercaseCamelCaseName .Name }} = {{ TrackDependency "decodeMap" }}(decoded) 8 | decoded = {{ LowercaseCamelCaseName .Name }}.buf 9 | const {{ LowercaseCamelCaseName .Name }}Temp: { value: Map<{{ $mapKeyValue }},{{ $mapValueValue }}> } = { value: new Map() } 10 | for (let i = 0; i < {{ LowercaseCamelCaseName .Name }}.size; i++) { 11 | {{ if eq .MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 12 | let keyEnum = {{ TrackDependency "decodeUint32" }}(decoded) 13 | const key = { value: keyEnum as {{ $mapKeyValue }} } 14 | decoded = keyEnum.buf 15 | {{ else if eq .MapKey.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 16 | let key = {{ TrimSuffix $mapKeyValue "[]" }}.decode(decoded) 17 | decoded = key.buf 18 | {{ else -}} 19 | let key = {{ $keyDecoder }}(decoded) 20 | decoded = key.buf 21 | {{end}} 22 | 23 | {{ if eq .MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 24 | let valueEnum = {{ TrackDependency "decodeUint32" }}(decoded) 25 | const value = { value: valueEnum as {{ $mapValueValue }} } 26 | decoded = valueEnum.buf 27 | {{ else if eq .MapValue.Kind 11 -}} {{/* protoreflect.MessageKind */ -}} 28 | let value = {{ TrimSuffix $mapValueValue "[]" }}.decode(decoded) 29 | decoded = value.buf 30 | {{ else -}} 31 | let value = {{ $valDecoder }}(decoded) 32 | decoded = value.buf 33 | {{end}} 34 | 35 | {{ LowercaseCamelCaseName .Name }}.value.set(key, value) 36 | } 37 | {{end}} -------------------------------------------------------------------------------- /v2/generator/typescript/templates/encode.templ: -------------------------------------------------------------------------------- 1 | {{define "encode"}} 2 | encode(buf: Uint8Array): Uint8Array { 3 | let encoded = buf 4 | 5 | {{ $encoding := GetEncodingFields .Fields -}} 6 | 7 | {{ range $val := $encoding.Values}} 8 | encoded = {{ $val -}} 9 | {{end }} 10 | 11 | {{ if $encoding.SliceFields -}} 12 | {{template "encodeSlices" $encoding -}} 13 | {{end -}} 14 | {{ if $encoding.MessageFields -}} 15 | {{template "encodeMessages" $encoding -}} 16 | {{end}} 17 | 18 | return encoded 19 | } 20 | {{end}} 21 | 22 | {{define "encodeSlices"}} 23 | {{ range $field := .SliceFields -}} 24 | {{ $encoder := GetLUTEncoder $field.Kind -}} 25 | 26 | {{ if and (eq $encoder "") (eq $field.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 27 | encoded = {{ TrackDependency "encodeArray" }}(encoded, this._{{ LowercaseCamelCaseName $field.Name}}.length, Kind.Any) 28 | this._{{ LowercaseCamelCaseName $field.Name}}.forEach((field) => { 29 | encoded = field.encode(encoded) 30 | }) 31 | {{else -}} 32 | encoded = {{ TrackDependency "encodeArray" }}(this._{{ LowercaseCamelCaseName $field.Name}}.length, {{ GetKindLUT $field.Kind }}) 33 | this._{{ LowercaseCamelCaseName $field.Name}}.forEach((field) => { 34 | encoded = {{$encoder}}(encoded, field) 35 | }) 36 | {{end -}} 37 | {{end -}} 38 | {{end}} 39 | 40 | 41 | {{define "encodeMessages"}} 42 | {{ range $field := .MessageFields -}} 43 | {{ if $field.IsMap -}} 44 | {{ $keyKind := GetKind $field.MapKey.Kind -}} 45 | {{ $valKind := GetKind $field.MapValue.Kind -}} 46 | encoded = {{ TrackDependency "encodeMap" }}(encoded, this._{{ LowercaseCamelCaseName $field.Name }}.size, {{ $keyKind }}, {{ $valKind }}) 47 | this._{{ LowercaseCamelCaseName $field.Name }}.forEach((v, k) => { 48 | {{ $keyEncoder := GetLUTEncoder $field.MapKey.Kind -}} 49 | {{ if and (eq $keyEncoder "") (eq $field.MapKey.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 50 | encoded = k.encode(encoded) 51 | {{else -}} 52 | {{ if eq $field.MapKey.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 53 | encoded = {{ TrackDependency "encodeUint32" }}(encoded, k as number) 54 | {{else -}} 55 | encoded = {{$keyEncoder}}(encoded, k) 56 | {{end -}} 57 | {{end -}} 58 | {{ $valEncoder := GetLUTEncoder $field.MapValue.Kind -}} 59 | {{ if and (eq $valEncoder "") (eq $field.MapValue.Kind 11) -}} {{/* protoreflect.MessageKind */ -}} 60 | encoded = v.encode(encoded) 61 | {{else -}} 62 | {{ if eq $field.MapValue.Kind 14 -}} {{/* protoreflect.EnumKind */ -}} 63 | encoded = {{ TrackDependency "encodeUint32" }}(encoded, v as number) 64 | {{else -}} 65 | encoded = {{$valEncoder}}(encoded, v) 66 | {{end -}} 67 | {{end -}} 68 | }) 69 | {{else -}} 70 | encoded = this._{{ LowercaseCamelCaseName $field.Name }}.encode(encoded); 71 | {{end -}} 72 | {{end -}} 73 | {{end}} -------------------------------------------------------------------------------- /v2/generator/typescript/templates/enums.templ: -------------------------------------------------------------------------------- 1 | {{define "enums"}} 2 | {{range $i, $e := (MakeIterable .enums.Len) -}} 3 | {{ $enum := ($.enums.Get $i) -}} 4 | {{template "enum" $enum}} 5 | {{end -}} 6 | {{end}} 7 | 8 | {{define "enum"}} 9 | {{ $enumName := (CamelCaseFullName $.FullName) -}} 10 | enum {{ $enumName }} { 11 | {{range $i, $v := (MakeIterable $.Values.Len) -}} 12 | {{ $val := ($.Values.Get $i) -}} 13 | {{$val.Name}} = {{ $i }}, 14 | {{end -}} 15 | } 16 | {{end}} 17 | 18 | -------------------------------------------------------------------------------- /v2/generator/typescript/templates/head.templ: -------------------------------------------------------------------------------- 1 | {{template "headers" .}} 2 | 3 | {{template "imports" .}} -------------------------------------------------------------------------------- /v2/generator/typescript/templates/headers.templ: -------------------------------------------------------------------------------- 1 | {{define "headers"}} 2 | // Code generated by polyglot {{ .pluginVersion }}, DO NOT EDIT. 3 | // source: {{ .sourcePath }} 4 | {{end}} -------------------------------------------------------------------------------- /v2/generator/typescript/templates/imports.templ: -------------------------------------------------------------------------------- 1 | {{define "imports"}} 2 | import { {{ range $key, $_ := .dependencies -}}{{ $key }},{{ end -}} } from "polyglot" 3 | {{end}} -------------------------------------------------------------------------------- /v2/generator/typescript/templates/messages.templ: -------------------------------------------------------------------------------- 1 | {{define "messages"}} 2 | {{range $i, $e := (MakeIterable .messages.Len) -}} 3 | {{ $message := $.messages.Get $i }} 4 | {{range $i, $e := (MakeIterable $message.Enums.Len) -}} 5 | {{ $enum := ($message.Enums.Get $i) }} 6 | {{template "enum" $enum}} 7 | {{end}} 8 | {{template "structs" $message}} 9 | {{end}} 10 | {{end}} -------------------------------------------------------------------------------- /v2/generator/typescript/templates/structs.templ: -------------------------------------------------------------------------------- 1 | {{define "structs"}} 2 | {{ range $i, $v := (MakeIterable $.Messages.Len) }} 3 | {{ $message := $.Messages.Get $i }} 4 | {{ if not $message.IsMapEntry }} 5 | {{template "structs" $message}} 6 | {{end}} 7 | {{end}} 8 | export class {{ CamelCase .FullName }} { 9 | constructor( 10 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 11 | {{ $field := $.Fields.Get $i -}} 12 | {{ $value := FindValue $field -}} 13 | {{ LowercaseCamelCaseName $field.Name }}: {{ $value }}, 14 | {{end -}} 15 | ) { 16 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 17 | {{ $field := $.Fields.Get $i -}} 18 | this._{{ LowercaseCamelCaseName $field.Name }} = {{ LowercaseCamelCaseName $field.Name }} 19 | {{end -}} 20 | } 21 | 22 | {{ range $i, $v := (MakeIterable $.Fields.Len) -}} 23 | {{ $field := $.Fields.Get $i -}} 24 | {{ $value := FindValue $field -}} 25 | private _{{ LowercaseCamelCaseName $field.Name }}: {{ $value }} 26 | 27 | get {{ LowercaseCamelCaseName $field.Name }}(): {{ $value }} { 28 | return this._{{ LowercaseCamelCaseName $field.Name }} 29 | } 30 | 31 | set {{ LowercaseCamelCaseName $field.Name }}({{ LowercaseCamelCaseName $field.Name }}: {{ $value }}) { 32 | this._{{ LowercaseCamelCaseName $field.Name }} = {{ LowercaseCamelCaseName $field.Name }} 33 | } 34 | 35 | {{end -}} 36 | 37 | {{template "encode" .}} 38 | 39 | {{template "decode" .}} 40 | } 41 | {{end}} 42 | -------------------------------------------------------------------------------- /v2/generator/typescript/templates/templates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | import "embed" 20 | 21 | //go:embed * 22 | var FS embed.FS 23 | -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/loopholelabs/polyglot/v2 2 | 3 | go 1.21.4 4 | 5 | require ( 6 | github.com/stretchr/testify v1.10.0 7 | google.golang.org/protobuf v1.36.5 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /v2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 4 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 10 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 11 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 12 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /v2/pool.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | import ( 20 | "sync" 21 | ) 22 | 23 | var ( 24 | pool = NewPool() 25 | ) 26 | 27 | type Pool struct { 28 | pool sync.Pool 29 | } 30 | 31 | func NewPool() *Pool { 32 | return new(Pool) 33 | } 34 | 35 | func (p *Pool) Get() (b *Buffer) { 36 | v := p.pool.Get() 37 | if v == nil { 38 | b = NewBuffer() 39 | return 40 | } 41 | return v.(*Buffer) 42 | } 43 | 44 | func (p *Pool) Put(b *Buffer) { 45 | if b != nil { 46 | b.Reset() 47 | p.pool.Put(b) 48 | } 49 | } 50 | 51 | func GetBuffer() *Buffer { 52 | return pool.Get() 53 | } 54 | 55 | func PutBuffer(b *Buffer) { 56 | pool.Put(b) 57 | } 58 | -------------------------------------------------------------------------------- /v2/pool_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package polyglot 18 | 19 | import ( 20 | "crypto/rand" 21 | "github.com/stretchr/testify/assert" 22 | "testing" 23 | ) 24 | 25 | func TestRecycle(t *testing.T) { 26 | pool := NewPool() 27 | 28 | data := make([]byte, 512) 29 | _, err := rand.Read(data) 30 | assert.NoError(t, err) 31 | 32 | b := pool.Get() 33 | b.Write(data) 34 | 35 | pool.Put(b) 36 | b = pool.Get() 37 | 38 | testData := make([]byte, b.Cap()*2) 39 | _, err = rand.Read(testData) 40 | assert.NoError(t, err) 41 | 42 | for { 43 | assert.Equal(t, NewBuffer().Bytes(), b.Bytes()) 44 | assert.Equal(t, 0, b.Len()) 45 | 46 | b.Write(testData) 47 | assert.Equal(t, len(testData), b.Len()) 48 | assert.GreaterOrEqual(t, b.Cap(), len(testData)) 49 | 50 | pool.Put(b) 51 | b = pool.Get() 52 | 53 | if b.Cap() < len(testData) { 54 | continue 55 | } 56 | assert.Equal(t, 0, b.Len()) 57 | assert.GreaterOrEqual(t, b.Cap(), len(testData)) 58 | break 59 | } 60 | 61 | pool.Put(b) 62 | } 63 | -------------------------------------------------------------------------------- /v2/protoc-gen-go-polyglot/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/loopholelabs/polyglot/v2/generator/golang" 21 | 22 | "io" 23 | "os" 24 | ) 25 | 26 | func main() { 27 | gen := golang.New() 28 | 29 | data, err := io.ReadAll(os.Stdin) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | req, err := gen.UnmarshalRequest(data) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | res, err := gen.Generate(req) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | data, err = gen.MarshalResponse(res) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | _, err = os.Stdout.Write(data) 50 | if err != nil { 51 | panic(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /v2/utils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "bytes" 21 | "google.golang.org/protobuf/reflect/protoreflect" 22 | 23 | "strings" 24 | "unicode" 25 | "unicode/utf8" 26 | ) 27 | 28 | // CamelCase returns the CamelCased name. 29 | // If there is an interior underscore followed by a lower case letter, 30 | // drop the underscore and convert the letter to upper case. 31 | // There is a remote possibility of this rewrite causing a name collision, 32 | // but it's so remote we're prepared to pretend it's nonexistent - since the 33 | // C++ generator lowercases names, it's extremely unlikely to have two fields 34 | // with different capitalizations. 35 | // In short, _my_field_name_2 becomes XMyFieldName_2. 36 | func CamelCase(s string) string { 37 | if s == "" { 38 | return "" 39 | } 40 | t := make([]byte, 0, 32) 41 | i := 0 42 | if s[0] == '_' { // Keep the initial _ if it exists 43 | i++ 44 | } 45 | // Invariant: if the next letter is lower case, it must be converted 46 | // to upper case. 47 | // That is, we process a word at a time, where words are marked by _ or 48 | // upper case letter. Digits are treated as words. 49 | for ; i < len(s); i++ { 50 | c := s[i] 51 | if c == '_' && i+1 < len(s) && 'a' <= s[i+1] && s[i+1] <= 'z' { 52 | continue // Skip the underscore in s. 53 | } 54 | if c == '.' { 55 | continue 56 | } 57 | if '0' <= c && c <= '9' { 58 | t = append(t, c) 59 | continue 60 | } 61 | // Assume we have a letter now - if not, it's a bogus identifier. 62 | // The next word is a sequence of characters that must start upper case. 63 | if 'a' <= c && c <= 'z' { 64 | c ^= ' ' // Make it a capital letter. 65 | } 66 | t = append(t, c) // Guaranteed not lower case. 67 | // Accept lower case sequence that follows. 68 | for i+1 < len(s) && 'a' <= s[i+1] && s[i+1] <= 'z' { 69 | i++ 70 | t = append(t, s[i]) 71 | } 72 | } 73 | return string(t) 74 | } 75 | 76 | func SnakeCase(s string) string { 77 | var b bytes.Buffer 78 | var consc bool 79 | for _, c := range s { 80 | if 'A' <= c && c <= 'Z' { 81 | if b.Len() > 0 && !consc { 82 | b.WriteRune('_') 83 | } 84 | b.WriteRune(c - 'A' + 'a') 85 | consc = true 86 | } else { 87 | b.WriteRune(c) 88 | consc = false 89 | } 90 | } 91 | return b.String() 92 | } 93 | 94 | func SnakeCaseName(name protoreflect.Name) string { 95 | return SnakeCase(string(name)) 96 | } 97 | 98 | func CamelCaseFullName(name protoreflect.FullName) string { 99 | return CamelCase(string(name)) 100 | } 101 | 102 | func CamelCaseName(name protoreflect.Name) string { 103 | return CamelCase(string(name)) 104 | } 105 | 106 | func AppendString(inputs ...string) string { 107 | builder := new(strings.Builder) 108 | for _, s := range inputs { 109 | builder.WriteString(s) 110 | } 111 | 112 | return builder.String() 113 | } 114 | 115 | func FirstLowerCase(s string) string { 116 | if s == "" { 117 | return "" 118 | } 119 | r, n := utf8.DecodeRuneInString(s) 120 | return string(unicode.ToLower(r)) + s[n:] 121 | } 122 | 123 | func FirstLowerCaseName(name protoreflect.Name) string { 124 | return FirstLowerCase(string(name)) 125 | } 126 | 127 | func LowercaseCamelCase(s string) string { 128 | if len(s) == 0 { 129 | return s 130 | } 131 | 132 | if len(s) == 1 { 133 | return string(unicode.ToLower(rune(s[0]))) 134 | } 135 | 136 | return string(unicode.ToLower(rune(s[0]))) + s[1:] 137 | } 138 | 139 | func LowercaseCamelCaseName(name protoreflect.Name) string { 140 | return LowercaseCamelCase(string(name)) 141 | } 142 | 143 | func MakeIterable(length int) []struct{} { 144 | return make([]struct{}, length) 145 | } 146 | 147 | func Counter(initial int) func() int { 148 | i := initial 149 | return func() int { 150 | i++ 151 | return i 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /v2/version/current_version: -------------------------------------------------------------------------------- 1 | v2.0.5 2 | -------------------------------------------------------------------------------- /v2/version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package version 18 | 19 | import ( 20 | _ "embed" 21 | ) 22 | 23 | //go:embed current_version 24 | var currentVersion string 25 | 26 | func Version() string { 27 | return currentVersion 28 | } 29 | -------------------------------------------------------------------------------- /version/current_version: -------------------------------------------------------------------------------- 1 | v1.3.0 2 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Loophole Labs 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package version 18 | 19 | import ( 20 | _ "embed" 21 | ) 22 | 23 | //go:embed current_version 24 | var currentVersion string 25 | 26 | func Version() string { 27 | return currentVersion 28 | } 29 | --------------------------------------------------------------------------------