├── .gitattributes ├── .github ├── ci │ └── ubuntu-install-packages ├── pre-commit └── workflows │ ├── book.yml │ ├── ci.yml │ ├── nix.yml │ └── release.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── deny.toml ├── docs ├── book.toml └── src │ ├── SUMMARY.md │ ├── commands │ ├── buffrs-add.md │ ├── buffrs-clean.md │ ├── buffrs-doc.md │ ├── buffrs-help.md │ ├── buffrs-init.md │ ├── buffrs-install.md │ ├── buffrs-lint.md │ ├── buffrs-list.md │ ├── buffrs-lock-print-files.md │ ├── buffrs-lock.md │ ├── buffrs-login.md │ ├── buffrs-logout.md │ ├── buffrs-new.md │ ├── buffrs-package.md │ ├── buffrs-publish.md │ ├── buffrs-remove.md │ ├── buffrs-tree.md │ ├── buffrs-uninstall.md │ ├── buffrs-update.md │ ├── buffrs-version.md │ ├── buffrs-yank.md │ ├── buffrs.md │ ├── build-commands.md │ ├── general-commands.md │ ├── index.md │ ├── manifest-commands.md │ ├── package-commands.md │ └── publishing-commands.md │ ├── faq.md │ ├── getting-started │ ├── first-steps.md │ ├── index.md │ └── installation.md │ ├── guide │ ├── buffrs-home.md │ ├── consuming-packages.md │ ├── creating-a-package.md │ ├── import-system.md │ ├── index.md │ ├── local-dependencies.md │ ├── manifest-vs-lockfile.md │ ├── package-types.md │ ├── project-layout.md │ ├── the-framework.md │ └── why-buffrs-exists.md │ ├── images │ ├── buffrs.svg │ └── dependency-graph.png │ ├── index.md │ ├── integrations │ └── index.md │ ├── migration │ ├── architectures.md │ ├── continuous-integration.md │ └── index.md │ ├── reference │ ├── build-scripts.md │ ├── config.md │ ├── editions.md │ ├── environment-variables.md │ ├── index.md │ ├── lockfile.md │ ├── manifest.md │ ├── overriding-dependencies.md │ ├── package-name-spec.md │ ├── protocol-buffer-rules.md │ ├── publishing.md │ ├── resolver.md │ ├── semver.md │ └── specifying-dependencies.md │ └── registry │ ├── index.md │ ├── registry-index.md │ ├── registry-web-api.md │ ├── self-hosting.md │ └── teams.md ├── flake.lock ├── flake.nix ├── nix ├── buffrs.nix ├── cache.nix └── toolchain.nix ├── src ├── cache.rs ├── command.rs ├── credentials.rs ├── errors.rs ├── lib.rs ├── lock.rs ├── lock │ └── digest.rs ├── main.rs ├── manifest.rs ├── package │ ├── compressed.rs │ ├── mod.rs │ ├── name.rs │ ├── store.rs │ └── type.rs ├── registry │ ├── artifactory.rs │ ├── cache.rs │ └── mod.rs ├── resolver.rs ├── validation.rs └── validation │ ├── data.rs │ ├── data │ ├── entity.rs │ ├── enum.rs │ ├── message.rs │ ├── package.rs │ ├── packages.rs │ └── service.rs │ ├── parse.rs │ ├── rules.rs │ ├── rules │ ├── ident_casing.rs │ ├── lib_package.rs │ ├── package_hierarchy.rs │ └── package_name.rs │ ├── serde.rs │ └── violation.rs └── tests ├── cmd ├── add │ ├── in │ ├── mod.rs │ ├── out │ │ ├── Proto.toml │ │ └── proto │ │ │ └── vendor │ │ │ └── .gitkeep │ ├── stderr.log │ └── stdout.log ├── init │ ├── api │ │ ├── mod.rs │ │ ├── out │ │ │ ├── Proto.toml │ │ │ └── proto │ │ │ │ └── vendor │ │ │ │ └── .gitkeep │ │ ├── stderr.log │ │ └── stdout.log │ ├── default │ │ ├── mod.rs │ │ ├── out │ │ │ ├── Proto.toml │ │ │ └── proto │ │ │ │ └── vendor │ │ │ │ └── .gitkeep │ │ ├── stderr.log │ │ └── stdout.log │ ├── lib │ │ ├── mod.rs │ │ ├── out │ │ │ ├── Proto.toml │ │ │ └── proto │ │ │ │ └── vendor │ │ │ │ └── .gitkeep │ │ ├── stderr.log │ │ └── stdout.log │ └── mod.rs ├── install │ ├── empty │ │ ├── in │ │ │ ├── Proto.toml │ │ │ └── proto │ │ │ │ ├── asset.proto │ │ │ │ ├── children │ │ │ │ └── child.proto │ │ │ │ ├── sibling.proto │ │ │ │ └── vendor │ │ │ │ └── some-test-api │ │ │ │ └── asset.proto │ │ ├── mod.rs │ │ ├── out │ │ │ ├── Proto.lock │ │ │ ├── Proto.toml │ │ │ └── proto │ │ │ │ ├── asset.proto │ │ │ │ ├── children │ │ │ │ └── child.proto │ │ │ │ ├── sibling.proto │ │ │ │ └── vendor │ │ │ │ └── some-test-api │ │ │ │ ├── asset.proto │ │ │ │ ├── children │ │ │ │ └── child.proto │ │ │ │ └── sibling.proto │ │ ├── stderr.log │ │ └── stdout.log │ ├── local │ │ ├── in │ │ │ ├── Proto.toml │ │ │ └── some-local-api │ │ │ │ ├── Proto.toml │ │ │ │ └── proto │ │ │ │ └── local.proto │ │ ├── mod.rs │ │ ├── out │ │ │ ├── Proto.lock │ │ │ ├── Proto.toml │ │ │ ├── proto │ │ │ │ └── vendor │ │ │ │ │ └── some-local-api │ │ │ │ │ ├── Proto.toml │ │ │ │ │ └── local.proto │ │ │ └── some-local-api │ │ │ │ ├── Proto.toml │ │ │ │ └── proto │ │ │ │ └── local.proto │ │ ├── stderr.log │ │ └── stdout.log │ ├── mod.rs │ └── upgrade │ │ ├── in │ │ ├── Proto.toml │ │ └── dummy.proto │ │ ├── mod.rs │ │ ├── stderr.log │ │ └── stdout.log ├── lint │ ├── in │ │ ├── Proto.toml │ │ └── proto │ │ │ ├── crud │ │ │ ├── resources.proto │ │ │ └── tokens.proto │ │ │ ├── root.proto │ │ │ ├── runtime │ │ │ ├── jobs.proto │ │ │ ├── type_one_runtime.proto │ │ │ └── type_two_runtime.proto │ │ │ ├── types │ │ │ └── sub │ │ │ │ ├── type_one.proto │ │ │ │ └── type_two.proto │ │ │ └── vendor │ │ │ └── the-lib │ │ │ ├── Proto.toml │ │ │ └── root.proto │ ├── mod.rs │ ├── stderr.log │ └── stdout.log ├── login │ ├── mod.rs │ ├── out │ │ └── $HOME │ │ │ └── .buffrs │ │ │ └── credentials.toml │ ├── stderr.log │ └── stdout.log ├── logout │ ├── in │ │ └── $HOME │ │ │ └── .buffrs │ │ │ └── credentials.toml │ ├── mod.rs │ ├── out │ │ └── $HOME │ │ │ └── .buffrs │ │ │ └── credentials.toml │ ├── stderr.log │ └── stdout.log ├── ls │ ├── in │ │ ├── Proto.toml │ │ └── proto │ │ │ ├── some.proto │ │ │ └── vendor │ │ │ ├── physics │ │ │ ├── Proto.toml │ │ │ ├── mass.proto │ │ │ └── temperature.proto │ │ │ └── test │ │ │ └── some.proto │ ├── mod.rs │ ├── out │ │ ├── Proto.toml │ │ └── proto │ │ │ ├── some.proto │ │ │ └── vendor │ │ │ ├── physics │ │ │ ├── Proto.toml │ │ │ ├── mass.proto │ │ │ └── temperature.proto │ │ │ └── test │ │ │ └── some.proto │ ├── stderr.log │ └── stdout.log ├── mod.rs ├── package │ ├── in │ │ ├── Proto.toml │ │ └── proto │ │ │ ├── foo │ │ │ └── bar.proto │ │ │ ├── hello.proto │ │ │ └── vendor │ │ │ ├── external │ │ │ └── external.proto │ │ │ └── lib │ │ │ ├── foo │ │ │ └── bar.proto │ │ │ └── hello.proto │ ├── mod.rs │ ├── out │ │ ├── Proto.toml │ │ ├── lib-0.0.1.tgz │ │ └── proto │ │ │ ├── foo │ │ │ └── bar.proto │ │ │ ├── hello.proto │ │ │ └── vendor │ │ │ ├── .gitkeep │ │ │ ├── external │ │ │ └── external.proto │ │ │ └── lib │ │ │ ├── foo │ │ │ └── bar.proto │ │ │ └── hello.proto │ ├── stderr.log │ └── stdout.log ├── publish │ ├── lib │ │ ├── in │ │ ├── mod.rs │ │ ├── out │ │ ├── stderr.log │ │ └── stdout.log │ ├── local │ │ ├── in │ │ │ ├── Proto.lock │ │ │ ├── Proto.toml │ │ │ ├── proto │ │ │ │ ├── local.proto │ │ │ │ └── vendor │ │ │ │ │ ├── my-api │ │ │ │ │ └── local.proto │ │ │ │ │ └── some-local-lib │ │ │ │ │ ├── Proto.toml │ │ │ │ │ └── local.proto │ │ │ └── some-local-lib │ │ │ │ ├── Proto.lock │ │ │ │ ├── Proto.toml │ │ │ │ └── proto │ │ │ │ └── local.proto │ │ ├── mod.rs │ │ ├── out │ │ │ ├── Proto.lock │ │ │ ├── Proto.toml │ │ │ ├── proto │ │ │ │ ├── local.proto │ │ │ │ └── vendor │ │ │ │ │ ├── my-api │ │ │ │ │ └── local.proto │ │ │ │ │ └── some-local-lib │ │ │ │ │ ├── Proto.toml │ │ │ │ │ └── local.proto │ │ │ └── some-local-lib │ │ │ │ ├── Proto.lock │ │ │ │ ├── Proto.toml │ │ │ │ └── proto │ │ │ │ └── local.proto │ │ ├── stderr.log │ │ └── stdout.log │ └── mod.rs ├── remove │ ├── in │ │ ├── Proto.toml │ │ └── proto │ │ │ └── vendor │ │ │ └── .gitkeep │ ├── mod.rs │ ├── out │ ├── stderr.log │ └── stdout.log └── tuto │ ├── in │ ├── Cargo.toml │ ├── build.rs │ ├── main.rs │ ├── sensor.proto │ └── temperature.proto │ └── mod.rs ├── data ├── packages │ └── test-api-0.1.0.tgz ├── parsing │ ├── addressbook.json │ ├── addressbook.proto │ ├── books.json │ └── books.proto └── projects │ ├── api │ ├── Proto.toml │ └── proto │ │ └── vendor │ │ └── .gitkeep │ ├── lib │ ├── Proto.toml │ └── proto │ │ └── vendor │ │ └── .gitkeep │ └── user │ ├── Proto.toml │ └── proto │ └── vendor │ └── .gitkeep ├── lib.rs └── test_registry.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-vendored 2 | *.jsx linguist-vendored 3 | *.ts linguist-vendored 4 | *.tsx linguist-vendored 5 | -------------------------------------------------------------------------------- /.github/ci/ubuntu-install-packages: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script gets run in weird environments that have been stripped of just 4 | # about every inessential thing. In order to keep this script versatile, we 5 | # just install 'sudo' and use it like normal if it doesn't exist. If it doesn't 6 | # exist, we assume we're root. (Otherwise we ain't doing much of anything 7 | # anyway.) 8 | if ! command -V sudo; then 9 | apt-get update 10 | apt-get install -y --no-install-recommends sudo 11 | fi 12 | sudo apt-get update 13 | sudo apt-get install -y --no-install-recommends \ 14 | zsh xz-utils liblz4-tool musl-tools brotli zstd 15 | -------------------------------------------------------------------------------- /.github/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: copy this file to .git/hooks/ 3 | 4 | # Exit at first error 5 | set -Eeu 6 | 7 | # For partially committed files, copy the staged changes to a separate location 8 | TEMPDIR=$(mktemp -d) 9 | trap "rm -rf $TEMPDIR" EXIT SIGHUP SIGINT SIGQUIT SIGTERM 10 | git checkout-index --prefix=$TEMPDIR/ -af 11 | 12 | GIT_ROOT=$(git rev-parse --show-toplevel) 13 | 14 | # Keep using the same target/ directory, not a new one in the temporary 15 | # directory. This avoids re-parsing everything from scratch every time. 16 | export CARGO_TARGET_DIR="${GIT_ROOT}/target" 17 | cd $TEMPDIR 18 | cargo fmt --check 19 | if command -v typos >/dev/null; then 20 | typos 21 | fi 22 | cargo clippy --all -- --deny warnings 23 | -------------------------------------------------------------------------------- /.github/workflows/book.yml: -------------------------------------------------------------------------------- 1 | name: Book 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | paths: 7 | - 'docs/**' 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | concurrency: 15 | group: "pages" 16 | cancel-in-progress: false 17 | 18 | jobs: 19 | deploy: 20 | environment: 21 | name: Book 22 | url: ${{ steps.deployment.outputs.page_url }} 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | - name: Setup Pages 28 | uses: actions/configure-pages@v3 29 | - name: Setup mdbook 30 | uses: peaceiris/actions-mdbook@v1.2.0 31 | - run: cd docs && mdbook build 32 | - name: Upload artifacts 33 | uses: actions/upload-pages-artifact@v2 34 | with: 35 | path: 'docs/book' 36 | - name: Deploy to GitHub Pages 37 | id: deployment 38 | uses: actions/deploy-pages@v2 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Buffrs CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | env: 11 | MINIMUM_LINE_COVERAGE_PERCENT: 35 12 | 13 | jobs: 14 | fmt: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - run: rustup update && rustup component add rustfmt 19 | - run: cargo fmt --check --all 20 | 21 | clippy: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: rustup update && rustup component add clippy 26 | - name: Install Protoc 27 | uses: arduino/setup-protoc@v2 28 | - uses: Swatinem/rust-cache@v2 29 | - run: cargo clippy --all-targets --workspace -- -D warnings -D clippy::all 30 | 31 | deny: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v3 35 | - run: rustup update 36 | - uses: Swatinem/rust-cache@v2 37 | - run: cargo install cargo-deny || true 38 | - run: cargo deny --workspace check 39 | 40 | test: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v3 44 | with: 45 | lfs: 'true' 46 | - run: rustup update 47 | - run: rustup component add llvm-tools-preview 48 | - uses: Swatinem/rust-cache@v2 49 | - run: cargo install cargo-llvm-cov || true 50 | - name: Install Protoc 51 | uses: arduino/setup-protoc@v2 52 | - run: cargo llvm-cov --workspace --fail-under-lines "$MINIMUM_LINE_COVERAGE_PERCENT" 53 | env: 54 | RUST_BACKTRACE: 1 55 | 56 | typos: 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v3 60 | - uses: Swatinem/rust-cache@v2 61 | - run: cargo install typos-cli@=1.16.26 --locked || true 62 | - run: typos 63 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: Nix 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | check: 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: DeterminateSystems/nix-installer-action@main 19 | - uses: DeterminateSystems/magic-nix-cache-action@main 20 | - uses: DeterminateSystems/flake-checker-action@main 21 | - name: Run `nix flake check` 22 | run: nix flake check 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | /docs/book/ 3 | /.direnv 4 | /result 5 | .idea/* 6 | 7 | .DS_Store 8 | .vscode 9 | .envrc 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Pull requests are welcome. For major changes, please open an issue first 4 | to discuss what you would like to change. 5 | 6 | Please make sure to update tests as appropriate. 7 | 8 | You can set-up a pre-commit hooks that automatically runs `cargo fmt` and `cargo clippy` by running: 9 | 10 | ```bash 11 | ln -s ../../.github/pre-commit .git/hooks 12 | ``` 13 | 14 | **Note:** running Clippy can take a while the first while, but subsequent run 15 | should only take a second or so. 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "buffrs" 3 | version = "0.10.0" 4 | edition = "2021" 5 | description = "Modern protobuf package management" 6 | authors = [ 7 | "André Sá De Mello ", 8 | "James Baker ", 9 | "Mara Schulke ", 10 | "Patrick Elsen ", 11 | "Quentin Santos ", 12 | "Robert Fink ", 13 | "Thomas Pellissier-Tanon ", 14 | "Tom Karwowski ", 15 | ] 16 | repository = "https://github.com/helsing-ai/buffrs" 17 | documentation = "https://docs.rs/buffrs" 18 | keywords = ["protobuf", "protocol", "buffers", "package", "distribution"] 19 | categories = ["command-line-utilities"] 20 | readme = "README.md" 21 | license = "Apache-2.0" 22 | exclude = ["/.github", "/tests"] 23 | 24 | [[bin]] 25 | name = "buffrs" 26 | path = "src/main.rs" 27 | required-features = ["git", "validation"] 28 | 29 | [[test]] 30 | name = "e2e" 31 | path = "tests/lib.rs" 32 | test = true 33 | 34 | [features] 35 | default = ["git", "validation"] 36 | validation = ["dep:anyhow", "dep:protobuf", "dep:protobuf-parse", "dep:diff-struct"] 37 | git = [] 38 | 39 | [dependencies] 40 | async-recursion = "1.0.5" 41 | anyhow = { version = "1.0", optional = true } 42 | bytes = "1.0" 43 | clap = { version = "4.3", features = ["cargo", "derive"] } 44 | diff-struct = { version = "0.5.3", optional = true } 45 | flate2 = "1" 46 | hex = "0.4.3" 47 | home = "0.5.5" 48 | human-panic = "2" 49 | miette = { version = "5.10.0", features = ["fancy"] } 50 | protobuf = { version = "3.7.2", optional = true } 51 | protobuf-parse = { version = "3.7.2", optional = true } 52 | reqwest = { version = "0.11", features = ["rustls-tls-native-roots"], default-features = false } 53 | semver = { version = "1", features = ["serde"] } 54 | serde = { version = "1", features = ["derive"] } 55 | serde_json = "1" 56 | tar = "0.4" 57 | thiserror = "1.0.49" 58 | tokio = { version = "^1.26", features = ["fs", "rt", "macros", "process", "io-std", "tracing"] } 59 | toml = "0.8.0" 60 | tracing = "0.1" 61 | tracing-subscriber = "0.3" 62 | url = { version = "2.4", features = ["serde"] } 63 | walkdir = "2" 64 | sha2 = "0.10.8" 65 | strum = { version = "0.26.2", features = ["derive"] } 66 | 67 | [dev-dependencies] 68 | assert_cmd = "2.0" 69 | assert_fs = "1.0" 70 | axum = { version = "0.7.2", default-features = false, features = ["tokio", "http1"] } 71 | fs_extra = "1.3" 72 | gix = { version = "0.71", default-features = false } 73 | hex = "0.4.3" 74 | predicates = "3.0" 75 | pretty_assertions = "1.4" 76 | serde_json = { version = "1.0.107" } 77 | serde_test = "1.0.176" 78 | similar-asserts = "1.5.0" 79 | tokio = { version = "^1.26", features = ["rt-multi-thread"] } 80 | 81 | [profile.deb] 82 | inherits = "release" 83 | debug = false 84 | 85 | [package.metadata.deb] 86 | section = "utils" 87 | assets = [ 88 | ["target/release/buffrs", "usr/bin/", "755"], 89 | ["LICENSE", "usr/share/doc/buffrs/", "644"], 90 | ["README.md", "usr/share/doc/buffrs/README", "644"], 91 | ] 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 | 7 | # Buffrs 8 | 9 | **Modern protobuf package management** 10 | 11 | [![Helsing](https://img.shields.io/badge/helsing-open%20source-black.svg)](https://helsing.ai) 12 | [![Buffrs Crate](https://img.shields.io/crates/v/buffrs.svg)](https://crates.io/crates/buffrs) 13 | [![Buffrs Book](https://img.shields.io/badge/book-latest-blueviolet.svg)](https://helsing-ai.github.io/buffrs) 14 | [![Buffrs Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://docs.rs/buffrs) 15 | ![Nix Flake](https://img.shields.io/github/actions/workflow/status/helsing-ai/buffrs/nix.yml?logo=nixos&label=flake) 16 | 17 |
18 | 19 | ## Quickstart 20 | 21 | ```bash,ignore 22 | $ cargo install buffrs 23 | $ buffrs login 24 | $ buffrs init --api 25 | $ buffrs add 26 | $ buffrs install 27 | ``` 28 | 29 | Useful resources: 30 | 31 | - [The Buffrs Book](https://helsing-ai.github.io/buffrs) 32 | - [Crate Documentation](https://docs.rs/buffrs) 33 | - [Announcement Post](https://blog.helsing.ai/buffrs-a-package-manager-for-protocol-buffers-1-2-aaf7c00153d2) 34 | - `buffrs help` 35 | 36 | ## Synopsis 37 | 38 | ```text,ignore 39 | Modern protobuf package management 40 | 41 | Usage: buffrs 42 | 43 | Commands: 44 | init Initializes a buffrs setup 45 | new Creates a new buffrs package in the current directory 46 | lint Check rule violations for this package 47 | add Adds dependencies to a manifest file 48 | remove Removes dependencies from a manifest file 49 | package Exports the current package into a distributable tgz archive 50 | publish Packages and uploads this api to the registry 51 | install Installs dependencies 52 | uninstall Uninstalls dependencies 53 | list Lists all protobuf files managed by Buffrs to stdout 54 | login Logs you in for a registry 55 | logout Logs you out from a registry 56 | lock Lockfile related commands 57 | help Print this message or the help of the given subcommand(s) 58 | 59 | Options: 60 | -h, --help Print help 61 | -V, --version Print version 62 | ``` 63 | 64 | ## Motivation 65 | 66 | Protocol buffers are agreeably a great way to define fully typed, 67 | language-independent API schemas with strong backward compatibility guarantees. 68 | They offer a neat experience for API consumers through generated bindings. *The 69 | biggest problem associated with Protocol Buffers is their distribution.* 70 | 71 | - How do you consume the raw protobuf files of one project reliably in another 72 | one? 73 | - How do you prevent transitive dependencies? 74 | - How do you publish to a unified registry with package format across 75 | languages? 76 | 77 | One obvious way is to generate code bindings in the repository containing the 78 | Protocol Buffers and publish the generated bindings, but this is associated 79 | with problems such as language lock-in. You need to proactively publish 80 | bindings for any possible language your API consumers may use. Also, in 81 | strongly typed languages like Rust, it is hard to extend the behavior of 82 | generated code in consuming projects due to _the orphan rule_. Summing up: this 83 | approach works somehow but hurts frequently. 84 | 85 | This is where Buffrs comes in: Buffrs solves this by defining a strict, 86 | package-based distribution mechanism and treats Protocol Buffers as a 87 | first-class citizen. 88 | 89 | *This allows you to publish Buffrs packages to a registry and properly depend 90 | on them in other projects.* 91 | 92 | 93 | ## Roadmap 94 | 95 | - [x] Support project manifests and dependency declaration 96 | - [x] Support package distribution via Artifactory 97 | - [x] Support tonic as code generation backend 98 | - [x] Support protoc as code generation backend 99 | - [ ] Implement `buffrs-registry`, a self-hostable, S3-based registry. 100 | - [ ] Supply tooling around Protocol Buffers, such as bindgen, linting, validation and 101 | formatting. 102 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | version = 2 3 | allow = ["Apache-2.0", "BSD-3-Clause", "MIT", "Unicode-3.0", "ISC"] 4 | 5 | [[licenses.clarify]] 6 | name = "ring" 7 | expression = "ISC" 8 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] 9 | 10 | [bans] 11 | multiple-versions = "warn" 12 | wildcards = "deny" 13 | 14 | skip = [ 15 | { crate = "bitflags@1", reason = "used by `redox`, `security-framework` and `system-configuration`" }, 16 | ] 17 | 18 | skip-tree = [ 19 | { crate = "windows-sys", reason = "Several foundational crates are not yet up to date" }, 20 | { crate = "windows-targets", reason = "Several foundational crates are not yet up to date" }, 21 | ] 22 | 23 | [advisories] 24 | version = 2 25 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Helsing"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The Buffrs Book" 7 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](index.md) 4 | 5 | # Summary 6 | 7 | [Introduction](index.md) 8 | 9 | * [Getting Started](getting-started/index.md) 10 | * [Installation](getting-started/installation.md) 11 | * [First Steps with Buffrs](getting-started/first-steps.md) 12 | 13 | * [Buffrs Guide](guide/index.md) 14 | * [Why Buffrs Exists](guide/why-buffrs-exists.md) 15 | * [The Framework](guide/the-framework.md) 16 | * [Package Types](guide/package-types.md) 17 | * [Creating a Package](guide/creating-a-package.md) 18 | * [Consuming Packages](guide/consuming-packages.md) 19 | * [Import System](guide/import-system.md) 20 | * [Project Layout](guide/project-layout.md) 21 | * [Manifest vs Lockfile](guide/manifest-vs-lockfile.md) 22 | * [Buffrs Home](guide/buffrs-home.md) 23 | 24 | * [Buffrs Integrations]() 25 | * [Cargo]() 26 | * [Poetry]() 27 | * [Npm]() 28 | 29 | * [Buffrs Migration]() 30 | * [Decomposition]() 31 | * [Project Setup]() 32 | * [Common Issues]() 33 | * [Continuous Integration](migration/continuous-integration.md) 34 | 35 | * [Buffrs Reference](reference/index.md) 36 | * [Editions](reference/editions.md) 37 | * [Specifying Dependencies]() 38 | * [Overriding Dependencies]() 39 | * [The Manifest Format]() 40 | * [The Lockfile Format]() 41 | * [Configuration]() 42 | * [Environment Variables]() 43 | * [Build Configuration]() 44 | * [Publishing on buff.rs]() 45 | * [Package Name Specifications]() 46 | * [Protocol Buffer Rules](reference/protocol-buffer-rules.md) 47 | * [Dependency Resolution]() 48 | * [SemVer Compatibility]() 49 | 50 | * [Buffrs Registry Reference]() 51 | * [Authentication]() 52 | * [Ownership]() 53 | * [Storage Layers]() 54 | * [Public Registry (buff.rs)]() 55 | * [Categories]() 56 | * [Self Hosting]() 57 | * [Registry Index]() 58 | * [Registry Web API]() 59 | * [Creating Teams]() 60 | 61 | * [Buffrs Commands](commands/index.md) 62 | * [General Commands](commands/general-commands.md) 63 | * [buffrs](commands/buffrs.md) 64 | * [buffrs help](commands/buffrs-help.md) 65 | * [buffrs version]() 66 | * [Build Commands](commands/build-commands.md) 67 | * [buffrs clean]() 68 | * [buffrs doc]() 69 | * [buffrs generate](commands/buffrs-generate.md) 70 | * [buffrs list](commands/buffrs-list.md) 71 | * [Manifest Commands](commands/manifest-commands.md) 72 | * [buffrs add](commands/buffrs-add.md) 73 | * [buffrs lock]() 74 | * [buffrs remove](commands/buffrs-remove.md) 75 | * [buffrs tree]() 76 | * [buffrs update]() 77 | * [Package Commands](commands/package-commands.md) 78 | * [buffrs init](commands/buffrs-init.md) 79 | * [buffrs new](commands/buffrs-new.md) 80 | * [buffrs lint](commands/buffrs-lint.md) 81 | * [buffrs package](commands/buffrs-package.md) 82 | * [buffrs install](commands/buffrs-install.md) 83 | * [buffrs uninstall](commands/buffrs-uninstall.md) 84 | * [Publishing Commands](commands/publishing-commands.md) 85 | * [buffrs login](commands/buffrs-login.md) 86 | * [buffrs logout](commands/buffrs-logout.md) 87 | * [buffrs publish](commands/buffrs-publish.md) 88 | * [buffrs yank]() 89 | 90 | * [FAQ](faq.md) 91 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-add.md: -------------------------------------------------------------------------------- 1 | ## buffrs add 2 | 3 | Adds a new dependency to the package manifest. 4 | 5 | ### Synopsis 6 | 7 | `buffrs add --registry ` 8 | 9 | ### Description 10 | 11 | The add command is the recommended way to include a new dependency in the 12 | current package. It modifies the local manifest file and will overwrite a 13 | pre-existing entry for the same dependency package if it exists. 14 | 15 | #### Dependency locator format 16 | 17 | The dependency should be specified with the repository, name and version 18 | according to the following format: 19 | 20 | ``` 21 | /[@] 22 | ``` 23 | 24 | Note: the version can be omitted (or set to `@latest`), in which case 25 | it will default to the latest version of this artifact in the registry. 26 | 27 | The repository name should adhere to lower-kebab case (e.g. `my-buffrs-repo`). 28 | The package name has its own set of constraints as detailed in [Package Name 29 | Specification](../reference/pkgid-spec.md). When specified, the version must 30 | adhere to the [Semantic Version convention](https://semver.org/) (e.g. `1.2.3`) 31 | -- see [SemVer compatibility](../reference/semver.md) for more information. 32 | 33 | Currently there is no support for resolving version operators but the specific 34 | version has to be provided. This means `^1.0.0`, `<2.3.0`, `~2.0.0`, etc. can't 35 | be installed, but `=1.2.3` has to be provided. 36 | 37 | #### Lockfile interaction 38 | 39 | Currently adding a new dependency won't automatically update the lockfile 40 | (`Proto.lock`). This is planned to change, but for now follow up 41 | with [`buffrs install`](buffrs-install.md) after adding a new dependency to make 42 | sure your lockfile is kept in sync. -------------------------------------------------------------------------------- /docs/src/commands/buffrs-clean.md: -------------------------------------------------------------------------------- 1 | ## buffrs clean 2 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-doc.md: -------------------------------------------------------------------------------- 1 | ## buffrs doc 2 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-help.md: -------------------------------------------------------------------------------- 1 | ## buffrs help 2 | 3 | Prints out information about how to use the CLI. 4 | 5 | ### Synopsis 6 | 7 | `buffrs help [command]` 8 | 9 | ### Description 10 | 11 | When called by itself, this command lists all the supported commands along with 12 | a brief description. 13 | 14 | When called with a command argument, it will provide specific help for that 15 | command. 16 | 17 | Passing the `-h` or `--help` flags is equivalent to invoking this command. 18 | 19 | ### Examples 20 | 21 | ``` 22 | > buffrs help 23 | Modern protobuf package management 24 | 25 | Usage: buffrs 26 | 27 | Commands: 28 | init Initializes a buffrs setup 29 | lint Check rule violations for this package 30 | add Adds dependencies to a manifest file 31 | remove Removes dependencies from a manifest file 32 | package Exports the current package into a distributable tgz archive 33 | publish Packages and uploads this api to the registry 34 | install Installs dependencies 35 | uninstall Uninstalls dependencies 36 | generate Generate code from installed buffrs packages 37 | login Logs you in for a registry 38 | logout Logs you out from a registry 39 | help Print this message or the help of the given subcommand(s) 40 | 41 | Options: 42 | -h, --help Print help 43 | -V, --version Print version 44 | ``` 45 | 46 | ``` 47 | > buffrs help init 48 | Initializes a buffrs setup 49 | 50 | Usage: buffrs init [OPTIONS] [PACKAGE] 51 | 52 | Arguments: 53 | [PACKAGE] The package name used for initialization 54 | 55 | Options: 56 | --lib Sets up the package as lib 57 | --api Sets up the package as api 58 | -h, --help Print help 59 | -V, --version Print version 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-init.md: -------------------------------------------------------------------------------- 1 | ## buffrs init 2 | 3 | Initializes the current directory as a Buffrs project. 4 | 5 | ### Synopsis 6 | 7 | `buffrs init [name]` 8 | 9 | `buffrs init --lib [name]` 10 | 11 | `buffrs init --api [name]` 12 | 13 | ### Description 14 | 15 | This command prepares the current directory as a Buffrs project, by creating a 16 | manifest file (`Proto.toml`) as well as `proto` and `proto/vendor` directories. 17 | 18 | By default, if no name is given, the current directory name is used as the 19 | package name. Note that there are special constraints on valid package names 20 | (see [Package Name Specification](../reference/pkgid-spec.md) for more details). 21 | 22 | By default, if no package type is provided, `impl` (implementation) will be 23 | used. The meaning of this is described in [Package 24 | Types](../guide/package-types.md). -------------------------------------------------------------------------------- /docs/src/commands/buffrs-install.md: -------------------------------------------------------------------------------- 1 | ## buffrs install 2 | 3 | Downloads and installs dependencies specified in the manifest. 4 | 5 | ### Synopsis 6 | 7 | `buffrs install` 8 | 9 | ### Description 10 | 11 | This command manages Buffrs local set of installed dependency packages. It is 12 | meant to be run from the root of the Buffrs project, where the `Proto.toml` 13 | manifest file can be found. Currently, only API and implementation packages can have 14 | dependencies, so this command is only useful for those package types. 15 | 16 | The installation process will respect the requirements stated in the manifest 17 | file -- specifically, the version, registry and repository provided for each 18 | dependency. Each dependency may specify its own dependencies, via its manifest 19 | file, which will also be downloaded and its contents unpacked flatly to the 20 | local filesystem, under the shared `proto/vendor` path prefix (see [Project 21 | Layout](../guide/project-layout.md) for more information). Only one version of 22 | each package can be installed, so if there is a conflicting requirement, 23 | installation will fail. 24 | 25 | Once installation has completed, the resolved packages versions will be frozen 26 | and captured in a `Proto.lock` file, which ensures that future installations 27 | (local or performed in another machine) will install the exact same dependency 28 | versions. This file is managed automatically and should be kept under version 29 | control, so that others can reproduce your local installation. 30 | 31 | #### Lockfile 32 | 33 | The install command manages the Buffrs lockfile (`Proto.lock`) automatically. If 34 | one doesn't exist when the command is invoked, one is created after the 35 | installation has completed. 36 | 37 | If dependencies have been added or removed since the last invocation, the 38 | lockfile will be modified accordingly. If the manifest requirements conflict 39 | with the lockfile (i.e. the manifest requests a different version than the one 40 | that was locked), installation will fail. 41 | 42 | Versions are locked upon first installation, and will persist until the lockfile 43 | is regenerated with `buffrs lock`, dependencies are explicitly upgraded via 44 | `buffrs update` (or a manual edit of the manifest) or they have been removed. 45 | Once removed, if dependencies are added back again, a different version may be 46 | automatically selected and locked. 47 | 48 | ##### Transitive dependencies 49 | 50 | Transitive dependencies are also managed by the current project's lockfile. Even 51 | if dependencies provide their own lockfile, those won't be used. 52 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-lint.md: -------------------------------------------------------------------------------- 1 | ## buffrs lint 2 | 3 | Lints your protocol buffers for the ([Buffrs Protocol Buffer 4 | Rules](../reference/protocol-buffer-rules.md)) 5 | 6 | ### Synopsis 7 | 8 | `buffrs lint` 9 | 10 | ### Description 11 | 12 | This command lints your local package (defined in `proto/*.proto`) for a set of 13 | rules defined in the ([Buffrs Protocol Buffer 14 | Rules](../reference/protocol-buffer-rules.md)). They contain a set of rules 15 | ranging from style to package layout (like filenaming, package declaration 16 | etc.). This enables a common flavor to Buffrs packages which affect users. 17 | 18 | One good example why this is required is the enforcement of euqality between 19 | the package declaration in the protocol buffers files (`*.proto`) and the 20 | Buffrs Package ID. This enables to expect that a Buffrs Package `a` declares 21 | the protocol buffer package `a.*` and prevents type colisions / ambiguity. 22 | 23 | ### Example 24 | 25 | Given a Buffrs Package `abc` that contains a protocol buffer file with the 26 | following file (`proto/xyz.proto`): 27 | 28 | ```proto 29 | syntax = "proto3"; 30 | 31 | package xyz; 32 | ``` 33 | 34 | Executing `buffrs lint` would return a rule violation: 35 | 36 | ```toml 37 | PackageName (https://helsing-ai.github.io/buffrs/rules/PackageName) 38 | 39 | × Make sure that the protobuf package name matches the buffer package name. 40 | ╰─▶ × package name is xyz but should have abc prefix 41 | 42 | ╭─[xyz.proto:1:1] 43 | ╰──── 44 | help: Make sure the file name matches the package. For example, a package with the name `package.subpackage` should be stored in `proto/package/subpackage.proto`. 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-list.md: -------------------------------------------------------------------------------- 1 | ## buffrs list 2 | 3 | Lists all protobuf files (`.proto`) managed by Buffrs to standard out. 4 | 5 | ### Synopsis 6 | 7 | `buffrs list|ls` 8 | 9 | ### Description 10 | 11 | This command lists all protobuf files managed by Buffrs. This way the 12 | output can be fed dynamically into external code generation tools like 13 | `protoc`. 14 | 15 | ### Example 16 | 17 | Given a project that depends on a `physics` package (that provides two `.proto` 18 | files: `temperature.proto` and `mass.proto`). Once it's dependencies are 19 | installed, the structure of the filesystem would look similar to this: 20 | 21 | ``` 22 | . 23 | ├── Proto.toml 24 | └── proto 25 | ├── some.proto 26 | └── vendor 27 | └── physics 28 | ├── Proto.toml 29 | ├── temperature.proto 30 | └── mass.proto 31 | ``` 32 | 33 | Using `buffrs ls` you can feed the installed protocol buffer files of all 34 | package dynamically into another command line tool like `protoc` to generate 35 | code, or run lints: 36 | 37 | ```bash 38 | protoc --cpp_out ./cpp --include proto $(buffrs ls) 39 | ``` 40 | 41 | --- 42 | 43 | The raw output of `buffrs ls` would return the following three paths: 44 | 45 | ```toml 46 | proto/some.proto proto/vendor/physics/temperature.proto proto/vendor/physics/mass.proto 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-lock-print-files.md: -------------------------------------------------------------------------------- 1 | ## buffrs lock print-files 2 | 3 | Prints the locked files as JSON to stdout. 4 | 5 | ### Synopsis 6 | 7 | `buffrs lock print-files` 8 | 9 | ### Description 10 | 11 | > Note: This command is designed for consumption through other scripts and 12 | > programs. 13 | 14 | Using this command you can retrieve a list of files that buffrs downloads 15 | according to the lockfile. For correct behavior please make sure your 16 | `Proto.lock` is up to date when using this command! 17 | 18 | ### Example 19 | 20 | Given a project that depends on a `physics` package at version `1.0.0` and a 21 | populated `Proto.lock`: 22 | 23 | ``` 24 | 25 | ``` 26 | 27 | Running `buffrs lock print-files` will print the following output derived from 28 | the lockfile: 29 | 30 | ``` 31 | [ 32 | { 33 | "url": "https://your.internal.registry/artifactory/your-repository/physics/physics-1.0.0.tgz", 34 | "digest": "sha256:61ecdcd949c7b234160dc5aacb4546a21512de4ff8ea85f2fdd7d5fff2bf92b5" 35 | } 36 | ] 37 | ``` 38 | 39 | This way you can programmatically consume this (e.g. in nix, bash, etc) and 40 | download the files if your project while maintaining integrity. 41 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-lock.md: -------------------------------------------------------------------------------- 1 | ## buffrs lock 2 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-login.md: -------------------------------------------------------------------------------- 1 | ## buffrs login 2 | 3 | Saves an authentication token in the credentials store. 4 | 5 | ### Synopsis 6 | 7 | `buffrs login --registry ` 8 | 9 | ### Description 10 | 11 | This command prompts for an API or Identity token that can be used to 12 | authenticate with Artifactory for downloading and publishing packages. 13 | 14 | The token is currently stored in `$HOME/.buffrs/credentials.toml` in the 15 | following format: 16 | 17 | ```toml 18 | [[credentials]] 19 | uri = "https://example.com/artifactory" 20 | token = "" 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-logout.md: -------------------------------------------------------------------------------- 1 | ## buffrs logout 2 | 3 | Removes an authentication token from the credentials store. 4 | 5 | ### Synopsis 6 | 7 | `buffrs logout --registry ` 8 | 9 | ### Description 10 | 11 | This command removes a previously saved token from the credentials store by its 12 | associated registry URL. Future invocations of `publish` and `install` that 13 | involve the given registry will then default to unauthenticated mode. 14 | 15 | The credentials are currently stored in `$HOME/.buffrs/credentials.toml`. -------------------------------------------------------------------------------- /docs/src/commands/buffrs-new.md: -------------------------------------------------------------------------------- 1 | ## buffrs init 2 | 3 | Initializes a Buffrs project in a new folder created in the current directory. 4 | 5 | ### Synopsis 6 | 7 | `buffrs new ` 8 | 9 | `buffrs new --lib ` 10 | 11 | `buffrs new --api ` 12 | 13 | ### Description 14 | 15 | This command creates a new Buffrs project with the provided name by creating a 16 | manifest file (`Proto.toml`) as well as `proto` and `proto/vendor` directories 17 | in a new directory created at the current location. 18 | 19 | By default, if no package type is provided, `impl` (implementation) will be 20 | used. The meaning of this is described in [Package 21 | Types](../guide/package-types.md). 22 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-package.md: -------------------------------------------------------------------------------- 1 | ## buffrs package 2 | 3 | Generates a release tarball for the package in the current directory. 4 | 5 | ### Synopsis 6 | 7 | `buffrs package` 8 | 9 | ### Options 10 | 11 | * `--dry-run`: prevents buffrs from actually writing the tarball to the filesystem 12 | * `--output-directory`: allows you to specify a directory to output the package 13 | * `--set-version`: allows you to override the version set in the manifest 14 | 15 | 16 | ### Description 17 | 18 | Like the [`publish`](buffrs-publish.md) command, the `package` command bundles 19 | the package's protocol buffer files and manifest into a gzip-compressed 20 | tarball. However, unlike the [`publish`](buffrs-publish.md) command it does not 21 | actually interact with the registry, instead it only writes the release tarball 22 | into the current directory. This is useful for manual distribution and for 23 | safely validating the package setup. 24 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-publish.md: -------------------------------------------------------------------------------- 1 | ## buffrs publish 2 | 3 | Generates a release and publishes it to the specified registry. 4 | 5 | ### Synopsis 6 | 7 | `buffrs publish [OPTIONS] --registry --repository ` 8 | 9 | ### Options 10 | 11 | * `--allow-dirty`: allows publishing the package even if the repository has 12 | uncommitted changes. 13 | * `--dry-run`: causes a release bundle to be generated but skips uploading to 14 | the registry. 15 | * `--set-version`: allows you to override the version set in the manifest 16 | 17 | ### Description 18 | 19 | The `publish` command bundles the package's protocol buffer files and manifest 20 | into a gzip-compressed tarball, which is then uploaded to the specified registry 21 | and repository for publication. Once published the artifact will be available 22 | for other packages to be installed as dependencies. 23 | 24 | In order for this command to be successful, the registry must be reachable via 25 | the network, and if authorization is required, credentials must have been 26 | previously saved via a [`buffrs login`](buffrs-login.md) invocation. 27 | 28 | By default, Buffrs does not allow publishing packages from git repositories in a 29 | dirty state (note: this requires the `git` feature to be enabled). This 30 | behaviour can be overridden by passing the `--allow-dirty` flag. 31 | 32 | #### Supported project types 33 | 34 | Only Buffrs libraries and API packages can be packaged and published. More details in [Package Types](../guide/package-types.md). 35 | 36 | Library packages cannot have dependencies, so releasing this kind of package may 37 | fail if any are provided in the manifest. API dependencies on library packages 38 | is also forbidden and will cause publication to fail. 39 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-remove.md: -------------------------------------------------------------------------------- 1 | ## buffrs remove 2 | 3 | Removes an existing dependency from the package manifest. 4 | 5 | ### Synopsis 6 | 7 | `buffrs remove ` 8 | 9 | ### Description 10 | 11 | The remove command is the recommended way to remove a dependency (identified by 12 | package name) from the manifest. It modifies the manifest and will produce an 13 | error if the specified package cannot be found. It implements the opposite 14 | operation from the [`add`](buffrs-add.md) command. 15 | 16 | #### Lockfile Interaction 17 | 18 | Currently removing a dependency won't automatically update the lockfile 19 | (`Proto.lock`). This is planned to change, but for now make sure to follow up 20 | with [`buffrs install`](buffrs-install.md) after adding a new dependency to 21 | make sure your lockfile is kept in sync. 22 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-tree.md: -------------------------------------------------------------------------------- 1 | ## buffrs tree 2 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-uninstall.md: -------------------------------------------------------------------------------- 1 | ## buffrs uninstall 2 | 3 | Deletes all installed dependencies from the local filesystem. 4 | 5 | ### Synopsis 6 | 7 | `buffrs uninstall` 8 | 9 | ### Description 10 | 11 | This command does the reverse operation from the 12 | [`install`](buffrs-uninstall.md) command, and will clear out the `proto/vendors` 13 | directory, thus removing all installed dependencies from the local filesystem. This 14 | is generally safe to do as the `vendors` directory is managed by Buffrs and 15 | shouldn't contain any custom proto files. Subsequently invoking the install 16 | command should restore the exact same files, assuming the lockfile hasn't been 17 | regenerated. 18 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-update.md: -------------------------------------------------------------------------------- 1 | ## buffrs update 2 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-version.md: -------------------------------------------------------------------------------- 1 | ## buffrs version 2 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs-yank.md: -------------------------------------------------------------------------------- 1 | ## buffrs yank 2 | -------------------------------------------------------------------------------- /docs/src/commands/buffrs.md: -------------------------------------------------------------------------------- 1 | ## buffrs 2 | 3 | The official Buffrs command-line interface. 4 | 5 | ### Synopsis 6 | 7 | `buffrs` 8 | 9 | ### Description 10 | 11 | When invoked without any arguments, the Buffrs binary defaults to printing out 12 | help information to the standard output. 13 | 14 | This is equivalent to [`buffrs help`](buffrs-help.md), or invoking with the `-h` 15 | or `--help` flags. 16 | 17 | Providing the `-V` or `--version` flags is also equivalent to `buffrs version`. 18 | 19 | ### Output 20 | 21 | ``` 22 | Modern protobuf package management 23 | 24 | Usage: buffrs 25 | 26 | Commands: 27 | init Initializes a buffrs setup 28 | lint Check rule violations for this package 29 | add Adds dependencies to a manifest file 30 | remove Removes dependencies from a manifest file 31 | package Exports the current package into a distributable tgz archive 32 | publish Packages and uploads this api to the registry 33 | install Installs dependencies 34 | uninstall Uninstalls dependencies 35 | list Lists all protobuf files managed by Buffrs to stdout 36 | generate Generate code from installed buffrs packages 37 | login Logs you in for a registry 38 | logout Logs you out from a registry 39 | help Print this message or the help of the given subcommand(s) 40 | 41 | Options: 42 | -h, --help Print help 43 | -V, --version Print version 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/src/commands/build-commands.md: -------------------------------------------------------------------------------- 1 | # Build Commands 2 | 3 | Build commands assist implementation projects with generating code and 4 | documentation from Buffrs-managed protocol buffer files. 5 | 6 | ## Index 7 | 8 | * [buffrs list](buffrs-list.md) 9 | -------------------------------------------------------------------------------- /docs/src/commands/general-commands.md: -------------------------------------------------------------------------------- 1 | # General Commands 2 | 3 | General commands interface with the CLI itself, in order to obtain access to 4 | built-in [help](buffrs-help.md) or [version]() 5 | information. 6 | 7 | ## Index 8 | 9 | * [buffrs](buffrs.md) 10 | * [buffrs help](buffrs-help.md) -------------------------------------------------------------------------------- /docs/src/commands/index.md: -------------------------------------------------------------------------------- 1 | # Buffrs Commands 2 | 3 | This section covers detailed documentation on operating the Buffrs CLI. Some of 4 | the content may also apply to the library, particularly the `command` module. 5 | 6 | The goal is to provide sufficient information to use the CLI effectively to 7 | manage a Buffrs project, also also capture context and behaviour that may 8 | otherwise be only expressed in code. 9 | 10 | The [`help`](buffrs-help.md) command should also provide useful information and is expected to 11 | generally be more up-to-date. 12 | 13 | ## Index 14 | 15 | * [General Commands](general-commands.md) 16 | * [buffrs](buffrs.md) 17 | * [buffrs help](buffrs-help.md) 18 | * [Build Commands](build-commands.md) 19 | * [buffrs list](buffrs-list.md) 20 | * [Manifest Commands](manifest-commands.md) 21 | * [buffrs add](buffrs-add.md) 22 | * [buffrs remove](buffrs-remove.md) 23 | * [Package Commands](package-commands.md) 24 | * [buffrs init](buffrs-init.md) 25 | * [buffrs lint](buffrs-lint.md) 26 | * [buffrs package](buffrs-package.md) 27 | * [buffrs install](buffrs-install.md) 28 | * [buffrs uninstall](buffrs-uninstall.md) 29 | * [Publishing Commands](publishing-commands.md) 30 | * [buffrs login](buffrs-login.md) 31 | * [buffrs logout](buffrs-logout.md) 32 | * [buffrs publish](buffrs-publish.md) 33 | -------------------------------------------------------------------------------- /docs/src/commands/manifest-commands.md: -------------------------------------------------------------------------------- 1 | # Manifest Commands 2 | 3 | Manifest commands manage the package's manifest file, which declares important 4 | metadata like the package name, version and dependencies. 5 | 6 | These commands can be used to include, remove and update dependencies while 7 | keeping the lockfile in sync. 8 | 9 | ## Index 10 | 11 | * [buffrs add](buffrs-add.md) 12 | * [buffrs remove](buffrs-remove.md) -------------------------------------------------------------------------------- /docs/src/commands/package-commands.md: -------------------------------------------------------------------------------- 1 | # Package Commands 2 | 3 | Package commands manage a local package, and are responsible for initializing the 4 | project structure, installing and uninstalling dependencies and building a 5 | publishable artifact from the current package state. 6 | 7 | ## Index 8 | 9 | * [buffrs init](buffrs-init.md) 10 | * [buffrs lint](buffrs-lint.md) 11 | * [buffrs package](buffrs-package.md) 12 | * [buffrs install](buffrs-install.md) 13 | * [buffrs uninstall](buffrs-uninstall.md) 14 | -------------------------------------------------------------------------------- /docs/src/commands/publishing-commands.md: -------------------------------------------------------------------------------- 1 | # Publishing Commands 2 | 3 | Publish commands interface with a remote registry and are primarily responsible 4 | for managing release publications. 5 | 6 | Also in this category are commands to manage locally saved registry credentials. 7 | 8 | ## Index 9 | 10 | * [buffrs login](buffrs-login.md) 11 | * [buffrs logout](buffrs-logout.md) 12 | * [buffrs publish](buffrs-publish.md) -------------------------------------------------------------------------------- /docs/src/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## Why doesn't `buffrs add`, `buffrs publish`, or `buffrs login` work anymore? 4 | 5 | We recently expanded the capabilities of Buffrs a bit and made it so it can 6 | handle being connected to multiple registries. For this reason, you'll have to 7 | add `--registry http://my-registry.jfrog.io/artifactory` to all three. 8 | 9 | Note that `buffrs login` had a `--url` flag previously. It was renamed to 10 | `--registry` for the sake of consistency. 11 | 12 | ## Why is my `credentials.toml` file broken? 13 | 14 | Because we expanded Buffrs and made it capable of connecting to multiple 15 | registries, we had to make some changes to how we store our credentials. 16 | 17 | When it only supported a single registry, it looked like this: 18 | 19 | ```toml 20 | [artifactory] 21 | url = "https://org.jfrog.io/artifactory" 22 | password = "some-token" 23 | ``` 24 | 25 | And now it looks like this, supporting multiple regisitries: 26 | 27 | ```toml 28 | [[credentials]] 29 | uri = "https://org1.jfrog.io/artifactory" 30 | token = "some-token" 31 | 32 | [[credentials]] 33 | uri = "https://org2.jfrog.io/artifactory" 34 | token = "some-other-token" 35 | ``` 36 | 37 | ## Why can't I log in with a username? 38 | 39 | `buffrs login` no longer supports the `--username` flag, as we no longer use 40 | BasicAuth. Instead we set the `Authorization` header which enables support for 41 | identity tokens, jwt, and encoded basic auth tokens at the same time. 42 | -------------------------------------------------------------------------------- /docs/src/getting-started/first-steps.md: -------------------------------------------------------------------------------- 1 | ## First Steps with Buffrs 2 | 3 | This section gives you a quick intro to the `buffrs` command line 4 | interface. Let us take a look at its ability to declare protocol buffer 5 | dependencies and to publish new packages to 6 | the registry. 7 | 8 | To initialize a new project with Buffrs, use [`buffrs init`](../commands/buffrs-init.md): 9 | 10 | ```bash 11 | $ mkdir web-server && cd web-server 12 | $ buffrs init --api 13 | ``` 14 | 15 | > Note: By omitting the `--api` flag (or `--lib` flag respectively) you 16 | > instruct Buffrs to not declare a local package and setup the project to be a 17 | > consumer-only (e.g. a server implementation). 18 | 19 | 20 | 21 | ```bash 22 | $ tree . 23 | . 24 | ├── Proto.toml 25 | └── proto 26 | └── vendor 27 | 28 | 2 directories, 1 file 29 | ``` 30 | 31 | This is all we need to get started. Now let’s check out the newly created `Proto.toml`: 32 | 33 | ```toml 34 | [package] 35 | name = "web-server" 36 | version = "0.1.0" 37 | type = "api" 38 | 39 | [dependencies] 40 | ``` 41 | 42 | This is called a Buffrs Manifest, and it contains all of the metadata that 43 | Buffrs needs to know about your package to install dependencies and distribute 44 | your protocol buffers as a package. 45 | 46 | Let us define a dependency of the webserver on a hypothetical library 47 | called `user` in the `datatypes` repository. 48 | 49 | This is done by invoking [`buffrs add`](../commands/buffrs-add.md): 50 | 51 | ```bash 52 | $ buffrs add --registry https://your.registry.com datatypes/user@=0.1.0 53 | ``` 54 | 55 | The result is a dependency in the `Proto.toml`: 56 | 57 | ```toml 58 | [dependencies.user] 59 | version = "=0.1.0" 60 | repository = "datatypes" 61 | registry = "https://your.registry.com/" 62 | ``` 63 | 64 | ### Going further 65 | 66 | For more details on using Buffrs, check out the [Buffrs Guide](../guide/index.md) 67 | -------------------------------------------------------------------------------- /docs/src/getting-started/index.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | To get started with buffrs, install it and set up your first package. 4 | 5 | * [Installation](installation.md) 6 | * [First steps with Buffrs](first-steps.md) 7 | -------------------------------------------------------------------------------- /docs/src/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ### Install Buffrs 4 | 5 | The easiest way to get `buffrs` is to install the current stable release from 6 | [crates.io] using: 7 | 8 | ```bash 9 | cargo install buffrs 10 | ``` 11 | 12 | As of right now you are required to authenticate yourself against your private 13 | artifactory instance (which will be replaced by the Buffrs Registry in Q4 14 | 2023). 15 | 16 | Logging in to your instance is done using the following `buffrs` command: 17 | 18 | ```bash 19 | buffrs login --registry https://.jfrog.io/artifactory 20 | ``` 21 | 22 | You will be prompted for an artifactory identity token which you can create 23 | within the artifactory user interface or programmatically through terraform. 24 | 25 | ### Build and Install Buffrs from Source 26 | 27 | As alternative installation method you can clone the [Buffrs Repository] and 28 | install it locally using Cargo (`cargo install --path .`). 29 | 30 | [crates.io]: https://crates.io 31 | [Buffrs Repository]: https://github.com/helsing-ai/buffrs 32 | -------------------------------------------------------------------------------- /docs/src/guide/buffrs-home.md: -------------------------------------------------------------------------------- 1 | ## Buffrs Home 2 | 3 | `$HOME/.buffrs` 4 | 5 | The buffrs home directory is a place for global configuration and data 6 | belonging to buffrs such as credentials and a cache. You should not need to 7 | interact with this folder manually. 8 | 9 | The home directory that is used by buffrs can be configured via the 10 | `BUFFRS_HOME` environment variable. This enables you do override the default 11 | location in case you want to keep your home directory clean. 12 | -------------------------------------------------------------------------------- /docs/src/guide/consuming-packages.md: -------------------------------------------------------------------------------- 1 | ## Consuming Packages 2 | 3 | As described in the [package types](./package-types.md) section you can declare 4 | dependencies through your project's `Proto.toml`. This is true for both 5 | libraries and APIs. But what if you want to implement your server and you need 6 | to consume buffrs packages that contain your type and service definitions? 7 | 8 | So there are three scenarios in which you would want to depend on other packages: 9 | 10 | a) You are defining a library and you want to make use of an external type 11 | coming from another library (e.g. `google` for basic types such as 12 | `google.None`). 13 | b) You are defining an API and you want to use libraries to reuse common types 14 | for that domain (e.g. `time` or `physics`) 15 | c) You are implementing your server and you want to get access to API 16 | definitions to generate bindings. 17 | 18 | The good news are: They are all achieved in a similar fashion. You make use of 19 | the `[dependencies]` key in your manifest to declare the packages that your 20 | projects needs – either to publish another package or to compile to protos to 21 | bindings. 22 | 23 | ### Examples 24 | 25 | #### Libraries & APIs 26 | 27 | > This section is identical for libraries and APIs. 28 | 29 | An example of the `time` library reusing the `google` library: 30 | 31 | ``` 32 | [package] 33 | name = "time" 34 | type = "lib" 35 | version = "1.0.0" 36 | 37 | [dependencies] 38 | google = { version = "=1.0.0", registry = "", repository = " } 39 | ``` 40 | 41 | Running `buffrs install` yields you with the following filesystem: 42 | 43 | 44 | ```text 45 | time 46 | ├── Proto.toml 47 | └── proto 48 | ├── time.proto 49 | └── vendor 50 | ├── time 51 | ├ └── .. 52 | └── google 53 | ├── any.proto 54 | ├── .. 55 | ├── struct.proto 56 | └── timestamp.proto 57 | ``` 58 | 59 | You can now develop your library and publish it using `buffrs publish`. 60 | 61 | ##### Servers 62 | 63 | If you want to implement your server and thus use e.g. a `logging` API the only 64 | major difference is the lack of the `[package]` section in your manifest. 65 | 66 | ``` 67 | [dependencies] 68 | logging = { version = "=1.0.0", registry = "", repository = " } 69 | ``` 70 | 71 | Running a `buffrs install` yields you the very same as above, except for the 72 | omitted local package and the `logging` dependency instead of `time`. 73 | 74 | ```text 75 | . 76 | ├── Proto.toml 77 | └── proto 78 | └── vendor 79 | └── logging 80 | └── logger.proto 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/src/guide/creating-a-package.md: -------------------------------------------------------------------------------- 1 | ## Creating a Package 2 | 3 | Creating a new, consumable, Buffrs package can be done in four steps: 4 | 5 | 1. Initialize a project using [`buffrs init`](../commands/buffrs-init.md) 6 | 2. Set metadata, package type, version correctly and declare the 7 | [dependencies](./dependencies.md) that you want to use. 8 | 3. Declare `message`, `enum` and `service` types you want to publish in the 9 | newly created `proto` folder using `.proto` files. 10 | 4. Publish to a registry using [`buffrs publish`](../commands/buffrs-publish.md). 11 | 12 | ### Example: Publishing `physics` 13 | 14 | #### Initialize Your Project 15 | 16 | Start by initializing a new Buffrs project using the buffrs init command. This 17 | will set up the basic structure for your package and allow you to manage your 18 | package's metadata and dependencies. 19 | 20 | ```bash 21 | mkdir physics 22 | cd physics 23 | buffrs init --lib 24 | ``` 25 | 26 | #### Define Package Metadata and Dependencies 27 | 28 | In your project folder, locate the `Proto.toml` file. This is where you'll 29 | specify your package's metadata and dependencies. Open this file in a text 30 | editor. 31 | 32 | Here's an example `Proto.toml` file: 33 | 34 | ```toml 35 | # Package metadata 36 | [package] 37 | package = "physics" 38 | version = "1.0.0" 39 | type = "lib" 40 | description = "A library containing physic related types" 41 | 42 | # Declare dependencies (none in this case) 43 | [dependencies] 44 | ``` 45 | 46 | #### Define Message, Enum, and Service Types 47 | 48 | Inside your project directory, `buffrs init` created a `proto` folder. This is 49 | where you will store your `.proto` files that define your library or api. 50 | 51 | An example file structure: 52 | 53 | ```text 54 | physics 55 | ├── Proto.toml 56 | └── proto 57 | ├── temperature.proto 58 | ├── mass.proto 59 | └── vendor 60 | ``` 61 | 62 | Write your Protocol Buffer definitions in these `.proto` files. Here's a simple 63 | example of the `temperature.proto` file that could be in a physics library: 64 | 65 | ```protobuf 66 | syntax = "proto3"; 67 | 68 | package physics.temperature; 69 | 70 | // Define temperature units 71 | enum TemperatureUnit { 72 | CELSIUS = 0; 73 | FAHRENHEIT = 1; 74 | KELVIN = 2; 75 | } 76 | 77 | // Define a message for temperature 78 | message Temperature { 79 | double value = 1; // Temperature value 80 | TemperatureUnit unit = 2; // Temperature unit (Celsius, Fahrenheit, Kelvin) 81 | } 82 | ``` 83 | 84 | #### Publish Your Package 85 | 86 | Once you've set up your Buffrs package and defined your Protocol Buffer 87 | definitions, you can publish it to a registry using the buffrs publish command. 88 | Make sure you're logged in to the registry if required. 89 | 90 | ```bash 91 | buffrs publish --registry https://your.registry.com --repository tutorial 92 | ``` 93 | 94 | Your package will be uploaded to the registry, and others can now consume it 95 | using Buffrs. 96 | 97 | Congratulations! You've successfully published your Buffrs package. Other 98 | developers can now use your Protocol Buffer definitions by adding your package 99 | as a dependency in their Buffrs projects. 100 | 101 | That's it! You've created a Buffrs package that others can easily consume. 102 | Remember to keep your package up-to-date and well-documented to make it even 103 | more valuable to the community. 104 | -------------------------------------------------------------------------------- /docs/src/guide/import-system.md: -------------------------------------------------------------------------------- 1 | ## Import System 2 | 3 | To reuse already defined messages, protobuf files can be imported from both 4 | dependencies and the managed package itself. Unique identification of the files 5 | is made available through the name of the package as declared in ``Proto.toml`` 6 | which is used as root of the imports. 7 | 8 | For a dependency ``units``: 9 | 10 | ``` 11 | units 12 | ├── Proto.toml 13 | ├── weight.proto 14 | └── length.proto 15 | ``` 16 | 17 | and a package named ``physic``: 18 | 19 | ``` 20 | physic 21 | ├── Proto.toml 22 | └── proto 23 | ├── root.proto 24 | ├── length.proto 25 | └── calculations 26 | ├── distance.proto 27 | └── graph.proto 28 | ``` 29 | 30 | messages can be imported from both packages relative to their root: 31 | 32 | ```proto 33 | // root.proto 34 | syntax = "proto3"; 35 | 36 | package physic; 37 | 38 | import "physic/length.proto"; 39 | import "physic/calculations/distance.proto"; 40 | import "units/length.proto"; 41 | 42 | message Lengths { 43 | units.Meter meter = 1; 44 | physic.Parsec parsec = 2; 45 | physic.calculations.Haversine = 3; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/src/guide/index.md: -------------------------------------------------------------------------------- 1 | ## Buffrs Guide 2 | 3 | This guide will give you all that you need to know about how to use 4 | Buffrs to develop and consume protocol buffer packages. 5 | 6 | * [Why Buffrs Exists](./why-buffrs-exists.md) 7 | * [The Framework](./the-framework.md) 8 | * [Package Types](./package-types.md) 9 | * [Dependencies](./dependencies.md) 10 | * [Creating a Package](./creating-a-package.md) 11 | * [Consuming Packages](./consuming-packages.md) 12 | * [Local Dependencies](./local-dependencies.md) 13 | * [Import System](./import-system.md) 14 | * [Project Layout](./project-layout.md) 15 | * [Manifest vs Lockfile](./manifest-vs-lockfile.md) 16 | * [Continuous Integration](./continuous-integration.md) 17 | * [Buffrs Home](./buffrs-home.md) 18 | -------------------------------------------------------------------------------- /docs/src/guide/local-dependencies.md: -------------------------------------------------------------------------------- 1 | ## Local Dependencies 2 | 3 | When working on larger projects or in monorepo projects setups you may find 4 | yourself in the situation to consume a locally defined buffrs project. 5 | 6 | Imagine the following project setup: 7 | 8 | ``` 9 | mono 10 | ├── build.rs 11 | ├── Cargo.toml 12 | ├── Proto.toml 13 | ├── Proto.toml 14 | ├── proto 15 | | └── mono.proto 16 | └── src 17 | └── main.rs 18 | ``` 19 | 20 | In this scenario the buffrs project for `mono-api` and the cargo project for 21 | the `mono-server` are setup in the very same directory, which is totally fine 22 | as long as this server does not require other buffrs api packages to be 23 | compiled! 24 | 25 | #### Problem 26 | 27 | Adding a dependency on other (unrelated api packages to `mono-api`) is 28 | complicated in the above scenario because its not clear to buffrs whether you 29 | are trying to reuse third party api definitions for your api or just wanting to 30 | install protos for compilation. 31 | 32 | Hence buffrs will throw you an error if you try to publish an api package with 33 | dependencies on other apis! 34 | 35 | ``` 36 | Error: × failed to publish `mono-api` to `http:///` 37 | ╰─▶ depending on API packages is not allowed 38 | ``` 39 | 40 | #### Solution 41 | 42 | Gladly buffrs offers a builtin solution for this! You can separate the 43 | `mono-api` buffrs package (used to publish your api) from the `mono-server` 44 | buffrs projects (used to install protos for compiling the server). 45 | 46 | A monorepo setup here could look like this: 47 | 48 | ``` 49 | mono 50 | ├── mono-api 51 | | ├── Proto.toml 52 | | └── proto 53 | | └── mono.proto 54 | └── mono-server 55 | ├── build.rs 56 | ├── Cargo.toml 57 | ├── Proto.toml 58 | ├── proto 59 | | └── vendor 60 | └── src 61 | └── main.rs 62 | ``` 63 | 64 | Where `mono/mono-api/Proto.toml` has this content: 65 | 66 | ``` 67 | edition = "0.9" 68 | 69 | [package] 70 | type = "api" 71 | name = "mono-api" 72 | version = "0.1.0" 73 | ``` 74 | 75 | And `mono/mono-server/Proto.toml` has this content: 76 | 77 | ``` 78 | edition = "0.9" 79 | 80 | [dependencies] 81 | mono-api = { path = "../api" } 82 | third-party-api = { version = "=1.0.0", repository = "some-repo", registry = "http://..." } 83 | ``` 84 | 85 | This enables you to: 86 | 87 | - Independently publish `mono-api` using `buffrs publish` / `buffrs package` 88 | - Independently declare dependencies for `mono-server` 89 | 90 | #### Caveats 91 | 92 | Please note that projects containing any local dependencies can not be 93 | published anymore. The ability to declare local filesystem dependencies is 94 | mainly useful for the above scenario where you want to install buffrs packages 95 | for your server from different locations on the filesystem (monorepo, git 96 | submodules etc). 97 | -------------------------------------------------------------------------------- /docs/src/guide/manifest-vs-lockfile.md: -------------------------------------------------------------------------------- 1 | ## Manifest (`Proto.toml`) vs Lockfile (`Proto.lock`) 2 | 3 | ### Manifest – `Proto.toml` 4 | 5 | **Purpose**: The Manifest (`Proto.toml`) serves as a specification file 6 | for the current package. It includes metadata, dependencies, and other 7 | package-related information. 8 | 9 | **Contents**: 10 | 11 | - **Package Name**: A unique name for the package. 12 | - **Version**: Specifies the package version. 13 | - **Type**: Specifies the type of this package (see [Package 14 | Types](./package-types.md)). 15 | - **Description**: Provides a brief description of the package. 16 | - **Metadata**: Additional metadata like package category, tags, and any 17 | other relevant package details. 18 | - **Dependencies**: Contains package names, version constraints, registry 19 | locations etc of the dependencies of the current package. 20 | 21 | **Usage**: The `Proto.toml` is used to define the package's characteristics, 22 | metadata, and its dependencies, making it a comprehensive specification format. 23 | This file is included in compressed package artefacts and distributed 24 | alongside. For a usage guide see [Creating A New 25 | Package](./creating-a-new-package.md). 26 | 27 | ### Lockfile – `Proto.lock` 28 | 29 | **Purpose**: The Lockfile (`Proto.lock`) is a separate, autogenerated and 30 | automanaged, file that records the exact versions of packages, including their 31 | transitive dependencies, which have been successfully resolved and installed. 32 | 33 | **Contents**: It contains a detailed record of the package versions 34 | used, the registry or package source used during the installation and 35 | cryptographic hashes to ensure package integrity. 36 | 37 | **Usage**: The lockfile is crucial for ensuring the reproducibility of 38 | installations. It guarantees that the same package versions are installed, 39 | regardless of changes in the upstream package registry, ensuring consistency 40 | across different installations. 41 | -------------------------------------------------------------------------------- /docs/src/guide/package-types.md: -------------------------------------------------------------------------------- 1 | ## Package Types 2 | 3 | Buffrs makes distinctions between two different packages: 4 | 5 | ```toml 6 | [package] 7 | type = "lib" | "api" 8 | ``` 9 | 10 | This is used in order to fuel composition and type reuse across APIs and thus 11 | enable shared types + wire compatibility. 12 | 13 | ### `lib` – Libraries 14 | 15 | Libraries contain atomic and composite type definitions that describe a domain. 16 | (e.g. `physics`, `auth`, `time` etc.). This pattern is really useful for 17 | scaling systems and maintaining dozens of APIs as they can share types and thus 18 | get the aforementioned benefits of source code and wire compatibility for free. 19 | 20 | An example of a proto library named `time` that depends on `google`: 21 | 22 | ```toml 23 | [package] 24 | name = "time" 25 | type = "lib" 26 | version = "1.0.0" 27 | 28 | [dependencies] 29 | google = { version = "=1.0.0", ... } 30 | ``` 31 | 32 | ```proto 33 | syntax = "proto3"; 34 | 35 | package time; 36 | 37 | import "google/timestamp.proto"; 38 | 39 | /// A timestamp wrapper for various formats 40 | message Time { 41 | oneof format { 42 | string rfc3339 = 1; 43 | uint64 unix = 2; 44 | google.protobuf.Timestamp google = 3; 45 | .. 46 | } 47 | } 48 | ``` 49 | 50 | ### `api` – APIs 51 | 52 | APIs are the next logical building block for real world systems – they define 53 | services and RPCs that your server can implement. You can use the 54 | aforementioned libraries to fuel your development / API definition experience. 55 | 56 | A good example of an API could be an imaginary `logging` service that makes use 57 | of the just declared `time.Time`: 58 | 59 | ```toml 60 | [package] 61 | name = "logging" 62 | type = "api" 63 | version = "1.0.0" 64 | 65 | [dependencies] 66 | time = { version = "=1.0.0", ... } 67 | ``` 68 | 69 | ```proto 70 | syntax = "proto3"; 71 | 72 | package logging; 73 | 74 | import "time/time.proto"; 75 | 76 | service Logging { 77 | rpc critical(LogInput) returns (LogOutput); 78 | rpc telemetry(LogInput) returns (LogOutput); 79 | rpc healthiness(HealthInput) returns (HealthOutput); 80 | } 81 | 82 | message LogInput { string context = 1; time.Time timestamp = 2; } 83 | message LogOutput { } 84 | 85 | message HealthInput { bool db = 1; } 86 | message HealthOutput { } 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/src/guide/project-layout.md: -------------------------------------------------------------------------------- 1 | ## Project Layout 2 | 3 | To get an understanding of the project layout that buffers uses it is helpful to 4 | start in a clean manner and introspect the outcome. 5 | 6 | Lets create a new clean directory initialize for our `physic` library. 7 | 8 | ``` 9 | $ mkdir physic 10 | $ cd physic 11 | $ buffrs init --lib 12 | ``` 13 | 14 | This will initialize the following project structure: 15 | 16 | ``` 17 | physic 18 | ├── Proto.toml 19 | └── proto 20 | └── vendor 21 | ``` 22 | 23 | This will create the `Proto.toml` file which is the manifest file that buffrs 24 | uses. The `proto` directory, which is the source directory for all your protocol 25 | buffer definitions and the `proto/vendor` directory, which contains external 26 | protocol buffers. 27 | 28 | **Important:** The vendor directory is managed by Buffrs, all manual changes 29 | will be overridden / can cause unreproducible behavior. 30 | -------------------------------------------------------------------------------- /docs/src/guide/the-framework.md: -------------------------------------------------------------------------------- 1 | ## The Framework 2 | 3 | To understand the distribution and decomposition framework that Buffrs provides 4 | it is useful to understand which properties of API management systems are 5 | desirable and why. Key aspects include: 6 | 7 | ### Versioning 8 | 9 | A versioning scheme for APIs — similar to versioned library dependencies — 10 | explicitly encodes compatibility properties. This allows developers to make 11 | either backwards-compatible or breaking changes and encode the compatibility 12 | guarantees in the API version: a minor version upgrade “just works”, while 13 | a new major version API may require manual migration/adaption in the consuming 14 | server/client implementations. 15 | 16 | ### Source Compatibility 17 | 18 | Given versioned protocol buffer APIs with explicit compatibility guarantees, it 19 | is desirable to have a system in which wire-format compatible APIs are also 20 | source-code compatible. This means that engineers can update minor patches 21 | automatically and that APIs that build upon the same protocol buffer types can 22 | be used with the same generated code types. This is especially important in 23 | strict languages like Rust (due to, e.g., the orphan rule). 24 | 25 | ### Composition 26 | 27 | Enabling engineers to reuse and combine code in order to build new 28 | systems from existing building blocks is a key feature. A common composition 29 | scheme with protocol buffers is to use a set of base messages and data types 30 | across many APIs. 31 | 32 | ### Discoverability 33 | 34 | Before engineers can reuse and compose protocol buffers, they 35 | need to be able to discover and understand what APIs exist and how to use them. 36 | Discoverability is a significant accelerator for engineering productivity and 37 | helps developers stay abreast of the evolution of APIs and architecture. 38 | 39 | ### 40 | 41 | --- 42 | 43 | ### Protocol Buffers as a First Class Citizen 44 | 45 | Buffrs decided to take a unique approach compared to other protocol buffer 46 | management systems (or systems capable of distributing protocol buffers) like 47 | [buf] and [bazel]. Buffrs is essentially a package manager for protocol 48 | buffers. Protocol buffers are treated as a _first class citizen_ 49 | within Buffrs – which means that they are seen as distributable units called 50 | packages. 51 | 52 | Complex software projects frequently turn out to depend on different versions 53 | of the same APIs over time and individual components in those systems may have 54 | diverging compatibility guarantees. Internal services may break their API 55 | backwards compatibility, way more frequent than external gateways that serve 56 | millions of users. 57 | 58 | The three fundamental ideas of Buffrs to enable a stable management approach 59 | for scenarios like the above are: 60 | 61 | #### Buffrs Packages 62 | 63 | A closed and meaningful unit of protocol buffers which enables either 64 | productivity through shared types (e.g., Google's `google.protobuf.Timestamp`) 65 | or describes a domain / API (e.g., `service GeoLocator`). 66 | 67 | #### Buffrs Registry 68 | 69 | A central registry for managing and hosting packages, documentation, enabling 70 | engineers to search and find and to implement access control. 71 | 72 | #### Local Code Generation 73 | 74 | The last block is local code generation. This enables projects to freely 75 | diverge in their actual implementations by choosing a code generation 76 | implementation which fits their specific needs while still maintaining complete 77 | wire compatibility. This prevents language lock-ins and makes any version of 78 | any package usable for any language that exists today or might exist in the 79 | future (given it has a protobuf code generator). 80 | 81 | [buf]: https://buf.build/ 82 | [bazel]: https://bazel.build/ 83 | -------------------------------------------------------------------------------- /docs/src/guide/why-buffrs-exists.md: -------------------------------------------------------------------------------- 1 | ## Why Buffrs Exists 2 | 3 | Modern gRPC based software platforms make extensive use of protocol buffers to 4 | define and implement inter-service APIs. While the open-source protocol buffers 5 | ecosystem makes it very easy to get started with a few services and a handful 6 | of messages, recurring difficulties occur with scaling gRPC APIs beyond 7 | immediate repository boundaries for two main reasons: 8 | 9 | 1. Limited tooling for packaging, publishing, and distributing protocol buffer 10 | definitions. 11 | 2. The lack of widely-adopted best practices for API decomposition and reuse. 12 | 13 | To overcome these problems we developed Buffrs - a package manager for protocol 14 | buffers. Using Buffrs, engineers package protocol buffer definitions, publish 15 | them to a shared registry, and integrate them seamlessly into their projects 16 | using versioned API dependencies and batteries-included build tool integration. 17 | 18 | For a detailed differentiation against existing approaches (like [buf], [bazel] 19 | and [git submodules]) and architecture deep dive take a look at the 20 | _[announcement post.]_ 21 | 22 | 23 | 24 | [announcement post.]: https://blog.helsing.ai/buffrs-a-package-manager-for-protocol-buffers-1-2-aaf7c00153d2 25 | [buf]: https://buf.build/ 26 | [bazel]: https://bazel.build/ 27 | [git submodules]: https://git-scm.com/book/en/v2/Git-Tools-Submodules 28 | -------------------------------------------------------------------------------- /docs/src/images/dependency-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/docs/src/images/dependency-graph.png -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # The Buffrs Book 2 | 3 | ![Buffrs Logo](images/buffrs.svg) 4 | 5 | Buffrs is a protocol buffers package manager. Buffrs downloads your projects 6 | protocol buffer dependencies, creates distributable packages for libraries and 7 | apis, and uploads them to a package registry. 8 | 9 | ### Sections 10 | 11 | **[Getting Started](getting-started/index.md)** 12 | 13 | To get started with Buffrs, install it and set 14 | up your first package. 15 | 16 | **[Buffrs Guide](guide/index.md)** 17 | 18 | An in-depth guide on how to use Buffrs to 19 | distribute protocol buffers. 20 | 21 | **[Buffrs Integrations](integration/index.md)** 22 | 23 | Learn how to integrate Buffrs into existing 24 | build tools for a seamless development 25 | experience. 26 | 27 | **[Buffrs Reference](reference/index.md)** 28 | 29 | Detailed aspects of Buffrs behavior, including 30 | advanced configuration and troubleshooting 31 | resources. 32 | 33 | **[Buffrs Registry Reference](registry/index.md)** 34 | 35 | Advanced resources on the Buffrs Registry. 36 | 37 | **[Buffrs Commands](commands/index.md)** 38 | 39 | Detailed documentation of the commands of the 40 | Buffrs CLI. 41 | 42 | [buff.rs]: https://buff.rs/ 43 | [GitHub]: https://github.com/helsing-ai/buffrs/tree/main/src/docs 44 | -------------------------------------------------------------------------------- /docs/src/integrations/index.md: -------------------------------------------------------------------------------- 1 | ## Integrations 2 | 3 | Having a standalone distribution system and package manager for protocol 4 | buffers is the first step towards solving [the 5 | problem that engineers are facing](../guide/why-buffrs-exists.md). 6 | 7 | Ensuring a _works out of the box_ experience through integrating into common 8 | build systems is the next one. 9 | 10 | Currently we are providing the following integrations: 11 | 12 | If you are missing your favorite build system, please create an issue on GitHub 13 | to request it – or ideally help us by building or researching parts the 14 | integration! 15 | -------------------------------------------------------------------------------- /docs/src/migration/architectures.md: -------------------------------------------------------------------------------- 1 | ## Architectures 2 | 3 | ### Consumer Side 4 | 5 | ### Producer Side 6 | 7 | --- 8 | 9 | ### Migrating Producer Side Code Generation 10 | -------------------------------------------------------------------------------- /docs/src/migration/continuous-integration.md: -------------------------------------------------------------------------------- 1 | ## Continuous Integration 2 | 3 | To utilize continuous integration for your Buffrs package (e.g. to automate code review and publishing 4 | of your packages) you can utilize the following templates for GitHub Actions and GitLab CI: 5 | 6 | ### GitHub Actions 7 | 8 | ```yaml 9 | name: Buffrs 10 | 11 | on: 12 | push: 13 | branches: 14 | - '*' 15 | tags: 16 | - '*' 17 | 18 | env: 19 | REGISTRY: https://.jfrog.io/artifactoy 20 | REPOSITORY: your-artifactory-repo 21 | 22 | jobs: 23 | verify: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v2 29 | 30 | - name: Install Rust 31 | uses: actions-rs/toolchain@v1 32 | with: 33 | profile: minimal 34 | toolchain: nightly 35 | 36 | - name: Set up Rust environment 37 | run: | 38 | rustup target add aarch64-unknown-linux-gnu 39 | shell: bash 40 | 41 | - name: Verify 42 | run: | 43 | cargo install --force buffrs 44 | echo $TOKEN | buffrs login --registry $REGISTRY 45 | buffrs lint 46 | env: 47 | TOKEN: ${{ secrets.BUFFRS_TOKEN }} 48 | shell: bash 49 | 50 | publish: 51 | runs-on: ubuntu-latest 52 | needs: build 53 | if: startsWith(github.ref, 'refs/tags/') 54 | 55 | steps: 56 | - name: Checkout code 57 | uses: actions/checkout@v2 58 | 59 | - name: Publish on tag 60 | run: | 61 | cargo install --force buffrs 62 | echo $TOKEN | buffrs login --registry $REGISTRY 63 | buffrs publish --registry $REGISTRY --repository $REPOSITORY 64 | env: 65 | TOKEN: ${{ secrets.BUFFRS_TOKEN }} 66 | shell: bash 67 | 68 | ``` 69 | 70 | ### GitLab CI 71 | 72 | ```yaml 73 | stages: 74 | - verify 75 | - publish 76 | 77 | variables: 78 | TOKEN: $BUFFRS_TOKEN # Your secret artifactory token 79 | REGISTRY: https://.jfrog.io/artifactory 80 | REPOSITORY: your-artifactory-repo 81 | 82 | verify: 83 | stage: verify 84 | script: 85 | - cargo install buffrs 86 | - echo $TOKEN | buffrs login --registry $REGISTRY 87 | - buffrs lint 88 | only: 89 | - branches 90 | 91 | publish: 92 | stage: publish 93 | script: 94 | - cargo install buffrs 95 | - echo $TOKEN | buffrs login --registry $REGISTRY 96 | - buffrs publish --registry $REGISTRY --repository $REPOSITORY 97 | only: 98 | - tags 99 | ``` 100 | -------------------------------------------------------------------------------- /docs/src/migration/index.md: -------------------------------------------------------------------------------- 1 | ## Migration Guide 2 | 3 | Once you read the initial guide on how buffrs works and how to use it, you can 4 | follow this guide to migrate your existing protobuf code into buffrs and use 5 | it as the distribution mechanism! 6 | 7 | * [Continuous Integration](./continuous-integration.md) 8 | -------------------------------------------------------------------------------- /docs/src/reference/build-scripts.md: -------------------------------------------------------------------------------- 1 | # Build Configuration 2 | -------------------------------------------------------------------------------- /docs/src/reference/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ## Authentication 4 | 5 | Buffrs uses a local credential storage for authenticating with registries. The [`login`](../commands/buffrs-login.md) command can be used to add new credentials to the storage. Once saved, credentials are automatically used for authenticating with the registry they are associated with. Registries are identified by their URL. 6 | 7 | Note that credentials are optional, if they are missing for a given registry URL, no authentication is attempted. 8 | 9 | ## TLS configuration 10 | 11 | Buffrs will automatically pick up the `SSL_CERT_FILE` environment variable if it's been set, and attempt to use the native subsystem to parse and load the specified root certificate into the certificate store. No additional configuration is needed to apply custom root certificates. 12 | 13 | ## Proxy support 14 | 15 | Buffrs will automatically pick up on `HTTP_PROXY` and `HTTPS_PROXY` environment variables if they've been set, and use the specified proxy URLs for the associated remote requests. No additional configuration is needed. -------------------------------------------------------------------------------- /docs/src/reference/editions.md: -------------------------------------------------------------------------------- 1 | ## Editions 2 | 3 | Editions of buffrs mark a specific evolutionary state of the package manager. 4 | The edition system exists to allow for fast development of buffrs while 5 | allowing you to already migrate existing protobufs to buffrs even though it 6 | has not yet reached a stable version. 7 | 8 | Editions can be either explicitly stated in the `Proto.toml` or are 9 | automatically inlined once a package is created using buffrs. This ensures that 10 | you dont need to care about them as a user but get the benefits. 11 | 12 | > Note: If you release a package with an edition that is incompatible with 13 | > another one (e.g. if `0.7` is incompatible with `0.8`) you will need to 14 | > re-release the package for the new edition (by bumping the version, or 15 | > overriding the existing package) to regain compatibility. 16 | 17 | You may see errors like this if you try to consume (or work on) a package of 18 | another edition. 19 | 20 | ``` 21 | Error: × could not deserialize Proto.toml 22 | ╰─▶ TOML parse error at line 1, column 1 23 | | 24 | 1 | edition = "0.7" 25 | | ^^^^^^^^^^^^^^^ 26 | unsupported manifest edition, supported editions of 0.8.0 are: 0.8 27 | ``` 28 | 29 | ### Canary Editions 30 | 31 | ```toml 32 | edition = "0.7" 33 | ``` 34 | 35 | Canary editions are short-lived editions that are attached to a specific 36 | minor release of buffrs in the `0.x.x` version range. The edition name contains 37 | the minor version it is usable for. E.g. the edition `0.7` is usable / 38 | supported by all `0.7.x` buffrs releases. Compatibility beyond minor releases 39 | is not guaranteed as fundamental breaking changes may be introduced between 40 | editions. 41 | -------------------------------------------------------------------------------- /docs/src/reference/environment-variables.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | -------------------------------------------------------------------------------- /docs/src/reference/index.md: -------------------------------------------------------------------------------- 1 | # Buffrs Reference 2 | 3 | This section covers detailed documentation on the Buffrs Ecosystem / Framework. 4 | 5 | ## Index 6 | 7 | * [Editions](editions.md) 8 | * [Protocol Buffer Rules](protocol-buffer-rules.md) 9 | -------------------------------------------------------------------------------- /docs/src/reference/lockfile.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/docs/src/reference/lockfile.md -------------------------------------------------------------------------------- /docs/src/reference/manifest.md: -------------------------------------------------------------------------------- 1 | # The Manifest Format 2 | -------------------------------------------------------------------------------- /docs/src/reference/overriding-dependencies.md: -------------------------------------------------------------------------------- 1 | # Overriding Dependencies 2 | -------------------------------------------------------------------------------- /docs/src/reference/package-name-spec.md: -------------------------------------------------------------------------------- 1 | # Package Name Specifications 2 | -------------------------------------------------------------------------------- /docs/src/reference/protocol-buffer-rules.md: -------------------------------------------------------------------------------- 1 | ## Protocol Buffer Rules 2 | 3 | This specification defines rules enforced by Buffrs to prevent package 4 | colisions, and provide uniformity and transparency for package consumers. 5 | 6 | Rules with the `00XX` code define the package and filesystem layout, where as 7 | rules with a `01XX` code enforce certain protocol buffer definition rules. 8 | 9 | ### `0000` – Package Name Prefix / Symmetry 10 | 11 | Enforces that the Buffrs Package ID is used as the prefix for all protocol 12 | buffer package declarations. 13 | 14 | So given a Buffrs Package with the ID `physics` this enforces that the package 15 | only contains protocol buffer package declarations matching 16 | `physics|physics.*`; 17 | 18 | A violation would cause type colisions and ambiguity when trying to resolve a 19 | type. 20 | 21 | ### `0010` – Sub-Package Declaration 22 | 23 | Enforces that subpackages are declared through a sensible folder 24 | structure. Given a Buffrs Package with the ID `physics` the protocol buffer 25 | file that declares `package physics.units;` has to be called 26 | `proto/units.proto`. 27 | 28 | Nested subpackages are represented / grouped through folders. So if one wants 29 | to declare `package physics.units.temperature;` the respective file must be 30 | located at `proto/units/temperature.proto`. 31 | 32 | ### `0020` – Root Package Declaration 33 | 34 | Enforces that only one file at a time declares the _root_ package. 35 | 36 | Namely: If a Buffrs Package with the ID `physics` is defined, the 37 | `proto/physics.proto` must declare the the same package in the protocol buffer 38 | syntax through `package physics;`. 39 | -------------------------------------------------------------------------------- /docs/src/reference/publishing.md: -------------------------------------------------------------------------------- 1 | # Publishing on buff.rs 2 | -------------------------------------------------------------------------------- /docs/src/reference/resolver.md: -------------------------------------------------------------------------------- 1 | # Dependency Resolution 2 | -------------------------------------------------------------------------------- /docs/src/reference/semver.md: -------------------------------------------------------------------------------- 1 | # SemVer Compatibility 2 | -------------------------------------------------------------------------------- /docs/src/reference/specifying-dependencies.md: -------------------------------------------------------------------------------- 1 | # Specifying Dependencies 2 | -------------------------------------------------------------------------------- /docs/src/registry/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/docs/src/registry/index.md -------------------------------------------------------------------------------- /docs/src/registry/registry-index.md: -------------------------------------------------------------------------------- 1 | # Registry Index 2 | -------------------------------------------------------------------------------- /docs/src/registry/registry-web-api.md: -------------------------------------------------------------------------------- 1 | # Registry Web API 2 | -------------------------------------------------------------------------------- /docs/src/registry/self-hosting.md: -------------------------------------------------------------------------------- 1 | # Self Hosting 2 | -------------------------------------------------------------------------------- /docs/src/registry/teams.md: -------------------------------------------------------------------------------- 1 | # Creating Teams 2 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "advisory-db": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1733749954, 7 | "narHash": "sha256-2Ug80Uf/oUujxgh02Iy5vTG0V+Ab9+YUHuRLRY0ayiY=", 8 | "owner": "rustsec", 9 | "repo": "advisory-db", 10 | "rev": "ec9ce28714bb38d77a2223e7266df705500a7f11", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "rustsec", 15 | "repo": "advisory-db", 16 | "type": "github" 17 | } 18 | }, 19 | "crane": { 20 | "locked": { 21 | "lastModified": 1733688869, 22 | "narHash": "sha256-KrhxxFj1CjESDrL5+u/zsVH0K+Ik9tvoac/oFPoxSB8=", 23 | "owner": "ipetkov", 24 | "repo": "crane", 25 | "rev": "604637106e420ad99907cae401e13ab6b452e7d9", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "ipetkov", 30 | "repo": "crane", 31 | "type": "github" 32 | } 33 | }, 34 | "flake-utils": { 35 | "inputs": { 36 | "systems": "systems" 37 | }, 38 | "locked": { 39 | "lastModified": 1731533236, 40 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 41 | "owner": "numtide", 42 | "repo": "flake-utils", 43 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "numtide", 48 | "repo": "flake-utils", 49 | "type": "github" 50 | } 51 | }, 52 | "nixpkgs": { 53 | "locked": { 54 | "lastModified": 1733686850, 55 | "narHash": "sha256-NQEO/nZWWGTGlkBWtCs/1iF1yl2lmQ1oY/8YZrumn3I=", 56 | "owner": "NixOS", 57 | "repo": "nixpkgs", 58 | "rev": "dd51f52372a20a93c219e8216fe528a648ffcbf4", 59 | "type": "github" 60 | }, 61 | "original": { 62 | "owner": "NixOS", 63 | "ref": "nixpkgs-unstable", 64 | "repo": "nixpkgs", 65 | "type": "github" 66 | } 67 | }, 68 | "root": { 69 | "inputs": { 70 | "advisory-db": "advisory-db", 71 | "crane": "crane", 72 | "flake-utils": "flake-utils", 73 | "nixpkgs": "nixpkgs", 74 | "rust-overlay": "rust-overlay" 75 | } 76 | }, 77 | "rust-overlay": { 78 | "inputs": { 79 | "nixpkgs": [ 80 | "nixpkgs" 81 | ] 82 | }, 83 | "locked": { 84 | "lastModified": 1744252416, 85 | "narHash": "sha256-Vrs2GxaL0tLi9GCIUrutHgPSr+g7GYCetu7argsNrB4=", 86 | "owner": "oxalica", 87 | "repo": "rust-overlay", 88 | "rev": "2af83121f9d2c5281796e60e2b048906a84b9fac", 89 | "type": "github" 90 | }, 91 | "original": { 92 | "owner": "oxalica", 93 | "repo": "rust-overlay", 94 | "type": "github" 95 | } 96 | }, 97 | "systems": { 98 | "locked": { 99 | "lastModified": 1681028828, 100 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 101 | "owner": "nix-systems", 102 | "repo": "default", 103 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 104 | "type": "github" 105 | }, 106 | "original": { 107 | "owner": "nix-systems", 108 | "repo": "default", 109 | "type": "github" 110 | } 111 | } 112 | }, 113 | "root": "root", 114 | "version": 7 115 | } 116 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | crane.url = "github:ipetkov/crane"; 6 | rust-overlay = { 7 | url = "github:oxalica/rust-overlay"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | advisory-db = { 11 | url = "github:rustsec/advisory-db"; 12 | flake = false; 13 | }; 14 | }; 15 | 16 | outputs = { self, flake-utils, rust-overlay, crane, advisory-db, nixpkgs, }: 17 | let 18 | perSystemOutputs = flake-utils.lib.eachDefaultSystem (system: 19 | let 20 | pkgs = (import nixpkgs) { 21 | inherit system; 22 | overlays = [ (import rust-overlay) ]; 23 | }; 24 | inherit (pkgs) lib callPackage; 25 | rustToolchain = callPackage ./nix/toolchain.nix { }; 26 | 27 | darwinFrameworks = with pkgs.darwin.apple_sdk.frameworks; [ 28 | Security 29 | SystemConfiguration 30 | ]; 31 | 32 | devTools = [ rustToolchain ]; 33 | 34 | dependencies = with pkgs; 35 | [ libiconv ] 36 | ++ lib.lists.optionals stdenv.isDarwin darwinFrameworks; 37 | 38 | nativeBuildInputs = with pkgs; [ pkg-config ] ++ dependencies; 39 | 40 | buildEnvVars = { 41 | NIX_LDFLAGS = [ "-L" "${pkgs.libiconv}/lib" ]; 42 | OPENSSL_NO_VENDOR = 1; 43 | }; 44 | 45 | buffrs = callPackage ./nix/buffrs.nix { 46 | inherit crane advisory-db buildEnvVars nativeBuildInputs 47 | rustToolchain; 48 | 49 | buildInputs = [ rustToolchain ]; 50 | }; 51 | 52 | app = flake-utils.lib.mkApp { drv = buffrs.package; }; 53 | in { 54 | # NB: if this does not build and you need to modify the file, 55 | # please ensure you also make the corresponding changes in the devshell 56 | packages.default = buffrs.package; 57 | apps.default = app; 58 | 59 | lib.vendorDependencies = 60 | pkgs.callPackage ./nix/cache.nix { buffrs = buffrs.package; }; 61 | 62 | devShells.default = pkgs.mkShell ({ 63 | nativeBuildInputs = nativeBuildInputs ++ [ pkgs.protobuf ]; 64 | buildInputs = devTools ++ dependencies; 65 | } // buildEnvVars); 66 | 67 | formatter = with pkgs; 68 | writeShellApplication { 69 | name = "nixfmt-nix-files"; 70 | runtimeInputs = [ fd nixfmt-classic ]; 71 | text = "fd \\.nix\\$ --hidden --type f | xargs nixfmt"; 72 | }; 73 | 74 | checks = ({ 75 | nix-files-are-formatted = pkgs.stdenvNoCC.mkDerivation { 76 | name = "fmt-check"; 77 | dontBuild = true; 78 | src = ./.; 79 | doCheck = true; 80 | nativeBuildInputs = with pkgs; [ fd nixfmt-classic ]; 81 | checkPhase = '' 82 | set -e 83 | # find all nix files, and verify that they're formatted correctly 84 | fd \.nix\$ --hidden --type f | xargs nixfmt -c 85 | ''; 86 | installPhase = '' 87 | mkdir "$out" 88 | ''; 89 | }; 90 | } // buffrs.checks); 91 | 92 | overlays.default = (_final: _prev: { buffrs = app; }); 93 | }); 94 | in perSystemOutputs // { 95 | overlays.default = (final: _prev: { 96 | buffrs = perSystemOutputs.packages.${final.stdenv.system}.default; 97 | }); 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /nix/buffrs.nix: -------------------------------------------------------------------------------- 1 | { pkgs, crane, rustToolchain, advisory-db, buildInputs, nativeBuildInputs 2 | , buildEnvVars }: 3 | let 4 | craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; 5 | src = ../.; 6 | 7 | # Common arguments can be set here to avoid repeating them later 8 | commonArgs = { 9 | inherit src buildInputs nativeBuildInputs; 10 | strictDeps = false; 11 | } // buildEnvVars; 12 | 13 | # Build *just* the cargo dependencies, so we can reuse 14 | # all of that work (e.g. via cachix) when running in CI 15 | cargoArtifacts = craneLib.buildDepsOnly commonArgs; 16 | 17 | # Build the actual crate itself, reusing the dependency 18 | # artifacts from above. 19 | buffrs = craneLib.buildPackage (commonArgs // { 20 | inherit cargoArtifacts; 21 | # we don't want to run tests twice 22 | doCheck = false; 23 | }); 24 | in { 25 | package = buffrs; 26 | 27 | checks = { 28 | # Build the crate as part of `nix flake check` for convenience 29 | buffrs = buffrs; 30 | 31 | # Audit dependencies 32 | buffrs-audit = craneLib.cargoAudit { 33 | inherit src advisory-db; 34 | # ignoring as no workaround is currently available 35 | cargoAuditExtraArgs = "--ignore RUSTSEC-2023-0071"; 36 | }; 37 | 38 | # Audit licenses 39 | buffrs-deny = craneLib.cargoDeny { inherit src; }; 40 | 41 | # Rust unit and integration tests 42 | buffers-nextest = craneLib.cargoNextest (commonArgs // { 43 | inherit cargoArtifacts; 44 | partitions = 1; 45 | partitionType = "count"; 46 | # Ignore tutorial test because it requires git and cargo to work 47 | cargoNextestExtraArgs = 48 | "--filter-expr 'all() - test(=cmd::tuto::fixture)'"; 49 | SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; 50 | }); 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /nix/cache.nix: -------------------------------------------------------------------------------- 1 | { fetchurl, runCommand, lib, buffrs, symlinkJoin }: 2 | 3 | lockfile: 4 | 5 | let 6 | src = runCommand "vendor-lockfile" { } '' 7 | mkdir -p $out 8 | cp ${lockfile} $out/Proto.lock 9 | ''; 10 | 11 | fileRequirementsJson = 12 | runCommand "buffrs-urls" { buildInputs = [ buffrs ]; } '' 13 | cd ${src} 14 | buffrs lock print-files > $out 15 | ''; 16 | 17 | fileRequirements = builtins.fromJSON (builtins.readFile fileRequirementsJson); 18 | 19 | cachePackage = (file: 20 | let 21 | prefix = "sha256:"; 22 | 23 | sha256 = assert lib.strings.hasPrefix prefix file.digest; 24 | lib.strings.removePrefix prefix file.digest; 25 | 26 | tar = fetchurl { 27 | inherit sha256; 28 | url = file.url; 29 | }; 30 | in runCommand "cache-${file.package}" { } '' 31 | mkdir -p $out 32 | cp ${tar} $out/${file.package}.sha256.${sha256}.tgz 33 | ''); 34 | 35 | cache = map cachePackage fileRequirements; 36 | in { 37 | BUFFRS_CACHE = symlinkJoin { 38 | name = "buffrs-cache"; 39 | paths = cache; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /nix/toolchain.nix: -------------------------------------------------------------------------------- 1 | { rust-bin }: 2 | 3 | rust-bin.stable.latest.default.override { 4 | extensions = [ "rust-src" "rust-analyzer" ]; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/credentials.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use miette::{Context, IntoDiagnostic}; 16 | use serde::{Deserialize, Serialize}; 17 | use std::{collections::HashMap, io::ErrorKind, path::PathBuf}; 18 | use tokio::fs; 19 | 20 | use crate::{ 21 | errors::{DeserializationError, FileExistsError, ReadError, SerializationError, WriteError}, 22 | registry::RegistryUri, 23 | ManagedFile, 24 | }; 25 | 26 | /// Filename of the credential store 27 | pub const CREDENTIALS_FILE: &str = "credentials.toml"; 28 | 29 | /// Credential store for storing authentication data 30 | /// 31 | /// This type represents a snapshot of the read credential store. 32 | #[derive(Debug, Default, Clone)] 33 | pub struct Credentials { 34 | /// A mapping from registry URIs to their corresponding tokens 35 | pub registry_tokens: HashMap, 36 | } 37 | 38 | impl Credentials { 39 | fn location() -> miette::Result { 40 | Ok(crate::home().into_diagnostic()?.join(CREDENTIALS_FILE)) 41 | } 42 | 43 | /// Checks if the credentials exists 44 | pub async fn exists() -> miette::Result { 45 | fs::try_exists(Self::location()?) 46 | .await 47 | .into_diagnostic() 48 | .wrap_err(FileExistsError(CREDENTIALS_FILE)) 49 | } 50 | 51 | /// Reads the credentials from the file system 52 | pub async fn read() -> miette::Result> { 53 | // if the file does not exist, we don't need to treat it as an error. 54 | match fs::read_to_string(Self::location()?).await { 55 | Ok(contents) => { 56 | let raw: RawCredentialCollection = toml::from_str(&contents) 57 | .into_diagnostic() 58 | .wrap_err(DeserializationError(ManagedFile::Credentials))?; 59 | Ok(Some(raw.into())) 60 | } 61 | Err(error) if error.kind() == ErrorKind::NotFound => Ok(None), 62 | Err(error) => Err(error) 63 | .into_diagnostic() 64 | .wrap_err(ReadError(CREDENTIALS_FILE)), 65 | } 66 | } 67 | 68 | /// Writes the credentials to the file system 69 | pub async fn write(&self) -> miette::Result<()> { 70 | let location = Self::location()?; 71 | 72 | if let Some(parent) = location.parent() { 73 | // if directory already exists, error is returned but that is fine 74 | fs::create_dir(parent).await.ok(); 75 | } 76 | 77 | let data: RawCredentialCollection = self.clone().into(); 78 | 79 | fs::write( 80 | location, 81 | toml::to_string(&data) 82 | .into_diagnostic() 83 | .wrap_err(SerializationError(ManagedFile::Credentials))? 84 | .into_bytes(), 85 | ) 86 | .await 87 | .into_diagnostic() 88 | .wrap_err(WriteError(CREDENTIALS_FILE)) 89 | } 90 | 91 | /// Loads the credentials from the file system, returning default credentials if 92 | /// they do not exist. 93 | /// 94 | /// Note, this should not create files in the user's home directory, as we should 95 | /// not be performing global stateful operations in absence of a user instruction. 96 | pub async fn load() -> miette::Result { 97 | Ok(Self::read().await?.unwrap_or_else(Credentials::default)) 98 | } 99 | } 100 | 101 | /// Credential store for storing authentication data. Serialization type. 102 | #[derive(Serialize, Deserialize)] 103 | struct RawCredentialCollection { 104 | #[serde(skip_serializing_if = "Vec::is_empty", default)] 105 | credentials: Vec, 106 | } 107 | 108 | /// Credentials for a single registry. Serialization type. 109 | #[derive(Serialize, Deserialize)] 110 | struct RawRegistryCredentials { 111 | uri: RegistryUri, 112 | token: String, 113 | } 114 | 115 | impl From for Credentials { 116 | fn from(value: RawCredentialCollection) -> Self { 117 | let credentials = value 118 | .credentials 119 | .into_iter() 120 | .map(|it| (it.uri, it.token)) 121 | .collect(); 122 | 123 | Self { 124 | registry_tokens: credentials, 125 | } 126 | } 127 | } 128 | 129 | impl From for RawCredentialCollection { 130 | fn from(value: Credentials) -> Self { 131 | let credentials = value 132 | .registry_tokens 133 | .into_iter() 134 | .map(|(uri, token)| RawRegistryCredentials { uri, token }) 135 | .collect(); 136 | 137 | Self { credentials } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use miette::Diagnostic; 2 | 3 | use crate::ManagedFile; 4 | 5 | #[derive(thiserror::Error, Diagnostic, Debug)] 6 | #[error("failed to determine if {0} file exists")] 7 | pub(crate) struct FileExistsError(pub &'static str); 8 | 9 | #[derive(thiserror::Error, Diagnostic, Debug)] 10 | #[error("could not write to {0} file")] 11 | pub(crate) struct WriteError(pub &'static str); 12 | 13 | #[derive(thiserror::Error, Diagnostic, Debug)] 14 | #[error("could not read from {0} file")] 15 | pub(crate) struct ReadError(pub &'static str); 16 | 17 | #[derive(thiserror::Error, Diagnostic, Debug)] 18 | #[error("could not deserialize {0}")] 19 | pub(crate) struct DeserializationError(pub ManagedFile); 20 | 21 | #[derive(thiserror::Error, Diagnostic, Debug)] 22 | #[error("could not serialize {0}")] 23 | pub(crate) struct SerializationError(pub ManagedFile); 24 | 25 | #[derive(thiserror::Error, Diagnostic, Debug)] 26 | #[error("file `{0}` is missing")] 27 | pub(crate) struct FileNotFound(pub String); 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![warn(missing_docs)] 16 | #![doc = include_str!("../README.md")] 17 | 18 | use miette::Diagnostic; 19 | use std::{env, path::PathBuf}; 20 | use thiserror::Error; 21 | 22 | /// Caching implementation 23 | pub mod cache; 24 | /// CLI command implementations 25 | pub mod command; 26 | /// Credential management 27 | pub mod credentials; 28 | /// Common error types 29 | pub mod errors; 30 | /// Lockfile implementation 31 | pub mod lock; 32 | /// Manifest format and IO 33 | pub mod manifest; 34 | /// Packages formats and utilities 35 | pub mod package; 36 | /// Supported registries 37 | pub mod registry; 38 | /// Resolve package dependencies. 39 | pub mod resolver; 40 | /// Validation for buffrs packages. 41 | #[cfg(feature = "validation")] 42 | pub mod validation; 43 | 44 | /// Managed directory for `buffrs` 45 | pub const BUFFRS_HOME: &str = ".buffrs"; 46 | 47 | pub(crate) const BUFFRS_HOME_VAR: &str = "BUFFRS_HOME"; 48 | 49 | #[derive(Error, Diagnostic, Debug)] 50 | #[error("could not determine buffrs home location")] 51 | struct HomeError(#[diagnostic_source] miette::Report); 52 | 53 | fn home() -> Result { 54 | env::var(BUFFRS_HOME_VAR) 55 | .map(PathBuf::from) 56 | .or_else(|_| { 57 | home::home_dir() 58 | .ok_or_else(|| miette::miette!("{BUFFRS_HOME_VAR} is not set and the user's home folder could not be determined")) 59 | }) 60 | .map(|home| home.join(BUFFRS_HOME)) 61 | .map_err(HomeError) 62 | } 63 | 64 | #[derive(Debug)] 65 | pub(crate) enum ManagedFile { 66 | Credentials, 67 | Manifest, 68 | Lock, 69 | } 70 | 71 | impl ManagedFile { 72 | fn name(&self) -> &str { 73 | use credentials::CREDENTIALS_FILE; 74 | use lock::LOCKFILE; 75 | use manifest::MANIFEST_FILE; 76 | 77 | match self { 78 | ManagedFile::Manifest => MANIFEST_FILE, 79 | ManagedFile::Lock => LOCKFILE, 80 | ManagedFile::Credentials => CREDENTIALS_FILE, 81 | } 82 | } 83 | } 84 | 85 | impl std::fmt::Display for ManagedFile { 86 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 87 | f.write_str(self.name()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/package/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod compressed; 16 | mod name; 17 | mod store; 18 | mod r#type; 19 | 20 | pub use self::{compressed::Package, name::PackageName, r#type::PackageType, store::PackageStore}; 21 | -------------------------------------------------------------------------------- /src/package/type.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use serde::{Deserialize, Serialize}; 16 | use strum::{Display, EnumString}; 17 | 18 | /// Package types 19 | #[derive( 20 | Copy, 21 | Clone, 22 | Debug, 23 | Hash, 24 | Serialize, 25 | Deserialize, 26 | PartialEq, 27 | Eq, 28 | PartialOrd, 29 | Ord, 30 | EnumString, 31 | Display, 32 | )] 33 | #[serde(rename_all = "snake_case")] 34 | pub enum PackageType { 35 | /// A library package containing primitive type definitions 36 | Lib, 37 | /// An api package containing message and service definition 38 | Api, 39 | } 40 | 41 | impl TryFrom for PackageType { 42 | type Error = &'static str; 43 | 44 | fn try_from(value: i32) -> Result { 45 | match value { 46 | 1 => Ok(PackageType::Lib), 47 | 2 => Ok(PackageType::Api), 48 | _ => Err("Invalid value, check `PackageType` potential values"), 49 | } 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | 57 | #[test] 58 | fn can_parse_package_type() { 59 | let types = [PackageType::Lib, PackageType::Api]; 60 | for typ in &types { 61 | let string = typ.to_string(); 62 | let parsed: PackageType = string.parse().unwrap(); 63 | assert_eq!(parsed, *typ); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/validation.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Parsed protocol buffer definitions. 16 | mod data; 17 | mod parse; 18 | /// Rules for protocol buffer definitions. 19 | mod rules; 20 | /// Serde utilities. 21 | pub(crate) mod serde; 22 | mod violation; 23 | 24 | pub use violation::*; 25 | 26 | use miette::IntoDiagnostic; 27 | use std::path::Path; 28 | 29 | use self::parse::*; 30 | use crate::manifest::PackageManifest; 31 | 32 | /// Validates buffrs packages. 33 | /// 34 | /// This allows running validations on top of buffrs packages. 35 | pub struct Validator { 36 | parser: parse::Parser, 37 | manifest: PackageManifest, 38 | } 39 | 40 | impl Validator { 41 | /// Create new parser with a given root path. 42 | pub fn new(root: &Path, manifest: &PackageManifest) -> Self { 43 | Self { 44 | parser: Parser::new(root), 45 | manifest: manifest.clone(), 46 | } 47 | } 48 | 49 | /// Add file to be validated. 50 | pub fn input(&mut self, file: &Path) { 51 | self.parser.input(file); 52 | } 53 | 54 | /// Run validation. 55 | /// 56 | /// This produces a list of [`Violation`]. These implement the 57 | /// [`Diagnostic`](miette::Diagnostic) trait which gives them important metadata, such as the 58 | /// severity. 59 | pub fn validate(self) -> miette::Result { 60 | let parsed = self.parser.parse().into_diagnostic()?; 61 | let mut rule_set = rules::all(&self.manifest); 62 | Ok(parsed.check(&mut rule_set)) 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | #[test] 69 | fn can_parse_books() { 70 | use std::path::Path; 71 | let mut parser = super::Parser::new(Path::new("tests/data/parsing")); 72 | parser.input(std::path::Path::new("tests/data/parsing/books.proto")); 73 | let packages = parser.parse().unwrap(); 74 | let expected = include_str!("../tests/data/parsing/books.json"); 75 | let expected = serde_json::from_str(expected).unwrap(); 76 | similar_asserts::assert_eq!(packages, expected); 77 | } 78 | 79 | #[test] 80 | fn can_parse_addressbook() { 81 | use std::path::Path; 82 | let mut parser = super::Parser::new(Path::new("tests/data/parsing")); 83 | parser.input(std::path::Path::new("tests/data/parsing/addressbook.proto")); 84 | let packages = parser.parse().unwrap(); 85 | let expected = include_str!("../tests/data/parsing/addressbook.json"); 86 | let expected = serde_json::from_str(expected).unwrap(); 87 | similar_asserts::assert_eq!(packages, expected); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/validation/data.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{ 16 | collections::{btree_map::Entry, BTreeMap}, 17 | path::PathBuf, 18 | }; 19 | 20 | use diff::Diff; 21 | use miette::Diagnostic; 22 | use protobuf::descriptor::{ 23 | field_descriptor_proto::{Label as FieldDescriptorLabel, Type as FieldDescriptorType}, 24 | *, 25 | }; 26 | use serde::{Deserialize, Serialize}; 27 | use thiserror::Error; 28 | 29 | use crate::validation::{ 30 | rules::{Rule, RuleSet}, 31 | Violations, 32 | }; 33 | 34 | mod entity; 35 | mod r#enum; 36 | mod message; 37 | mod package; 38 | mod packages; 39 | mod service; 40 | 41 | pub use self::{entity::*, message::*, package::*, packages::*, r#enum::*, service::*}; 42 | -------------------------------------------------------------------------------- /src/validation/data/entity.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Entity that can be defined in a protocol buffer file. 18 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Diff)] 19 | #[serde(tag = "kind", rename_all = "snake_case")] 20 | #[diff(attr( 21 | #[derive(Debug)] 22 | #[allow(missing_docs)] 23 | ))] 24 | pub enum Entity { 25 | /// Enumeration. 26 | Enum(Enum), 27 | /// Service definition. 28 | Service(Service), 29 | /// Message definition. 30 | Message(Message), 31 | } 32 | 33 | impl From for Entity { 34 | fn from(entity: Enum) -> Self { 35 | Self::Enum(entity) 36 | } 37 | } 38 | 39 | impl From for Entity { 40 | fn from(entity: Service) -> Self { 41 | Self::Service(entity) 42 | } 43 | } 44 | 45 | impl From for Entity { 46 | fn from(entity: Message) -> Self { 47 | Self::Message(entity) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/validation/data/enum.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Error parsing package. 18 | #[derive(Error, Debug, Diagnostic)] 19 | #[allow(missing_docs)] 20 | pub enum EnumError { 21 | #[error("missing value number")] 22 | ValueNumberMissing, 23 | 24 | #[error("missing value name")] 25 | ValueNameMissing, 26 | } 27 | 28 | /// Enumeration definition. 29 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Diff)] 30 | #[diff(attr( 31 | #[derive(Debug)] 32 | #[allow(missing_docs)] 33 | ))] 34 | pub struct Enum { 35 | /// Variants of this enum. 36 | #[serde(deserialize_with = "crate::validation::serde::de_int_key")] 37 | pub values: BTreeMap, 38 | } 39 | 40 | impl Enum { 41 | /// Attempt to create new from [`EnumDescriptorProto`]. 42 | pub fn new(descriptor: &EnumDescriptorProto) -> Result { 43 | let mut entity = Self::default(); 44 | 45 | for value in &descriptor.value { 46 | entity.add(value)?; 47 | } 48 | 49 | Ok(entity) 50 | } 51 | 52 | /// Add an [`EnumValue`] to this enum definition. 53 | pub fn add(&mut self, value: &EnumValueDescriptorProto) -> Result<(), EnumError> { 54 | let number = value.number.ok_or(EnumError::ValueNumberMissing)?; 55 | self.values.insert(number, EnumValue::new(value)?); 56 | Ok(()) 57 | } 58 | } 59 | 60 | /// Single value for an [`Enum`]. 61 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Diff)] 62 | #[diff(attr( 63 | #[derive(Debug)] 64 | #[allow(missing_docs)] 65 | ))] 66 | pub struct EnumValue { 67 | /// Name of this enum value. 68 | pub name: String, 69 | } 70 | 71 | impl EnumValue { 72 | /// Attempt to create new from [`EnumValueDescriptorProto`]. 73 | pub fn new(descriptor: &EnumValueDescriptorProto) -> Result { 74 | Ok(Self { 75 | name: descriptor.name.clone().ok_or(EnumError::ValueNameMissing)?, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/validation/data/message.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Error converting parsed protobuf fileset into custom representation. 18 | #[derive(Error, Debug, Diagnostic)] 19 | #[allow(missing_docs, clippy::enum_variant_names)] 20 | pub enum MessageError { 21 | #[error("field number missing")] 22 | FieldNumberMissing, 23 | #[error("field name missing")] 24 | FieldNameMissing, 25 | #[error("field type missing")] 26 | FieldTypeMissing, 27 | } 28 | 29 | /// Message definition. 30 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Diff)] 31 | #[diff(attr( 32 | #[derive(Debug)] 33 | #[allow(missing_docs)] 34 | ))] 35 | pub struct Message { 36 | /// Fields defined in this message. 37 | #[serde(deserialize_with = "crate::validation::serde::de_int_key")] 38 | pub fields: BTreeMap, 39 | } 40 | 41 | impl Message { 42 | /// Try to create new [`Message`] from [`DescriptorProto`]. 43 | pub fn new(descriptor: &DescriptorProto) -> Result { 44 | let mut message = Message::default(); 45 | 46 | for field in &descriptor.field { 47 | message.fields.insert( 48 | field.number.ok_or(MessageError::FieldNumberMissing)?, 49 | Field::new(field)?, 50 | ); 51 | } 52 | 53 | Ok(message) 54 | } 55 | } 56 | 57 | /// Field defined in this message. 58 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Diff)] 59 | #[diff(attr( 60 | #[derive(Debug)] 61 | #[allow(missing_docs)] 62 | ))] 63 | pub struct Field { 64 | /// Name of field. 65 | pub name: String, 66 | /// Type of field. 67 | pub type_: FieldType, 68 | /// Label of field. 69 | pub label: Option, 70 | /// Default value. 71 | pub default: Option, 72 | } 73 | 74 | impl Field { 75 | /// Try to create a new [`Field`] from a [`FieldDescriptorProto`]. 76 | fn new(descriptor: &FieldDescriptorProto) -> Result { 77 | Ok(Self { 78 | name: descriptor 79 | .name 80 | .clone() 81 | .ok_or(MessageError::FieldNameMissing)?, 82 | type_: match descriptor 83 | .type_ 84 | .ok_or(MessageError::FieldTypeMissing)? 85 | .enum_value() 86 | { 87 | Ok(value) => value.into(), 88 | Err(number) => FieldType::Unknown(number), 89 | }, 90 | label: match descriptor.label.map(|label| label.enum_value()) { 91 | None => None, 92 | Some(Ok(label)) => Some(label.into()), 93 | Some(Err(number)) => Some(FieldLabel::Unknown(number)), 94 | }, 95 | default: descriptor.default_value.clone(), 96 | }) 97 | } 98 | } 99 | 100 | /// Built-in field types. 101 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Diff)] 102 | #[serde(rename_all = "snake_case")] 103 | #[diff(attr( 104 | #[derive(Debug)] 105 | #[allow(missing_docs)] 106 | ))] 107 | #[allow(missing_docs)] 108 | pub enum FieldType { 109 | Double, 110 | Float, 111 | Int64, 112 | Uint64, 113 | Int32, 114 | Fixed64, 115 | Fixed32, 116 | Bool, 117 | String, 118 | Group, 119 | Message, 120 | Bytes, 121 | Uint32, 122 | Enum, 123 | Sfixed32, 124 | Sfixed64, 125 | Sint32, 126 | Sint64, 127 | Unknown(i32), 128 | } 129 | 130 | impl From for FieldType { 131 | fn from(type_: FieldDescriptorType) -> Self { 132 | use FieldDescriptorType::*; 133 | use FieldType::*; 134 | match type_ { 135 | TYPE_DOUBLE => Double, 136 | TYPE_FLOAT => Float, 137 | TYPE_INT64 => Int64, 138 | TYPE_UINT64 => Uint64, 139 | TYPE_INT32 => Int32, 140 | TYPE_FIXED64 => Fixed64, 141 | TYPE_FIXED32 => Fixed32, 142 | TYPE_BOOL => Bool, 143 | TYPE_STRING => String, 144 | TYPE_GROUP => Group, 145 | TYPE_MESSAGE => Message, 146 | TYPE_BYTES => Bytes, 147 | TYPE_UINT32 => Uint32, 148 | TYPE_ENUM => Enum, 149 | TYPE_SFIXED32 => Sfixed32, 150 | TYPE_SFIXED64 => Sfixed64, 151 | TYPE_SINT32 => Sint32, 152 | TYPE_SINT64 => Sint64, 153 | } 154 | } 155 | } 156 | 157 | /// Field label. 158 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Diff)] 159 | #[serde(rename_all = "snake_case")] 160 | #[diff(attr( 161 | #[derive(Debug)] 162 | #[allow(missing_docs)] 163 | ))] 164 | #[allow(missing_docs)] 165 | pub enum FieldLabel { 166 | Optional, 167 | Required, 168 | Repeated, 169 | Unknown(i32), 170 | } 171 | 172 | impl From for FieldLabel { 173 | fn from(label: FieldDescriptorLabel) -> Self { 174 | use FieldDescriptorLabel::*; 175 | use FieldLabel::*; 176 | match label { 177 | LABEL_OPTIONAL => Optional, 178 | LABEL_REQUIRED => Required, 179 | LABEL_REPEATED => Repeated, 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/validation/data/package.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Protocol buffer package. 18 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Diff)] 19 | #[diff(attr( 20 | #[derive(Debug)] 21 | #[allow(missing_docs)] 22 | ))] 23 | pub struct Package { 24 | /// Name of the package. 25 | pub name: String, 26 | /// File paths where this package is defined. 27 | pub files: Vec, 28 | /// Entities defined in this package. 29 | pub entities: BTreeMap, 30 | } 31 | 32 | /// Error parsing package. 33 | #[derive(Error, Debug, Diagnostic)] 34 | #[allow(missing_docs)] 35 | pub enum PackageError { 36 | #[error("duplicate entity {entity} in package {package}")] 37 | #[diagnostic( 38 | help = "check to make sure you don't define two entities of the same name", 39 | code = "duplicate-entity" 40 | )] 41 | DuplicateEntity { package: String, entity: String }, 42 | 43 | #[error( 44 | "tried to add a file descriptor of package {got} that doest belong to package {expected}" 45 | )] 46 | #[diagnostic(code = "wrong-package")] 47 | WrongPackage { expected: String, got: String }, 48 | 49 | #[error("error parsing message {name}")] 50 | Message { 51 | name: String, 52 | #[source] 53 | #[diagnostic_source] 54 | error: MessageError, 55 | }, 56 | 57 | #[error("error parsing enum {name}")] 58 | Enum { 59 | name: String, 60 | #[source] 61 | #[diagnostic_source] 62 | error: EnumError, 63 | }, 64 | } 65 | 66 | impl Package { 67 | /// Try to create a new one from a [`FileDescriptorProto`]. 68 | pub fn new(descriptor: &FileDescriptorProto) -> Result { 69 | let mut package = Self { 70 | files: vec![descriptor.name().into()], 71 | name: descriptor.package().to_string(), 72 | entities: Default::default(), 73 | }; 74 | 75 | package.parse(descriptor)?; 76 | 77 | Ok(package) 78 | } 79 | 80 | pub fn add(&mut self, descriptor: &FileDescriptorProto) -> Result<(), PackageError> { 81 | if descriptor.package() != self.name { 82 | return Err(PackageError::WrongPackage { 83 | expected: self.name.to_owned(), 84 | got: descriptor.package().to_owned(), 85 | }); 86 | } 87 | 88 | self.parse(descriptor)?; 89 | 90 | Ok(()) 91 | } 92 | 93 | fn parse(&mut self, descriptor: &FileDescriptorProto) -> Result<&Self, PackageError> { 94 | for message in &descriptor.message_type { 95 | self.add_entity( 96 | message.name(), 97 | Message::new(message).map_err(|error| PackageError::Message { 98 | name: message.name().into(), 99 | error, 100 | })?, 101 | )?; 102 | } 103 | 104 | for entity in &descriptor.enum_type { 105 | self.add_entity( 106 | entity.name(), 107 | Enum::new(entity).map_err(|error| PackageError::Enum { 108 | name: entity.name().into(), 109 | error, 110 | })?, 111 | )?; 112 | } 113 | 114 | for entity in &descriptor.service { 115 | self.add_entity(entity.name(), Service {})?; 116 | } 117 | 118 | Ok(self) 119 | } 120 | 121 | /// Try to add an entity. 122 | fn add_entity>(&mut self, name: &str, entity: T) -> Result<(), PackageError> { 123 | match self.entities.entry(name.into()) { 124 | Entry::Vacant(entry) => { 125 | entry.insert(entity.into()); 126 | Ok(()) 127 | } 128 | Entry::Occupied(_entry) => Err(PackageError::DuplicateEntity { 129 | package: self.name.clone(), 130 | entity: name.into(), 131 | }), 132 | } 133 | } 134 | 135 | /// Check this [`Package`] against a [`RuleSet`] for violations. 136 | pub fn check(&self, rules: &mut RuleSet) -> Violations { 137 | rules 138 | .check_package(self) 139 | .into_iter() 140 | .chain( 141 | self.entities 142 | .iter() 143 | .flat_map(|(name, entity)| rules.check_entity(name, entity).into_iter()), 144 | ) 145 | .map(|mut violation| { 146 | violation.location.file = Some(self.files.last().unwrap().display().to_string()); 147 | violation.location.package = Some(self.name.clone()); 148 | violation 149 | }) 150 | .collect() 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/validation/data/packages.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Packages that make up a protocol buffer package. 18 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Diff)] 19 | #[diff(attr( 20 | #[derive(Debug)] 21 | #[allow(missing_docs)] 22 | ))] 23 | pub struct Packages { 24 | /// Packages defined in this protocol buffer package. 25 | pub packages: BTreeMap, 26 | } 27 | 28 | /// Error parsing packages. 29 | #[derive(Error, Debug, Diagnostic)] 30 | #[allow(missing_docs)] 31 | pub enum PackagesError { 32 | #[error("error parsing package {package} in {file}")] 33 | PackageParse { 34 | package: String, 35 | file: String, 36 | #[source] 37 | #[diagnostic_source] 38 | error: PackageError, 39 | }, 40 | #[error("internal data structure error occurred")] 41 | Internal, 42 | } 43 | 44 | impl Packages { 45 | /// Add a package from a [`FileDescriptorProto`]. 46 | pub fn add(&mut self, descriptor: &FileDescriptorProto) -> Result<(), PackagesError> { 47 | let name = descriptor.package().to_string(); 48 | 49 | let Some(package) = self.packages.get_mut(&name) else { 50 | let package = 51 | Package::new(descriptor).map_err(|error| PackagesError::PackageParse { 52 | package: descriptor.package().to_string(), 53 | file: descriptor.name().to_string(), 54 | error, 55 | })?; 56 | 57 | self.packages.insert(name, package); 58 | 59 | return Ok(()); 60 | }; 61 | 62 | package 63 | .add(descriptor) 64 | .map_err(|_| PackagesError::Internal)?; 65 | 66 | Ok(()) 67 | } 68 | 69 | /// Run checks against this. 70 | pub fn check(&self, rules: &mut RuleSet) -> Violations { 71 | let mut violations = rules.check_packages(self); 72 | for package in self.packages.values() { 73 | violations.append(&mut package.check(rules)); 74 | } 75 | violations 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/validation/data/service.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Service definition. 18 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Diff)] 19 | #[diff(attr( 20 | #[derive(Debug)] 21 | #[allow(missing_docs)] 22 | ))] 23 | pub struct Service {} 24 | -------------------------------------------------------------------------------- /src/validation/parse.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::path::Path; 16 | 17 | use crate::validation::data::{Packages, PackagesError}; 18 | 19 | /// Errors parsing `buffrs` packages. 20 | #[derive(thiserror::Error, Debug)] 21 | #[allow(missing_docs)] 22 | pub enum ParseError { 23 | #[error(transparent)] 24 | Parse(#[from] anyhow::Error), 25 | #[error(transparent)] 26 | Adding(#[from] PackagesError), 27 | } 28 | 29 | /// Parser for `buffrs` packages. 30 | pub struct Parser { 31 | parser: protobuf_parse::Parser, 32 | } 33 | 34 | impl Parser { 35 | /// Create new parser with a given root path. 36 | pub fn new(root: &Path) -> Self { 37 | let mut parser = protobuf_parse::Parser::new(); 38 | parser.pure(); 39 | parser.include(root); 40 | 41 | Self { parser } 42 | } 43 | 44 | /// Add file to be processed by this parser. 45 | pub fn input(&mut self, file: &Path) { 46 | self.parser.input(file); 47 | } 48 | 49 | /// Parse into [`Packages`]. 50 | pub fn parse(self) -> Result { 51 | let fds = self.parser.file_descriptor_set()?; 52 | 53 | let packages = fds 54 | .file 55 | .iter() 56 | .try_fold(Packages::default(), |mut packages, item| { 57 | packages.add(item).map(|_| packages) 58 | })?; 59 | 60 | Ok(packages) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/validation/rules.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::fmt::Debug; 16 | 17 | use lib_package::LibPackage; 18 | use package_hierarchy::PackageHierarchy; 19 | 20 | use crate::{ 21 | manifest::PackageManifest, 22 | package::PackageType, 23 | validation::{ 24 | data::*, 25 | violation::{self, *}, 26 | }, 27 | }; 28 | 29 | mod ident_casing; 30 | mod lib_package; 31 | mod package_hierarchy; 32 | mod package_name; 33 | 34 | pub use self::{ident_casing::*, package_name::*}; 35 | 36 | /// Collection of rules. 37 | pub type RuleSet = Vec>; 38 | 39 | /// Rule to enforce for buffrs packages. 40 | pub trait Rule: Debug { 41 | /// Name of this rule. 42 | /// 43 | /// Defaults to the name of the type of this rule. 44 | fn rule_name(&self) -> &'static str { 45 | std::any::type_name::().split("::").last().unwrap() 46 | } 47 | 48 | /// Help text for rule. 49 | fn rule_info(&self) -> &'static str; 50 | 51 | /// Default severity [`Level`] of the rule. 52 | fn rule_level(&self) -> Level { 53 | Level::Error 54 | } 55 | 56 | /// Turn a message into a violation. 57 | fn to_violation(&self, message: violation::Message) -> Violation { 58 | Violation { 59 | rule: self.rule_name().into(), 60 | level: self.rule_level(), 61 | message, 62 | location: Default::default(), 63 | info: self.rule_info().into(), 64 | } 65 | } 66 | 67 | /// Check [`Packages`] for violations. 68 | fn check_packages(&mut self, _packages: &Packages) -> Violations { 69 | vec![] 70 | } 71 | 72 | /// Check [`Package`] for violations. 73 | fn check_package(&mut self, _package: &Package) -> Violations { 74 | vec![] 75 | } 76 | 77 | /// Check [`Entity`] for violations. 78 | fn check_entity(&mut self, _name: &str, _entity: &Entity) -> Violations { 79 | vec![] 80 | } 81 | } 82 | 83 | impl Rule for RuleSet { 84 | fn rule_name(&self) -> &'static str { 85 | "RuleSet" 86 | } 87 | 88 | fn rule_info(&self) -> &'static str { 89 | "RuleSet" 90 | } 91 | 92 | fn check_packages(&mut self, packages: &Packages) -> Violations { 93 | self.iter_mut() 94 | .flat_map(|rule| rule.check_packages(packages)) 95 | .collect() 96 | } 97 | 98 | fn check_package(&mut self, package: &Package) -> Violations { 99 | self.iter_mut() 100 | .flat_map(|rule| rule.check_package(package)) 101 | .collect() 102 | } 103 | 104 | fn check_entity(&mut self, name: &str, entity: &Entity) -> Violations { 105 | self.iter_mut() 106 | .flat_map(|rule| rule.check_entity(name, entity)) 107 | .collect() 108 | } 109 | } 110 | 111 | /// Get default rules for a given `buffrs` package name. 112 | pub fn all(manifest: &PackageManifest) -> RuleSet { 113 | let mut ret: Vec> = vec![ 114 | Box::new(PackageName::new(manifest.name.clone())), 115 | Box::new(IdentCasing), 116 | Box::new(PackageHierarchy), 117 | ]; 118 | 119 | if manifest.kind == PackageType::Lib { 120 | ret.push(Box::new(LibPackage)); 121 | } 122 | 123 | ret 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::*; 129 | use crate::package::PackageType; 130 | use semver::Version; 131 | 132 | #[test] 133 | fn all_should_contain_these_rules_for_api_packages() -> Result<(), Box> { 134 | let manifest = PackageManifest { 135 | kind: PackageType::Api, 136 | name: crate::package::PackageName::new("package")?, 137 | version: Version::new(0, 1, 0), 138 | description: Default::default(), 139 | }; 140 | 141 | let all = all(&manifest) 142 | .iter() 143 | .map(|r| r.rule_name()) 144 | .collect::>(); 145 | 146 | assert_eq!( 147 | all, 148 | &[ 149 | PackageName::new(manifest.name.clone()).rule_name(), 150 | IdentCasing.rule_name(), 151 | PackageHierarchy.rule_name(), 152 | ], 153 | ); 154 | 155 | Ok(()) 156 | } 157 | 158 | #[test] 159 | fn all_should_contain_these_rules_for_lib_packages() -> Result<(), Box> { 160 | let manifest = PackageManifest { 161 | kind: PackageType::Lib, 162 | name: crate::package::PackageName::new("package")?, 163 | version: Version::new(0, 1, 0), 164 | description: Default::default(), 165 | }; 166 | 167 | let all = all(&manifest) 168 | .iter() 169 | .map(|r| r.rule_name()) 170 | .collect::>(); 171 | 172 | assert_eq!( 173 | all, 174 | &[ 175 | PackageName::new(manifest.name.clone()).rule_name(), 176 | IdentCasing.rule_name(), 177 | PackageHierarchy.rule_name(), 178 | LibPackage.rule_name(), 179 | ], 180 | ); 181 | 182 | Ok(()) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/validation/rules/ident_casing.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Ensure that file names match package names. 18 | #[derive(Debug, Clone, Default)] 19 | pub struct IdentCasing; 20 | 21 | impl Rule for IdentCasing { 22 | fn rule_info(&self) -> &'static str { 23 | "making sure entity names are correct" 24 | } 25 | 26 | /// Default severity [`Level`] of the rule. 27 | fn rule_level(&self) -> Level { 28 | Level::Info 29 | } 30 | 31 | /// Check [`Entity`] for violations. 32 | fn check_entity(&mut self, _name: &str, _entity: &Entity) -> Violations { 33 | vec![] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/validation/rules/lib_package.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct LibPackage; 5 | 6 | impl Rule for LibPackage { 7 | fn rule_info(&self) -> &'static str { 8 | "Make sure that lib packages don't contain service definitions." 9 | } 10 | 11 | fn check_package(&mut self, package: &Package) -> Violations { 12 | let package_name = &package.name; 13 | package 14 | .entities 15 | .iter() 16 | .filter_map(|(name, entity)| { 17 | if let Entity::Service(_) = entity { 18 | let message = violation::Message { 19 | message: format!("{name} is a service definition but {package_name} is a lib type package and thus shouldn't contain services."), 20 | help: "It's best to keep packages containing services separate from packages containing messages and enums.".into(), 21 | }; 22 | Some(self.to_violation(message)) 23 | } else { 24 | None 25 | } 26 | }) 27 | .collect() 28 | } 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use std::collections::BTreeMap; 34 | 35 | use super::*; 36 | 37 | #[test] 38 | fn should_complain_about_service_defs() { 39 | let entities = { 40 | let mut ret: BTreeMap = BTreeMap::new(); 41 | ret.insert("service A".into(), Entity::Service(Service {})); 42 | ret.insert("service B".into(), Entity::Service(Service {})); 43 | 44 | ret 45 | }; 46 | 47 | let package = Package { 48 | name: "my package".into(), 49 | files: vec!["ignored.proto".into()], 50 | entities, 51 | }; 52 | 53 | let violations = LibPackage.check_package(&package); 54 | 55 | let help = "It's best to keep packages containing services separate from packages containing messages and enums."; 56 | let violation_1 = Violation { 57 | rule: "LibPackage".into(), 58 | level: Level::Error, 59 | message: violation::Message { 60 | message: "service A is a service definition but my package is a lib type package and thus shouldn't contain services.".into(), 61 | help: help.into(), 62 | }, 63 | location: Default::default(), 64 | info: LibPackage.rule_info().into(), 65 | }; 66 | let violation_2 = Violation { 67 | message: violation::Message { 68 | message: "service B is a service definition but my package is a lib type package and thus shouldn't contain services.".into(), 69 | help: help.into(), 70 | }, 71 | ..violation_1.clone() 72 | }; 73 | 74 | assert_eq!(violations, vec![violation_1, violation_2]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/validation/rules/package_name.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use super::*; 16 | 17 | /// Ensure that the protobuf package names match the buffrs package name. 18 | #[derive(Debug, Clone)] 19 | pub struct PackageName { 20 | /// Package name to enforce. 21 | name: crate::package::PackageName, 22 | } 23 | 24 | impl PackageName { 25 | /// Create new checker for this rule. 26 | pub fn new(name: crate::package::PackageName) -> Self { 27 | Self { name } 28 | } 29 | } 30 | 31 | impl Rule for PackageName { 32 | fn rule_info(&self) -> &'static str { 33 | "Make sure that the protobuf package name matches the buffer package name." 34 | } 35 | 36 | fn check_package(&mut self, package: &Package) -> Violations { 37 | let transposed = self.name.to_string().replace('-', "_"); 38 | 39 | if !is_prefix(&transposed, &package.name) { 40 | let message = violation::Message { 41 | message: format!("package name is {} but should have {} prefix", package.name, transposed), 42 | help: "Make sure the file name matches the package. For example, a package with the name `package.subpackage` should be stored in `proto/package/subpackage.proto`.".into(), 43 | }; 44 | 45 | return vec![self.to_violation(message)]; 46 | } 47 | 48 | Violations::default() 49 | } 50 | } 51 | 52 | fn is_prefix(prefix: &str, package: &str) -> bool { 53 | prefix 54 | .replace('-', "_") 55 | .split('.') 56 | .zip(package.split('.')) 57 | .all(|(a, b)| a == b) 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[test] 65 | fn can_check_prefix() { 66 | // any value is a prefix of itself 67 | assert!(is_prefix("abc", "abc")); 68 | 69 | assert!(is_prefix("abc", "abc.def")); 70 | assert!(is_prefix("abc", "abc.def.ghi")); 71 | } 72 | 73 | #[test] 74 | fn can_fail_wrong_prefix() { 75 | assert!(!is_prefix("abc", "def")); 76 | assert!(!is_prefix("abc", "abcdef")); 77 | assert!(!is_prefix("abc", "")); 78 | assert!(!is_prefix("abc", "ab")); 79 | } 80 | 81 | #[test] 82 | fn correct_package_name() { 83 | let package = Package { 84 | name: "my_package".into(), 85 | files: vec!["ignored.proto".into()], 86 | entities: Default::default(), 87 | }; 88 | let mut rule = PackageName::new("my-package".parse().unwrap()); 89 | assert!(rule.check_package(&package).is_empty()); 90 | } 91 | 92 | #[test] 93 | fn correct_package_name_submodule() { 94 | let package = Package { 95 | name: "my_package.submodule".into(), 96 | files: vec!["ignored.proto".into()], 97 | entities: Default::default(), 98 | }; 99 | let mut rule = PackageName::new("my-package".parse().unwrap()); 100 | assert!(rule.check_package(&package).is_empty()); 101 | } 102 | 103 | #[test] 104 | fn correct_case_transformation() { 105 | let package = Package { 106 | name: "my_package.submodule".into(), 107 | files: vec!["ignored.proto".into()], 108 | entities: Default::default(), 109 | }; 110 | let mut rule = PackageName::new("my-package".parse().unwrap()); 111 | assert!(rule.check_package(&package).is_empty()); 112 | } 113 | 114 | #[test] 115 | fn incorrect_package_name() { 116 | let package = Package { 117 | name: "my_package_other".into(), 118 | files: vec!["ignored.proto".into()], 119 | entities: Default::default(), 120 | }; 121 | let mut rule = PackageName::new("my-package".parse().unwrap()); 122 | assert_eq!( 123 | rule.check_package(&package), 124 | vec![Violation { 125 | rule: "PackageName".into(), 126 | level: Level::Error, 127 | location: Default::default(), 128 | info: rule.rule_info().into(), 129 | message: violation::Message { 130 | message: "package name is my_package_other but should have my_package prefix".into(), 131 | help: "Make sure the file name matches the package. For example, a package with the name `package.subpackage` should be stored in `proto/package/subpackage.proto`.".into(), 132 | } 133 | }] 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/validation/serde.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use serde::{de, Deserialize, Deserializer}; 16 | use std::collections::BTreeMap; 17 | use std::fmt::Display; 18 | use std::str::FromStr; 19 | 20 | /// Allow deserializing an integer key from a string representation. 21 | /// 22 | /// This is needed because when serializing to JSON, serde will serialize maps with integer keys 23 | /// as strings, but will not allow deserializing from this encoding. 24 | pub(crate) fn de_int_key<'de, D, K, V>(deserializer: D) -> Result, D::Error> 25 | where 26 | D: Deserializer<'de>, 27 | K: Eq + Ord + FromStr, 28 | K::Err: Display, 29 | V: Deserialize<'de>, 30 | { 31 | let string_map = >::deserialize(deserializer)?; 32 | let mut map = BTreeMap::default(); 33 | for (s, v) in string_map { 34 | let k = K::from_str(&s).map_err(de::Error::custom)?; 35 | map.insert(k, v); 36 | } 37 | Ok(map) 38 | } 39 | -------------------------------------------------------------------------------- /src/validation/violation.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Helsing GmbH 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::fmt::{self, Display, Formatter}; 16 | 17 | use miette::{ 18 | Diagnostic, LabeledSpan, MietteError, MietteSpanContents, Severity, SourceCode, SourceSpan, 19 | SpanContents, 20 | }; 21 | 22 | /// Severity level of violation. 23 | #[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] 24 | #[allow(missing_docs)] 25 | pub enum Level { 26 | Info, 27 | Warning, 28 | Error, 29 | } 30 | 31 | /// Location of violation. 32 | #[derive(Default, PartialEq, Clone, Eq, Debug)] 33 | pub struct Location { 34 | /// File that contains violation 35 | pub file: Option, 36 | /// Package name of file containing violation 37 | pub package: Option, 38 | /// Entity name containing the violation 39 | pub entity: Option, 40 | } 41 | 42 | /// Violation message. 43 | #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] 44 | #[error("{message}")] 45 | pub struct Message { 46 | /// Message describing violation 47 | pub message: String, 48 | /// Information on what went wrong 49 | pub help: String, 50 | } 51 | 52 | impl Diagnostic for Message {} 53 | 54 | /// Rule violation 55 | #[derive(Debug, Clone, PartialEq, Eq)] 56 | pub struct Violation { 57 | /// Rule name that was violated 58 | pub rule: String, 59 | /// Level of violation 60 | pub level: Level, 61 | /// Message 62 | pub message: Message, 63 | /// Location where violation occurred 64 | pub location: Location, 65 | /// Help text 66 | pub info: String, 67 | } 68 | 69 | /// Alias for list of [`Violation`]. 70 | pub type Violations = Vec; 71 | 72 | impl std::error::Error for Violation {} 73 | 74 | impl Display for Violation { 75 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 76 | write!(f, "{}", &self.info) 77 | } 78 | } 79 | 80 | impl Diagnostic for Violation { 81 | fn code<'a>(&'a self) -> Option> { 82 | Some(Box::new(self.rule.split("::").last().unwrap_or(&self.rule))) 83 | } 84 | 85 | fn url<'a>(&'a self) -> Option> { 86 | Some(Box::new( 87 | "https://helsing-ai.github.io/buffrs/reference/protocol-buffer-rules.html", 88 | )) 89 | } 90 | 91 | fn severity(&self) -> Option { 92 | let level = match self.level { 93 | Level::Info => Severity::Advice, 94 | Level::Warning => Severity::Warning, 95 | Level::Error => Severity::Error, 96 | }; 97 | 98 | Some(level) 99 | } 100 | 101 | fn source_code(&self) -> Option<&dyn SourceCode> { 102 | Some(&self.location) 103 | } 104 | 105 | fn labels(&self) -> Option + '_>> { 106 | Some(Box::new( 107 | [LabeledSpan::new(Some("file".into()), 0, 0)].into_iter(), 108 | )) 109 | } 110 | 111 | fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { 112 | Some(&self.message) 113 | } 114 | 115 | fn help<'a>(&'a self) -> Option> { 116 | Some(Box::new(&self.message.help)) 117 | } 118 | } 119 | 120 | impl SourceCode for Location { 121 | fn read_span<'a>( 122 | &'a self, 123 | span: &SourceSpan, 124 | _context_lines_before: usize, 125 | _context_lines_after: usize, 126 | ) -> Result + 'a>, MietteError> { 127 | Ok(Box::new(MietteSpanContents::new_named( 128 | self.file.clone().unwrap_or_default(), 129 | &[], 130 | *span, 131 | 0, 132 | 0, 133 | 0, 134 | ))) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/cmd/add/in: -------------------------------------------------------------------------------- 1 | ../../data/projects/lib -------------------------------------------------------------------------------- /tests/cmd/add/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("add") 9 | .arg("--registry") 10 | .arg("http://my-reg.jfrog.io/artifactory") 11 | .arg("my-repository/my-package@=1.0.0") 12 | .current_dir(vfs.root()) 13 | .assert() 14 | .success() 15 | .stdout(include_str!("stdout.log")) 16 | .stderr(include_str!("stderr.log")); 17 | 18 | vfs.verify_against(crate::parent_directory!().join("out")); 19 | } 20 | -------------------------------------------------------------------------------- /tests/cmd/add/out/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [package] 4 | type = "lib" 5 | name = "lib" 6 | version = "0.0.1" 7 | 8 | [dependencies.my-package] 9 | version = "=1.0.0" 10 | repository = "my-repository" 11 | registry = "http://my-reg.jfrog.io/artifactory" 12 | -------------------------------------------------------------------------------- /tests/cmd/add/out/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/add/out/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/cmd/add/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/add/stderr.log -------------------------------------------------------------------------------- /tests/cmd/add/stdout.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/add/stdout.log -------------------------------------------------------------------------------- /tests/cmd/init/api/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::empty(); 6 | 7 | crate::cli!() 8 | .arg("init") 9 | .arg("--api") 10 | .arg("some-api") 11 | .current_dir(vfs.root()) 12 | .assert() 13 | .success() 14 | .stdout(include_str!("stdout.log")) 15 | .stderr(include_str!("stderr.log")); 16 | 17 | vfs.verify_against(crate::parent_directory!().join("out")); 18 | } 19 | -------------------------------------------------------------------------------- /tests/cmd/init/api/out/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [package] 4 | type = "api" 5 | name = "some-api" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/cmd/init/api/out/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/api/out/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/cmd/init/api/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/api/stderr.log -------------------------------------------------------------------------------- /tests/cmd/init/api/stdout.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/api/stdout.log -------------------------------------------------------------------------------- /tests/cmd/init/default/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::empty(); 6 | 7 | crate::cli!() 8 | .arg("init") 9 | .current_dir(vfs.root()) 10 | .assert() 11 | .success() 12 | .stdout(include_str!("stdout.log")) 13 | .stderr(include_str!("stderr.log")); 14 | 15 | vfs.verify_against(crate::parent_directory!().join("out")); 16 | } 17 | -------------------------------------------------------------------------------- /tests/cmd/init/default/out/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [dependencies] 4 | -------------------------------------------------------------------------------- /tests/cmd/init/default/out/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/default/out/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/cmd/init/default/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/default/stderr.log -------------------------------------------------------------------------------- /tests/cmd/init/default/stdout.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/default/stdout.log -------------------------------------------------------------------------------- /tests/cmd/init/lib/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::empty(); 6 | 7 | crate::cli!() 8 | .arg("init") 9 | .arg("--lib") 10 | .arg("some-lib") 11 | .current_dir(vfs.root()) 12 | .assert() 13 | .success() 14 | .stdout(include_str!("stdout.log")) 15 | .stderr(include_str!("stderr.log")); 16 | 17 | vfs.verify_against(crate::parent_directory!().join("out")); 18 | } 19 | -------------------------------------------------------------------------------- /tests/cmd/init/lib/out/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [package] 4 | type = "lib" 5 | name = "some-lib" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/cmd/init/lib/out/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/lib/out/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/cmd/init/lib/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/lib/stderr.log -------------------------------------------------------------------------------- /tests/cmd/init/lib/stdout.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/init/lib/stdout.log -------------------------------------------------------------------------------- /tests/cmd/init/mod.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod default; 3 | mod lib; 4 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "some-test-api" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/in/proto/asset.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset; 4 | 5 | message Asset { 6 | string name = 1; 7 | float value = 2; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/in/proto/children/child.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset.child; 4 | 5 | import "some-test-api/asset.proto"; 6 | 7 | message Child { 8 | asset.Asset asset = 1; 9 | float scalar = 2; 10 | } 11 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/in/proto/sibling.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset; 4 | 5 | import "some-test-api/asset.proto"; 6 | 7 | message Combined { 8 | asset.Asset asset = 1; 9 | float scalar = 2; 10 | } 11 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/in/proto/vendor/some-test-api/asset.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset; 4 | 5 | message Asset { 6 | string name = 1; 7 | float value = 2; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("install") 9 | .current_dir(vfs.root()) 10 | .assert() 11 | .success() 12 | .stdout(include_str!("stdout.log")) 13 | .stderr(include_str!("stderr.log")); 14 | 15 | vfs.verify_against(crate::parent_directory!().join("out")); 16 | } 17 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/Proto.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | packages = [] 3 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "some-test-api" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/proto/asset.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset; 4 | 5 | message Asset { 6 | string name = 1; 7 | float value = 2; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/proto/children/child.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset.child; 4 | 5 | import "some-test-api/asset.proto"; 6 | 7 | message Child { 8 | asset.Asset asset = 1; 9 | float scalar = 2; 10 | } 11 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/proto/sibling.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset; 4 | 5 | import "some-test-api/asset.proto"; 6 | 7 | message Combined { 8 | asset.Asset asset = 1; 9 | float scalar = 2; 10 | } 11 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/proto/vendor/some-test-api/asset.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset; 4 | 5 | message Asset { 6 | string name = 1; 7 | float value = 2; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/proto/vendor/some-test-api/children/child.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset.child; 4 | 5 | import "some-test-api/asset.proto"; 6 | 7 | message Child { 8 | asset.Asset asset = 1; 9 | float scalar = 2; 10 | } 11 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/out/proto/vendor/some-test-api/sibling.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package asset; 4 | 5 | import "some-test-api/asset.proto"; 6 | 7 | message Combined { 8 | asset.Asset asset = 1; 9 | float scalar = 2; 10 | } 11 | -------------------------------------------------------------------------------- /tests/cmd/install/empty/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/install/empty/stderr.log -------------------------------------------------------------------------------- /tests/cmd/install/empty/stdout.log: -------------------------------------------------------------------------------- 1 | :: installed some-test-api@0.1.0 2 | -------------------------------------------------------------------------------- /tests/cmd/install/local/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | some-local-api = { path = "./some-local-api" } 3 | -------------------------------------------------------------------------------- /tests/cmd/install/local/in/some-local-api/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "some-local-api" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/install/local/in/some-local-api/proto/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local; 4 | -------------------------------------------------------------------------------- /tests/cmd/install/local/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("install") 9 | .current_dir(vfs.root()) 10 | .assert() 11 | .success() 12 | .stdout(include_str!("stdout.log")) 13 | .stderr(include_str!("stderr.log")); 14 | 15 | vfs.verify_against(crate::parent_directory!().join("out")); 16 | } 17 | -------------------------------------------------------------------------------- /tests/cmd/install/local/out/Proto.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | packages = [] 3 | -------------------------------------------------------------------------------- /tests/cmd/install/local/out/Proto.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | some-local-api = { path = "./some-local-api" } 3 | -------------------------------------------------------------------------------- /tests/cmd/install/local/out/proto/vendor/some-local-api/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [package] 4 | type = "api" 5 | name = "some-local-api" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/cmd/install/local/out/proto/vendor/some-local-api/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local; 4 | -------------------------------------------------------------------------------- /tests/cmd/install/local/out/some-local-api/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "some-local-api" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/install/local/out/some-local-api/proto/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local; 4 | -------------------------------------------------------------------------------- /tests/cmd/install/local/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/install/local/stderr.log -------------------------------------------------------------------------------- /tests/cmd/install/local/stdout.log: -------------------------------------------------------------------------------- 1 | :: packaged some-local-api@0.1.0 2 | :: installed some-local-api@0.1.0 3 | -------------------------------------------------------------------------------- /tests/cmd/install/mod.rs: -------------------------------------------------------------------------------- 1 | mod empty; 2 | mod local; 3 | mod upgrade; 4 | -------------------------------------------------------------------------------- /tests/cmd/install/upgrade/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "some-test-api" 4 | version = "1.0.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/install/upgrade/in/dummy.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package dummy_api; 4 | 5 | message DummyMessage { 6 | string id = 1; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/cmd/install/upgrade/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{with_test_registry, VirtualFileSystem}; 2 | 3 | #[test] 4 | fn fixture() { 5 | with_test_registry(|url| { 6 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 7 | let buffrs_home = vfs.root().join("$HOME"); 8 | let cwd = vfs.root(); 9 | 10 | // Dummy Library 11 | { 12 | // mkdir dummy 13 | std::fs::create_dir(cwd.join("dummy")).unwrap(); 14 | 15 | // cd dummy 16 | let cwd = cwd.join("dummy"); 17 | 18 | // buffrs init --lib dummy 19 | crate::cli!() 20 | .args(["init", "--lib", "dummy"]) 21 | .env("BUFFRS_HOME", &buffrs_home) 22 | .current_dir(&cwd) 23 | .assert() 24 | .success(); 25 | 26 | // ed proto/dummy.proto 27 | std::fs::copy( 28 | crate::parent_directory!().join("in/dummy.proto"), 29 | cwd.join("proto/dummy.proto"), 30 | ) 31 | .unwrap(); 32 | 33 | // publish version 0.1.0 34 | // buffrs publish --repository dummy 35 | crate::cli!() 36 | .args(["publish", "--registry", url, "--repository", "dummy"]) 37 | .env("BUFFRS_HOME", &buffrs_home) 38 | .current_dir(&cwd) 39 | .assert() 40 | .success(); 41 | 42 | let manifest = std::fs::read_to_string(cwd.join("Proto.toml")).unwrap(); 43 | let updated_manifest = manifest.replacen("0.1.0", "0.2.0", 1); 44 | std::fs::write(cwd.join("Proto.toml"), updated_manifest).unwrap(); 45 | 46 | // publish version 0.2.0 47 | // buffrs publish --repository dummy 48 | crate::cli!() 49 | .args(["publish", "--registry", url, "--repository", "dummy"]) 50 | .env("BUFFRS_HOME", &buffrs_home) 51 | .current_dir(&cwd) 52 | .assert() 53 | .success(); 54 | } 55 | 56 | crate::cli!() 57 | .args(["add", "--registry", url, "dummy/dummy@=0.1.0"]) 58 | .env("BUFFRS_HOME", &buffrs_home) 59 | .current_dir(&cwd) 60 | .assert() 61 | .success(); 62 | 63 | crate::cli!() 64 | .arg("install") 65 | .current_dir(vfs.root()) 66 | .assert() 67 | .success(); 68 | 69 | // Upgrade dependency from 0.1.0 to 0.2.0 70 | let manifest = std::fs::read_to_string(cwd.join("Proto.toml")).unwrap(); 71 | let updated_manifest = manifest.replacen("0.1.0", "0.2.0", 1); 72 | std::fs::write(cwd.join("Proto.toml"), updated_manifest).unwrap(); 73 | 74 | crate::cli!() 75 | .arg("install") 76 | .current_dir(vfs.root()) 77 | .assert() 78 | .success() 79 | .stdout(include_str!("stdout.log")) 80 | .stderr(include_str!("stderr.log")); 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /tests/cmd/install/upgrade/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/install/upgrade/stderr.log -------------------------------------------------------------------------------- /tests/cmd/install/upgrade/stdout.log: -------------------------------------------------------------------------------- 1 | :: installed some-test-api@1.0.0 2 | :: installed dummy@0.2.0 3 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "the-api" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/crud/resources.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "the-api/types/sub/type_one.proto"; 4 | import "the-api/types/sub/type_two.proto"; 5 | import "the-api/crud/tokens.proto"; 6 | 7 | package the_api.crud; 8 | 9 | service Resources { 10 | rpc GetResource(GetResourceRequest) 11 | returns (GetResourceResponse); 12 | 13 | rpc ListResources(ListResourcesRequest) 14 | returns (ListResourcesResponse); 15 | 16 | rpc AssignResource(AssignResourceRequest) 17 | returns (AssignResourceResponse); 18 | 19 | rpc DeleteResource(DeleteResourceRequest) 20 | returns (DeleteResourceResponse); 21 | } 22 | 23 | message GetResourceRequest { string id = 1; } 24 | message GetResourceResponse { 25 | string id = 1; 26 | string name = 2; 27 | string created_at = 3; 28 | the_api.crud.GetTokenResponse sibling = 4; 29 | } 30 | 31 | message ListResourcesRequest {} 32 | message ListResourcesResponse { repeated GetResourceResponse resroucess = 1; } 33 | 34 | message AssignResourceRequest { string id = 1; string token_id = 2; } 35 | message AssignResourceResponse {} 36 | 37 | message DeleteResourceRequest { string id = 1; } 38 | message DeleteResourceResponse {} 39 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/crud/tokens.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package the_api.crud; 4 | 5 | service Tokens { 6 | rpc CreateToken(CreateTokenRequest) 7 | returns (CreateTokenResponse); 8 | 9 | rpc GetToken(GetTokenRequest) 10 | returns (GetTokenResponse); 11 | 12 | rpc ListTokens(ListTokensRequest) 13 | returns (ListTokensResponse); 14 | 15 | rpc DeleteToken(DeleteTokenRequest) 16 | returns (DeleteTokenResponse); 17 | } 18 | 19 | message CreateTokenRequest { 20 | string name = 1; 21 | string description = 2; 22 | } 23 | 24 | message CreateTokenResponse { 25 | string id = 1; 26 | } 27 | 28 | message GetTokenRequest { 29 | string id = 1; 30 | } 31 | 32 | message GetTokenResponse { 33 | string id = 1; 34 | } 35 | 36 | message ListTokensRequest {} 37 | 38 | message ListTokensResponse { 39 | repeated GetTokenResponse tokens = 1; 40 | } 41 | 42 | message DeleteTokenRequest { 43 | string id = 1; 44 | } 45 | 46 | message DeleteTokenResponse {} 47 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/root.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "the-api/crud/tokens.proto"; 4 | import "the-api/types/sub/type_one.proto"; 5 | 6 | package the_api; 7 | 8 | message Message { 9 | the_api.types.sub.TypeOneId id = 1; 10 | the_api.crud.GetTokenRequest request = 2; 11 | } 12 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/runtime/jobs.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package the_api.runtime; 4 | 5 | service Jobs { 6 | rpc GetJobToExecute(GetJobToExecuteRequest) 7 | returns (GetJobToExecuteResponse); 8 | 9 | rpc ReportJobStatus(ReportJobStatusRequest) 10 | returns (ReportJobStatusResponse); 11 | } 12 | 13 | message GetJobToExecuteRequest { 14 | string id = 1; 15 | } 16 | 17 | message GetJobToExecuteResponse { 18 | message Job { 19 | string id = 1; 20 | repeated Command commands = 2; 21 | } 22 | 23 | optional Job job = 1; 24 | } 25 | 26 | message ReportJobStatusRequest { 27 | string job_id = 1; 28 | string status = 2; 29 | } 30 | 31 | message ReportJobStatusResponse {} 32 | 33 | message Command { 34 | message CommandOne { 35 | string id = 1; 36 | string command = 2; 37 | } 38 | 39 | message CommandTwo { 40 | string id = 1; 41 | string another_command = 2; 42 | optional string thing = 3; 43 | } 44 | 45 | oneof command { 46 | CommandOne command_one = 1; 47 | CommandTwo command_two = 2; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/runtime/type_one_runtime.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package the_api.runtime; 4 | 5 | service TypeOneRuntime { 6 | rpc GetTypeOneResourceConfigs(GetTypeOneResourceConfigsRequest) 7 | returns (GetTypeOneResourceConfigsResponse); 8 | } 9 | 10 | message GetTypeOneResourceConfigsRequest { 11 | string id = 1; 12 | } 13 | 14 | message GetTypeOneResourceConfigsResponse { 15 | message TypeOneResourceConfig { 16 | string service_label = 1; 17 | string label = 2; 18 | string resource_id = 3; 19 | string commit_id = 4; 20 | } 21 | 22 | repeated TypeOneResourceConfig type_one_resource_configs = 1; 23 | } 24 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/runtime/type_two_runtime.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "the-api/types/sub/type_two.proto"; 4 | 5 | package the_api.runtime; 6 | 7 | service TypeTwoRuntime { 8 | rpc PullTypeTwoConfiguration(PullTypeTwoConfigurationRequest) 9 | returns (PullTypeTwoConfigurationResponse); 10 | } 11 | 12 | message PullTypeTwoConfigurationRequest { 13 | string resource_id = 1; 14 | string hardware_id = 2; 15 | } 16 | 17 | message PullTypeTwoConfigurationResponse { 18 | string hostname = 1; 19 | the_api.types.sub.TypeTwo type_two = 2; 20 | } 21 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/types/sub/type_one.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "the-api/types/sub/type_two.proto"; 4 | 5 | package the_api.types.sub; 6 | 7 | message TypeOne { 8 | string name = 1; 9 | the_api.types.sub.TypeTwo type_two = 2; 10 | } 11 | 12 | message TypeOneId { string id = 1; } 13 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/types/sub/type_two.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package the_api.types.sub; 4 | 5 | message TypeTwo { 6 | string public_key = 1; 7 | string private_key = 2; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/vendor/the-lib/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "lib" 3 | name = "the-lib" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/lint/in/proto/vendor/the-lib/root.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Only the root packages are checked, not vendored dependencies. 4 | package the_wrong_package; 5 | 6 | message Message { 7 | string somethin = 1; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cmd/lint/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("lint") 9 | .current_dir(vfs.root()) 10 | .assert() 11 | .success() 12 | .stdout(include_str!("stdout.log")) 13 | .stderr(include_str!("stderr.log")); 14 | } 15 | -------------------------------------------------------------------------------- /tests/cmd/lint/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/lint/stderr.log -------------------------------------------------------------------------------- /tests/cmd/lint/stdout.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/lint/stdout.log -------------------------------------------------------------------------------- /tests/cmd/login/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::empty().with_virtual_home(); 6 | 7 | crate::cli!() 8 | .arg("login") 9 | .arg("--registry") 10 | .arg("https://org.jfrog.io/artifactory") 11 | .current_dir(vfs.root()) 12 | .write_stdin("some-token") 13 | .assert() 14 | .success() 15 | .stdout(include_str!("stdout.log")) 16 | .stderr(include_str!("stderr.log")); 17 | 18 | vfs.verify_against(crate::parent_directory!().join("out")); 19 | } 20 | -------------------------------------------------------------------------------- /tests/cmd/login/out/$HOME/.buffrs/credentials.toml: -------------------------------------------------------------------------------- 1 | [[credentials]] 2 | uri = "https://org.jfrog.io/artifactory" 3 | token = "some-token" 4 | -------------------------------------------------------------------------------- /tests/cmd/login/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/login/stderr.log -------------------------------------------------------------------------------- /tests/cmd/login/stdout.log: -------------------------------------------------------------------------------- 1 | :: please enter your artifactory token: 2 | -------------------------------------------------------------------------------- /tests/cmd/logout/in/$HOME/.buffrs/credentials.toml: -------------------------------------------------------------------------------- 1 | [artifactory] 2 | url = "https://org.jfrog.io/artifactory" 3 | password = "some-token" 4 | -------------------------------------------------------------------------------- /tests/cmd/logout/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")).with_virtual_home(); 6 | 7 | crate::cli!() 8 | .arg("logout") 9 | .arg("--registry") 10 | .arg("https://org.jfrog.io/artifactory") 11 | .current_dir(vfs.root()) 12 | .assert() 13 | .success() 14 | .stdout(include_str!("stdout.log")) 15 | .stderr(include_str!("stderr.log")); 16 | 17 | vfs.verify_against(crate::parent_directory!().join("out")); 18 | } 19 | -------------------------------------------------------------------------------- /tests/cmd/logout/out/$HOME/.buffrs/credentials.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/logout/out/$HOME/.buffrs/credentials.toml -------------------------------------------------------------------------------- /tests/cmd/logout/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/logout/stderr.log -------------------------------------------------------------------------------- /tests/cmd/logout/stdout.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/logout/stdout.log -------------------------------------------------------------------------------- /tests/cmd/ls/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test" 3 | version = "1.0.0" 4 | type = "api" 5 | 6 | [dependencies] 7 | physics = { version = "1.0.0", registry = "https://some.registry", repository = "test" } 8 | -------------------------------------------------------------------------------- /tests/cmd/ls/in/proto/some.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test.some; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/in/proto/vendor/physics/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "physics" 3 | version = "1.0.0" 4 | type = "lib" 5 | -------------------------------------------------------------------------------- /tests/cmd/ls/in/proto/vendor/physics/mass.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package physics.mass; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/in/proto/vendor/physics/temperature.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package physics.temperature; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/in/proto/vendor/test/some.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test.some; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("ls") 9 | .current_dir(vfs.root()) 10 | .assert() 11 | .success() 12 | .stdout(include_str!("stdout.log")) 13 | .stderr(include_str!("stderr.log")); 14 | 15 | vfs.verify_against(crate::parent_directory!().join("out")); 16 | } 17 | -------------------------------------------------------------------------------- /tests/cmd/ls/out/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test" 3 | version = "1.0.0" 4 | type = "api" 5 | 6 | [dependencies] 7 | physics = { version = "1.0.0", registry = "https://some.registry", repository = "test" } 8 | -------------------------------------------------------------------------------- /tests/cmd/ls/out/proto/some.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test.some; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/out/proto/vendor/physics/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "physics" 3 | version = "1.0.0" 4 | type = "lib" 5 | -------------------------------------------------------------------------------- /tests/cmd/ls/out/proto/vendor/physics/mass.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package physics.mass; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/out/proto/vendor/physics/temperature.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package physics.temperature; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/out/proto/vendor/test/some.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test.some; 4 | -------------------------------------------------------------------------------- /tests/cmd/ls/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/ls/stderr.log -------------------------------------------------------------------------------- /tests/cmd/ls/stdout.log: -------------------------------------------------------------------------------- 1 | proto/vendor/physics/mass.proto proto/vendor/physics/temperature.proto proto/vendor/test/some.proto -------------------------------------------------------------------------------- /tests/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | mod add; 2 | mod init; 3 | mod install; 4 | mod lint; 5 | mod login; 6 | mod logout; 7 | mod ls; 8 | mod package; 9 | mod publish; 10 | mod remove; 11 | mod tuto; 12 | -------------------------------------------------------------------------------- /tests/cmd/package/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "lib" 3 | name = "lib" 4 | version = "0.0.1" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/package/in/proto/foo/bar.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package foo; 4 | 5 | message Bar { 6 | string foo = 1; 7 | } -------------------------------------------------------------------------------- /tests/cmd/package/in/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Hello { 4 | string name = 1; 5 | } -------------------------------------------------------------------------------- /tests/cmd/package/in/proto/vendor/external/external.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package external; 4 | 5 | message Window { 6 | int32 height = 1; 7 | int32 width = 2; 8 | } -------------------------------------------------------------------------------- /tests/cmd/package/in/proto/vendor/lib/foo/bar.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package foo; 4 | 5 | message Bar { 6 | string foo = 1; 7 | } -------------------------------------------------------------------------------- /tests/cmd/package/in/proto/vendor/lib/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Hello { 4 | string name = 1; 5 | } -------------------------------------------------------------------------------- /tests/cmd/package/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("package") 9 | .current_dir(vfs.root()) 10 | .assert() 11 | .success() 12 | .stdout(include_str!("stdout.log")) 13 | .stderr(include_str!("stderr.log")); 14 | 15 | vfs.verify_against(crate::parent_directory!().join("out")); 16 | } 17 | -------------------------------------------------------------------------------- /tests/cmd/package/out/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "lib" 3 | name = "lib" 4 | version = "0.0.1" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/package/out/lib-0.0.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/package/out/lib-0.0.1.tgz -------------------------------------------------------------------------------- /tests/cmd/package/out/proto/foo/bar.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package foo; 4 | 5 | message Bar { 6 | string foo = 1; 7 | } -------------------------------------------------------------------------------- /tests/cmd/package/out/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Hello { 4 | string name = 1; 5 | } -------------------------------------------------------------------------------- /tests/cmd/package/out/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/package/out/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/cmd/package/out/proto/vendor/external/external.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package external; 4 | 5 | message Window { 6 | int32 height = 1; 7 | int32 width = 2; 8 | } -------------------------------------------------------------------------------- /tests/cmd/package/out/proto/vendor/lib/foo/bar.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package foo; 4 | 5 | message Bar { 6 | string foo = 1; 7 | } -------------------------------------------------------------------------------- /tests/cmd/package/out/proto/vendor/lib/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Hello { 4 | string name = 1; 5 | } -------------------------------------------------------------------------------- /tests/cmd/package/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/package/stderr.log -------------------------------------------------------------------------------- /tests/cmd/package/stdout.log: -------------------------------------------------------------------------------- 1 | :: packaged lib@0.0.1 2 | -------------------------------------------------------------------------------- /tests/cmd/publish/lib/in: -------------------------------------------------------------------------------- 1 | ../../../data/projects/lib -------------------------------------------------------------------------------- /tests/cmd/publish/lib/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{with_test_registry, VirtualFileSystem}; 2 | 3 | #[test] 4 | fn fixture() { 5 | with_test_registry(|url| { 6 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 7 | 8 | crate::cli!() 9 | .arg("publish") 10 | .arg("--registry") 11 | .arg(url) 12 | .arg("--repository") 13 | .arg("my-repository") 14 | .current_dir(vfs.root()) 15 | .assert() 16 | .success() 17 | .stdout(include_str!("stdout.log")) 18 | .stderr(include_str!("stderr.log")); 19 | 20 | vfs.verify_against(crate::parent_directory!().join("out")); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /tests/cmd/publish/lib/out: -------------------------------------------------------------------------------- 1 | ../../../data/projects/lib -------------------------------------------------------------------------------- /tests/cmd/publish/lib/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/publish/lib/stderr.log -------------------------------------------------------------------------------- /tests/cmd/publish/lib/stdout.log: -------------------------------------------------------------------------------- 1 | :: packaged lib@0.0.1 2 | :: published my-repository/lib@0.0.1 3 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/Proto.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | packages = [] 3 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "my-api" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | some-local-lib = { path = "./some-local-lib" } 8 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/proto/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.api; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/proto/vendor/my-api/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.api; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/proto/vendor/some-local-lib/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [package] 4 | type = "lib" 5 | name = "some-local-lib" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/proto/vendor/some-local-lib/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.lib; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/some-local-lib/Proto.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | packages = [] 3 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/some-local-lib/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "lib" 3 | name = "some-local-lib" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/in/some-local-lib/proto/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.lib; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("publish") 9 | .arg("--registry") 10 | .arg("https://localhost:54321/fake-uri") 11 | .arg("--repository") 12 | .arg("my-repository") 13 | .current_dir(vfs.root()) 14 | .assert() 15 | .failure() 16 | .stdout(include_str!("stdout.log")) 17 | .stderr(include_str!("stderr.log")); 18 | 19 | vfs.verify_against(crate::parent_directory!().join("out")); 20 | } 21 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/Proto.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | packages = [] 3 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "my-api" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | some-local-lib = { path = "./some-local-lib" } 8 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/proto/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.api; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/proto/vendor/my-api/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.api; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/proto/vendor/some-local-lib/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [package] 4 | type = "lib" 5 | name = "some-local-lib" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/proto/vendor/some-local-lib/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.lib; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/some-local-lib/Proto.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | packages = [] 3 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/some-local-lib/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "lib" 3 | name = "some-local-lib" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/out/some-local-lib/proto/local.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local.lib; 4 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/stderr.log: -------------------------------------------------------------------------------- 1 | Error: × failed to publish `my-api` to `https://localhost:54321/fake-uri:my- 2 | │ repository` 3 | ╰─▶ unable to publish my-api to artifactory due having the following local 4 | dependencies: some-local-lib 5 | 6 | -------------------------------------------------------------------------------- /tests/cmd/publish/local/stdout.log: -------------------------------------------------------------------------------- 1 | :: packaged my-api@0.1.0 2 | -------------------------------------------------------------------------------- /tests/cmd/publish/mod.rs: -------------------------------------------------------------------------------- 1 | mod lib; 2 | mod local; 3 | -------------------------------------------------------------------------------- /tests/cmd/remove/in/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "lib" 3 | name = "lib" 4 | version = "0.0.1" 5 | 6 | [dependencies.my-package] 7 | version = "=1.0.0" 8 | repository = "my-repository" 9 | registry = "https://org.jfrog.io/artifactory" 10 | -------------------------------------------------------------------------------- /tests/cmd/remove/in/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/remove/in/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/cmd/remove/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::VirtualFileSystem; 2 | 3 | #[test] 4 | fn fixture() { 5 | let vfs = VirtualFileSystem::copy(crate::parent_directory!().join("in")); 6 | 7 | crate::cli!() 8 | .arg("remove") 9 | .arg("my-package") 10 | .current_dir(vfs.root()) 11 | .assert() 12 | .success() 13 | .stdout(include_str!("stdout.log")) 14 | .stderr(include_str!("stderr.log")); 15 | 16 | vfs.verify_against(crate::parent_directory!().join("out")); 17 | } 18 | -------------------------------------------------------------------------------- /tests/cmd/remove/out: -------------------------------------------------------------------------------- 1 | ../../data/projects/lib -------------------------------------------------------------------------------- /tests/cmd/remove/stderr.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/remove/stderr.log -------------------------------------------------------------------------------- /tests/cmd/remove/stdout.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/cmd/remove/stdout.log -------------------------------------------------------------------------------- /tests/cmd/tuto/in/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sensor-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tonic = "0.11" 8 | prost = "0.12" 9 | prost-types = "0.12" 10 | 11 | [build-dependencies] 12 | tokio = { version = "1", features = ["full"] } 13 | tonic-build = "0.11" 14 | -------------------------------------------------------------------------------- /tests/cmd/tuto/in/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use buffrs::package::PackageStore; 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | let store = Path::new(PackageStore::PROTO_VENDOR_PATH); 8 | 9 | let protos = PackageStore::current() 10 | .await 11 | .unwrap() 12 | .collect(store, true) 13 | .await; 14 | 15 | let includes = &[store]; 16 | 17 | tonic_build::configure() 18 | .build_client(true) 19 | .build_server(true) 20 | .build_transport(true) 21 | .include_file("buffrs.rs") 22 | .compile(&protos, includes) 23 | .unwrap(); 24 | } 25 | -------------------------------------------------------------------------------- /tests/cmd/tuto/in/main.rs: -------------------------------------------------------------------------------- 1 | mod protos { 2 | tonic::include_proto!("buffrs"); 3 | } 4 | 5 | #[allow(dead_code)] 6 | struct Sensor; 7 | 8 | impl protos::sensor_api::sensor_server::Sensor for Sensor { 9 | fn read_temperature<'life0, 'async_trait>( 10 | &'life0 self, 11 | _request: tonic::Request, 12 | ) -> ::core::pin::Pin< 13 | Box< 14 | dyn ::core::future::Future< 15 | Output = std::result::Result< 16 | tonic::Response, 17 | tonic::Status, 18 | >, 19 | > + ::core::marker::Send 20 | + 'async_trait, 21 | >, 22 | > 23 | where 24 | 'life0: 'async_trait, 25 | Self: 'async_trait, 26 | { 27 | todo!() 28 | } 29 | } 30 | 31 | fn main() { 32 | todo!() 33 | } 34 | -------------------------------------------------------------------------------- /tests/cmd/tuto/in/sensor.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package sensor_api; 4 | 5 | import "units/temperature.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | // A bridge to subscribe to real-time sensor data from connected devices 9 | service Sensor { 10 | // Read the temperature of a device 11 | rpc ReadTemperature(DeviceId) 12 | returns (Measurement); 13 | } 14 | 15 | // Device Identifier 16 | message DeviceId { 17 | string id = 1; 18 | } 19 | 20 | // Temperature measured by a device 21 | message Measurement { 22 | DeviceId device = 1; 23 | units.Temperature temperature = 2; 24 | google.protobuf.Timestamp measured_at = 3; 25 | } 26 | -------------------------------------------------------------------------------- /tests/cmd/tuto/in/temperature.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package units; 4 | 5 | // Temperature in Celsius 6 | message Celsius { 7 | float value = 1; 8 | } 9 | 10 | // Temperature in Fahrenheit 11 | message Fahrenheit { 12 | float value = 1; 13 | } 14 | 15 | // Temperature in Kelvin 16 | message Kelvin { 17 | float value = 1; 18 | } 19 | 20 | // A temperature 21 | message Temperature { 22 | // Allowed units for temperature measurements 23 | oneof unit { 24 | Celsius celsius = 1; 25 | Fahrenheit fahrenheit = 2; 26 | Kelvin kelvin = 3; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/data/packages/test-api-0.1.0.tgz: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1bcfc1d157c62a51fed0546350a61fa019272e96e5351b072141910e97b4f96c 3 | size 699 4 | -------------------------------------------------------------------------------- /tests/data/parsing/addressbook.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | "tutorial": { 4 | "name": "tutorial", 5 | "files": [ 6 | "addressbook.proto" 7 | ], 8 | "entities": { 9 | "AddressBook": { 10 | "kind": "message", 11 | "fields": { 12 | "1": { 13 | "name": "people", 14 | "type_": "message", 15 | "label": "repeated", 16 | "default": null 17 | } 18 | } 19 | }, 20 | "Person": { 21 | "kind": "message", 22 | "fields": { 23 | "1": { 24 | "name": "name", 25 | "type_": "string", 26 | "label": "optional", 27 | "default": null 28 | }, 29 | "2": { 30 | "name": "id", 31 | "type_": "int32", 32 | "label": "optional", 33 | "default": null 34 | }, 35 | "3": { 36 | "name": "email", 37 | "type_": "string", 38 | "label": "optional", 39 | "default": null 40 | }, 41 | "4": { 42 | "name": "phones", 43 | "type_": "message", 44 | "label": "repeated", 45 | "default": null 46 | }, 47 | "5": { 48 | "name": "last_updated", 49 | "type_": "message", 50 | "label": "optional", 51 | "default": null 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/data/parsing/addressbook.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tutorial; 3 | 4 | import "google/protobuf/timestamp.proto"; 5 | 6 | message Person { 7 | string name = 1; 8 | int32 id = 2; 9 | string email = 3; 10 | 11 | enum PhoneType { 12 | MOBILE = 0; 13 | HOME = 1; 14 | WORK = 2; 15 | } 16 | 17 | message PhoneNumber { 18 | string number = 1; 19 | PhoneType type = 2; 20 | } 21 | 22 | repeated PhoneNumber phones = 4; 23 | 24 | google.protobuf.Timestamp last_updated = 5; 25 | } 26 | 27 | message AddressBook { 28 | repeated Person people = 1; 29 | } 30 | -------------------------------------------------------------------------------- /tests/data/parsing/books.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | "com.book": { 4 | "name": "com.book", 5 | "files": [ 6 | "books.proto" 7 | ], 8 | "entities": { 9 | "Book": { 10 | "kind": "message", 11 | "fields": { 12 | "1": { 13 | "name": "isbn", 14 | "type_": "int64", 15 | "label": "optional", 16 | "default": null 17 | }, 18 | "2": { 19 | "name": "title", 20 | "type_": "string", 21 | "label": "optional", 22 | "default": null 23 | }, 24 | "3": { 25 | "name": "author", 26 | "type_": "string", 27 | "label": "optional", 28 | "default": null 29 | } 30 | } 31 | }, 32 | "BookService": { 33 | "kind": "service" 34 | }, 35 | "BookStore": { 36 | "kind": "message", 37 | "fields": { 38 | "1": { 39 | "name": "name", 40 | "type_": "string", 41 | "label": "optional", 42 | "default": null 43 | }, 44 | "2": { 45 | "name": "books", 46 | "type_": "message", 47 | "label": "repeated", 48 | "default": null 49 | } 50 | } 51 | }, 52 | "EnumSample": { 53 | "kind": "enum", 54 | "values": { 55 | "0": { 56 | "name": "UNKNOWN" 57 | }, 58 | "1": { 59 | "name": "RUNNING" 60 | } 61 | } 62 | }, 63 | "GetBookRequest": { 64 | "kind": "message", 65 | "fields": { 66 | "1": { 67 | "name": "isbn", 68 | "type_": "int64", 69 | "label": "optional", 70 | "default": null 71 | } 72 | } 73 | }, 74 | "GetBookViaAuthor": { 75 | "kind": "message", 76 | "fields": { 77 | "1": { 78 | "name": "author", 79 | "type_": "string", 80 | "label": "optional", 81 | "default": null 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/data/parsing/books.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package com.book; 4 | 5 | message Book { 6 | int64 isbn = 1; 7 | string title = 2; 8 | string author = 3; 9 | } 10 | 11 | message GetBookRequest { 12 | int64 isbn = 1; 13 | } 14 | 15 | message GetBookViaAuthor { 16 | string author = 1; 17 | } 18 | 19 | service BookService { 20 | rpc GetBook (GetBookRequest) returns (Book) {} 21 | rpc GetBooksViaAuthor (GetBookViaAuthor) returns (stream Book) {} 22 | rpc GetGreatestBook (stream GetBookRequest) returns (Book) {} 23 | rpc GetBooks (stream GetBookRequest) returns (stream Book) {} 24 | } 25 | 26 | message BookStore { 27 | string name = 1; 28 | map books = 2; 29 | } 30 | 31 | enum EnumSample { 32 | option allow_alias = true; 33 | UNKNOWN = 0; 34 | STARTED = 1; 35 | RUNNING = 1; 36 | } 37 | -------------------------------------------------------------------------------- /tests/data/projects/api/Proto.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | type = "api" 3 | name = "api" 4 | version = "0.0.1" 5 | -------------------------------------------------------------------------------- /tests/data/projects/api/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/data/projects/api/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/data/projects/lib/Proto.toml: -------------------------------------------------------------------------------- 1 | edition = "0.10" 2 | 3 | [package] 4 | type = "lib" 5 | name = "lib" 6 | version = "0.0.1" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/data/projects/lib/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/data/projects/lib/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/data/projects/user/Proto.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/data/projects/user/Proto.toml -------------------------------------------------------------------------------- /tests/data/projects/user/proto/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helsing-ai/buffrs/951e7fb10325a33b17a96c2af9f5203d069429dc/tests/data/projects/user/proto/vendor/.gitkeep -------------------------------------------------------------------------------- /tests/test_registry.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::net::SocketAddr; 3 | use std::sync::{Arc, RwLock}; 4 | use std::time::Duration; 5 | 6 | use axum::{ 7 | extract, 8 | http::{header, StatusCode}, 9 | response::IntoResponse, 10 | routing::get, 11 | Router, 12 | }; 13 | use bytes::Bytes; 14 | use miette::{miette, Context as _, IntoDiagnostic}; 15 | use tokio::net::TcpListener; 16 | 17 | type State = Arc>>; 18 | 19 | /// Run a minimal registry for local testing 20 | async fn test_registry(listener: TcpListener) -> miette::Result<()> { 21 | let state = Arc::new(RwLock::new(HashMap::::new())); 22 | let app = Router::new() 23 | .route("/*path", get(get_package).put(put_package)) 24 | .with_state(state); 25 | axum::serve(listener, app) 26 | .await 27 | .into_diagnostic() 28 | .wrap_err(miette!("failed to read the token from the user")) 29 | } 30 | 31 | // basic handler that responds with a static string 32 | async fn get_package( 33 | extract::State(state): extract::State, 34 | extract::Path(path): extract::Path, 35 | ) -> Result { 36 | tracing::info!("Downloaded package from {path}"); 37 | let content = state 38 | .read() 39 | .unwrap() 40 | .get(&path) 41 | .cloned() 42 | .ok_or(StatusCode::NOT_FOUND)?; 43 | Ok(([(header::CONTENT_TYPE, "application/x-gzip")], content)) 44 | } 45 | 46 | async fn put_package( 47 | extract::State(state): extract::State, 48 | extract::Path(path): extract::Path, 49 | body: Bytes, 50 | ) { 51 | tracing::info!("Uploaded package to {path} ({} bytes)", body.len()); 52 | state.write().unwrap().insert(path, body); 53 | } 54 | 55 | // do not use (flavor = "current_thread") here because the user-provided function is blocking 56 | #[tokio::main] 57 | pub async fn with_test_registry(f: F) { 58 | // spawn test registry in separate Tokio task 59 | let listen = SocketAddr::new("127.0.0.1".parse().unwrap(), 0); 60 | let listener = TcpListener::bind(listen).await.unwrap(); 61 | let local_addr = listener.local_addr().unwrap(); 62 | let handle = tokio::task::spawn(test_registry(listener)); 63 | 64 | tracing::info!("Listening on {local_addr:?}"); 65 | let url = format!("http://{local_addr}/registry"); 66 | 67 | // wait until the test registry is ready 68 | let dur = Duration::from_millis(10); 69 | let client = reqwest::Client::builder() 70 | .connect_timeout(dur) 71 | .build() 72 | .unwrap(); 73 | loop { 74 | // perform a simple request at an arbitrary URL to check readiness 75 | if client.get(&url).send().await.is_ok() { 76 | break; 77 | } 78 | // check whether the test registry has failed instead of looping indefinitely 79 | assert!(!handle.is_finished(), "test registry ended unexpectedly"); 80 | // no busy wait 81 | tokio::time::sleep(dur).await; 82 | } 83 | 84 | // run user code 85 | f(&url); 86 | } 87 | --------------------------------------------------------------------------------