├── .backstage └── aip-cli-go.yaml ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── release.yml │ └── stale.yml ├── .gitignore ├── .sage ├── .gitignore ├── go.mod ├── go.sum ├── main.go └── proto.go ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── aipcli ├── annotations.go ├── command.go ├── command_test.go ├── completion.go ├── completion_test.go ├── config.go ├── dial.go ├── doc.go ├── flags.go ├── help.go ├── iam.go ├── init.go ├── invoke.go ├── token.go └── use.go ├── cmd ├── examplectl │ ├── gen │ │ ├── einride │ │ │ └── example │ │ │ │ └── freight │ │ │ │ └── v1 │ │ │ │ ├── freight_service.pb.go │ │ │ │ ├── freight_service_cli.pb.go │ │ │ │ ├── shipment.pb.go │ │ │ │ ├── shipper.pb.go │ │ │ │ └── site.pb.go │ │ └── root.go │ └── main.go └── protoc-gen-go-aip-cli │ └── main.go ├── go.mod ├── go.sum ├── internal ├── gengoaipcli │ ├── comments.go │ ├── config.go │ ├── run.go │ └── service.go ├── protoflag │ ├── duration.go │ ├── enum.go │ ├── fieldmask.go │ ├── mapstringstring.go │ ├── primitive.go │ ├── primitivelist.go │ └── timestamp.go ├── protoshell │ ├── enum.go │ ├── enum_test.go │ ├── resourcename.go │ └── resourcename_test.go └── protoversion │ ├── compare.go │ ├── compare_test.go │ ├── stabilitylevel.go │ ├── version.go │ └── version_test.go └── proto ├── .gitignore ├── Makefile ├── buf.gen.example.yaml ├── buf.lock ├── buf.yaml └── einride └── aipcli └── v1 └── values.proto /.backstage/aip-cli-go.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: aip-cli-go 5 | title: AIP CLI Go 6 | description: Generate command line interfaces to your AIP gRPC services. 7 | spec: 8 | type: library 9 | lifecycle: production 10 | owner: platform-engineering 11 | system: backend-sdk 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @einride/platform-engineering 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: gomod 5 | directory: / 6 | schedule: 7 | interval: monthly 8 | groups: 9 | all: 10 | patterns: ["*"] 11 | 12 | - package-ecosystem: github-actions 13 | directory: / 14 | schedule: 15 | interval: monthly 16 | 17 | - package-ecosystem: gomod 18 | directory: .sage 19 | schedule: 20 | interval: monthly 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | make: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Setup Sage 12 | uses: einride/sage/actions/setup@master 13 | with: 14 | go-version: "1.23" 15 | 16 | - name: Make 17 | run: make 18 | 19 | - name: Dry-run semantic-release 20 | run: make semantic-release repo=${{ github.repository }} dry=true 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | permissions: write-all 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Setup Sage 14 | uses: einride/sage/actions/setup@master 15 | with: 16 | go-version: "1.23" 17 | 18 | - name: Make 19 | run: make 20 | 21 | - name: Run semantic-release 22 | run: make semantic-release repo=${{ github.repository }} dry=false 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale 2 | 3 | permissions: 4 | issues: write 5 | pull-requests: write 6 | 7 | on: 8 | workflow_dispatch: 9 | schedule: 10 | # At 08:00 on every Monday. 11 | - cron: "0 8 * * 1" 12 | 13 | jobs: 14 | stale: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/stale@v9 18 | with: 19 | days-before-pr-stale: 180 20 | days-before-pr-close: 14 21 | stale-pr-message: "This PR has been open for **180 days** with no activity. Remove the stale label or add a comment or it will be closed in **14 days**." 22 | days-before-issue-stale: 365 23 | days-before-issue-close: -1 24 | stale-issue-message: "This issue has been open for **365 days** with no activity. Marking as stale." 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | example/ctl/ 3 | -------------------------------------------------------------------------------- /.sage/.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | tools/ 3 | bin/ 4 | build/ 5 | -------------------------------------------------------------------------------- /.sage/go.mod: -------------------------------------------------------------------------------- 1 | module sage 2 | 3 | go 1.22.1 4 | 5 | require go.einride.tech/sage v0.354.0 6 | -------------------------------------------------------------------------------- /.sage/go.sum: -------------------------------------------------------------------------------- 1 | go.einride.tech/sage v0.354.0 h1:K+1uiIaiubbwJOWOzyEKjj9mA8HsTuhqNlo0xvsft7c= 2 | go.einride.tech/sage v0.354.0/go.mod h1:sy9YuK//XVwEZ2wD3f19xVSKEtN8CYtgtBZGpzC3p80= 3 | -------------------------------------------------------------------------------- /.sage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "go.einride.tech/sage/sg" 7 | "go.einride.tech/sage/tools/sgconvco" 8 | "go.einride.tech/sage/tools/sggit" 9 | "go.einride.tech/sage/tools/sggo" 10 | "go.einride.tech/sage/tools/sggolangcilint" 11 | "go.einride.tech/sage/tools/sggosemanticrelease" 12 | "go.einride.tech/sage/tools/sggovulncheck" 13 | "go.einride.tech/sage/tools/sgmdformat" 14 | "go.einride.tech/sage/tools/sgyamlfmt" 15 | ) 16 | 17 | func main() { 18 | sg.GenerateMakefiles( 19 | sg.Makefile{ 20 | Path: sg.FromGitRoot("Makefile"), 21 | DefaultTarget: All, 22 | }, 23 | 24 | sg.Makefile{ 25 | Path: sg.FromGitRoot("proto/Makefile"), 26 | DefaultTarget: Proto.All, 27 | Namespace: Proto{}, 28 | }, 29 | ) 30 | } 31 | 32 | func All(ctx context.Context) error { 33 | sg.Deps(ctx, ConvcoCheck, FormatMarkdown, FormatYAML, Proto.All) 34 | sg.Deps(ctx, GoLint, GoTest, GoVulnCheck) 35 | sg.Deps(ctx, GoModTidy) 36 | sg.Deps(ctx, GitVerifyNoDiff) 37 | return nil 38 | } 39 | 40 | func ConvcoCheck(ctx context.Context) error { 41 | sg.Logger(ctx).Println("checking git commits...") 42 | return sgconvco.Command(ctx, "check", "origin/master..HEAD").Run() 43 | } 44 | 45 | func FormatYAML(ctx context.Context) error { 46 | sg.Logger(ctx).Println("formatting YAML files...") 47 | return sgyamlfmt.Run(ctx) 48 | } 49 | 50 | func FormatMarkdown(ctx context.Context) error { 51 | sg.Logger(ctx).Println("formatting Markdown files...") 52 | return sgmdformat.Command(ctx).Run() 53 | } 54 | 55 | func GoLint(ctx context.Context) error { 56 | sg.Logger(ctx).Println("linting Go files...") 57 | return sggolangcilint.Run(ctx) 58 | } 59 | 60 | func GoModTidy(ctx context.Context) error { 61 | sg.Logger(ctx).Println("tidying Go module files...") 62 | return sg.Command(ctx, "go", "mod", "tidy", "-v").Run() 63 | } 64 | 65 | func GoTest(ctx context.Context) error { 66 | sg.Logger(ctx).Println("running Go tests...") 67 | return sggo.TestCommand(ctx).Run() 68 | } 69 | 70 | func GoVulnCheck(ctx context.Context) error { 71 | sg.Logger(ctx).Println("checking Go files for vulnerabilities...") 72 | return sggovulncheck.RunAll(ctx) 73 | } 74 | 75 | func GitVerifyNoDiff(ctx context.Context) error { 76 | sg.Logger(ctx).Println("verifying that git has no diff...") 77 | return sggit.VerifyNoDiff(ctx) 78 | } 79 | 80 | func SemanticRelease(ctx context.Context, repo string, dry bool) error { 81 | sg.Logger(ctx).Println("creating semantic release...") 82 | args := []string{ 83 | "--allow-initial-development-versions", 84 | "--allow-no-changes", 85 | "--ci-condition=default", 86 | "--provider=github", 87 | "--provider-opt=slug=" + repo, 88 | } 89 | if dry { 90 | args = append(args, "--dry") 91 | } 92 | return sggosemanticrelease.Command(ctx, args...).Run() 93 | } 94 | -------------------------------------------------------------------------------- /.sage/proto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "go.einride.tech/sage/sg" 8 | "go.einride.tech/sage/sgtool" 9 | "go.einride.tech/sage/tools/sgbuf" 10 | ) 11 | 12 | type Proto sg.Namespace 13 | 14 | func (Proto) All(ctx context.Context) error { 15 | sg.Deps(ctx, Proto.BufFormat, Proto.BufLint) 16 | sg.Deps(ctx, Proto.CleanGeneratedProto) 17 | sg.Deps(ctx, Proto.BufGenerateExample) 18 | return nil 19 | } 20 | 21 | func (Proto) BufLint(ctx context.Context) error { 22 | sg.Logger(ctx).Println("linting proto files...") 23 | cmd := sgbuf.Command(ctx, "lint") 24 | cmd.Dir = sg.FromGitRoot("proto") 25 | return cmd.Run() 26 | } 27 | 28 | func (Proto) BufFormat(ctx context.Context) error { 29 | sg.Logger(ctx).Println("formating proto files...") 30 | cmd := sgbuf.Command(ctx, "format", "--write") 31 | cmd.Dir = sg.FromGitRoot("proto") 32 | return cmd.Run() 33 | } 34 | 35 | func (Proto) BufPush(ctx context.Context) error { 36 | sg.Logger(ctx).Println("pushing proto module...") 37 | cmd := sgbuf.Command(ctx, "push") 38 | cmd.Dir = sg.FromGitRoot("proto") 39 | return cmd.Run() 40 | } 41 | 42 | func (Proto) ProtocGenGo(ctx context.Context) error { 43 | sg.Logger(ctx).Println("installing...") 44 | _, err := sgtool.GoInstallWithModfile(ctx, "google.golang.org/protobuf/cmd/protoc-gen-go", sg.FromGitRoot("go.mod")) 45 | return err 46 | } 47 | 48 | func (Proto) ProtocGenGoAIPCLI(ctx context.Context) error { 49 | sg.Logger(ctx).Println("building binary...") 50 | return sg.Command( 51 | ctx, 52 | "go", 53 | "build", 54 | "-o", 55 | sg.FromBinDir("protoc-gen-go-aip-cli"), 56 | sg.FromGitRoot("cmd", "protoc-gen-go-aip-cli"), 57 | ).Run() 58 | } 59 | 60 | func (Proto) CleanGeneratedProto(ctx context.Context) error { 61 | sg.Logger(ctx).Println("cleaning generated proto files...") 62 | return os.RemoveAll(sg.FromGitRoot("cmd", "examplectl", "gen")) 63 | } 64 | 65 | func (Proto) BufGenerateExample(ctx context.Context) error { 66 | sg.Deps(ctx, Proto.ProtocGenGo, Proto.ProtocGenGoAIPCLI) 67 | sg.Logger(ctx).Println("generating example proto stubs...") 68 | cmd := sgbuf.Command( 69 | ctx, 70 | "generate", 71 | "buf.build/einride/aip", 72 | "--output", 73 | sg.FromGitRoot(), 74 | "--template", 75 | "buf.gen.example.yaml", 76 | "--path", 77 | "einride/example/freight", 78 | ) 79 | cmd.Dir = sg.FromGitRoot("proto") 80 | return cmd.Run() 81 | } 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | open-source@einride.tech. 64 | 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the 119 | [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, 120 | available at 121 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 122 | 123 | Community Impact Guidelines were inspired by 124 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Einride AB 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Code generated by go.einride.tech/sage. DO NOT EDIT. 2 | # To learn more, see .sage/main.go and https://github.com/einride/sage. 3 | 4 | .DEFAULT_GOAL := all 5 | 6 | cwd := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) 7 | sagefile := $(abspath $(cwd)/.sage/bin/sagefile) 8 | 9 | # Setup Go. 10 | go := $(shell command -v go 2>/dev/null) 11 | export GOWORK ?= off 12 | ifndef go 13 | SAGE_GO_VERSION ?= 1.23.4 14 | export GOROOT := $(abspath $(cwd)/.sage/tools/go/$(SAGE_GO_VERSION)/go) 15 | export PATH := $(PATH):$(GOROOT)/bin 16 | go := $(GOROOT)/bin/go 17 | os := $(shell uname | tr '[:upper:]' '[:lower:]') 18 | arch := $(shell uname -m) 19 | ifeq ($(arch),x86_64) 20 | arch := amd64 21 | endif 22 | $(go): 23 | $(info installing Go $(SAGE_GO_VERSION)...) 24 | @mkdir -p $(dir $(GOROOT)) 25 | @curl -sSL https://go.dev/dl/go$(SAGE_GO_VERSION).$(os)-$(arch).tar.gz | tar xz -C $(dir $(GOROOT)) 26 | @touch $(GOROOT)/go.mod 27 | @chmod +x $(go) 28 | endif 29 | 30 | .PHONY: $(sagefile) 31 | $(sagefile): $(go) 32 | @cd .sage && $(go) mod tidy && $(go) run . 33 | 34 | .PHONY: sage 35 | sage: 36 | @$(MAKE) $(sagefile) 37 | 38 | .PHONY: update-sage 39 | update-sage: $(go) 40 | @cd .sage && $(go) get -d go.einride.tech/sage@latest && $(go) mod tidy && $(go) run . 41 | 42 | .PHONY: clean-sage 43 | clean-sage: 44 | @git clean -fdx .sage/tools .sage/bin .sage/build 45 | 46 | .PHONY: all 47 | all: $(sagefile) 48 | @$(sagefile) All 49 | 50 | .PHONY: convco-check 51 | convco-check: $(sagefile) 52 | @$(sagefile) ConvcoCheck 53 | 54 | .PHONY: format-markdown 55 | format-markdown: $(sagefile) 56 | @$(sagefile) FormatMarkdown 57 | 58 | .PHONY: format-yaml 59 | format-yaml: $(sagefile) 60 | @$(sagefile) FormatYAML 61 | 62 | .PHONY: git-verify-no-diff 63 | git-verify-no-diff: $(sagefile) 64 | @$(sagefile) GitVerifyNoDiff 65 | 66 | .PHONY: go-lint 67 | go-lint: $(sagefile) 68 | @$(sagefile) GoLint 69 | 70 | .PHONY: go-mod-tidy 71 | go-mod-tidy: $(sagefile) 72 | @$(sagefile) GoModTidy 73 | 74 | .PHONY: go-test 75 | go-test: $(sagefile) 76 | @$(sagefile) GoTest 77 | 78 | .PHONY: go-vuln-check 79 | go-vuln-check: $(sagefile) 80 | @$(sagefile) GoVulnCheck 81 | 82 | .PHONY: semantic-release 83 | semantic-release: $(sagefile) 84 | ifndef repo 85 | $(error missing argument repo="...") 86 | endif 87 | ifndef dry 88 | $(error missing argument dry="...") 89 | endif 90 | @$(sagefile) SemanticRelease "$(repo)" "$(dry)" 91 | 92 | .PHONY: proto 93 | proto: 94 | $(MAKE) -C proto -f Makefile 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AIP CLI Go 2 | 3 | Generate command line interfaces to your [AIP](https://aip.dev) gRPC services. 4 | 5 | ## How to 6 | 7 | ### Step 1: Install the plugin 8 | 9 | ```bash 10 | $ GOBIN=$PWD/build go install go.einride.tech/aip-cli/cmd/protoc-gen-go-aip-cli 11 | ``` 12 | 13 | ### Step 2: Configure the code generator 14 | 15 | The following example uses a 16 | [buf generate](https://docs.buf.build/generate/usage) template to configure the 17 | CLI generator. 18 | 19 | [buf.gen.example.yaml](./proto/buf.gen.example.yaml): 20 | 21 | ```yaml 22 | version: v1 23 | 24 | managed: 25 | enabled: true 26 | go_package_prefix: 27 | default: go.einride.tech/aip-cli/cmd/examplectl 28 | except: 29 | - buf.build/googleapis/googleapis 30 | 31 | plugins: 32 | # The CLI generator requires the stubs generated by protoc-gen-go. 33 | - name: go 34 | out: cmd/examplectl 35 | opt: module=go.einride.tech/aip-cli/cmd/examplectl 36 | 37 | # The CLI generator optionally generates a root command and a main file 38 | # to the root of the output module. 39 | - name: go-aip-cli 40 | out: cmd/examplectl 41 | strategy: all 42 | opt: 43 | - module=go.einride.tech/aip-cli/cmd/examplectl 44 | - root=examplectl 45 | - gcloud_identity_tokens=true 46 | ``` 47 | 48 | ### Step 3: Generate the code 49 | 50 | ```bash 51 | $ buf generate \ 52 | --template buf.gen.example.yaml \ 53 | --path einride/example/freight 54 | ``` 55 | 56 | ### Step 4: Install the CLI 57 | 58 | ```bash 59 | $ go install ./cmd/examplectl 60 | ``` 61 | 62 | ### Step 5: Use the CLI 63 | 64 | ```bash 65 | $ examplectl help freight 66 | 67 | this api represents a simple freight service 68 | 69 | Usage: 70 | examplectl freight [command] 71 | 72 | Available Commands: 73 | batch-get-sites batch get sites 74 | create-shipment create a shipment 75 | create-shipper create a shipper 76 | create-site create a site 77 | delete-shipment delete a shipment 78 | delete-shipper delete a shipper 79 | delete-site delete a site 80 | get-shipment get a shipment 81 | get-shipper get a shipper 82 | get-site get a site 83 | list-shipments list shipments for a shipper 84 | list-shippers list shippers 85 | list-sites list sites for a shipper 86 | update-shipment update a shipment 87 | update-shipper update a shipper 88 | update-site update a site 89 | 90 | Flags: 91 | -h, --help help for freight 92 | 93 | Global Flags: 94 | --address string address to connect to 95 | --insecure make insecure client connection (only on localhost) 96 | --token string bearer token used by client 97 | -v, --verbose print verbose output 98 | 99 | Use "examplectl freight [command] --help" for more information about a command. 100 | ``` 101 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Einride welcomes feedback from security researchers and the general public to 4 | help improve our security. If you believe you have discovered a vulnerability, 5 | privacy issue, exposed data, or other security issues in relation to this 6 | project, we want to hear from you. This policy outlines steps for reporting 7 | security issues to us, what we expect, and what you can expect from us. 8 | 9 | ## Supported versions 10 | 11 | We release patches for security issues according to semantic versioning. This 12 | project is currently unstable (v0.x) and only the latest version will receive 13 | security patches. 14 | 15 | ## Reporting a vulnerability 16 | 17 | Please do not report security vulnerabilities through public issues, 18 | discussions, or change requests. 19 | 20 | Please report security issues via [oss-security@einride.tech][email]. Provide 21 | all relevant information, including steps to reproduce the issue, any affected 22 | versions, and known mitigations. The more details you provide, the easier it 23 | will be for us to triage and fix the issue. You will receive a response from us 24 | within 2 business days. If the issue is confirmed, a patch will be released as 25 | soon as possible. 26 | 27 | For more information, or security issues not relating to open source code, 28 | please consult our [Vulnerability Disclosure Policy][vdp]. 29 | 30 | ## Preferred languages 31 | 32 | English is our preferred language of communication. 33 | 34 | ## Contributions and recognition 35 | 36 | We appreciate every contribution and will do our best to publicly 37 | [acknowledge][acknowledgments] your contributions. 38 | 39 | [acknowledgments]: https://einride.tech/security-acknowledgments.txt 40 | [email]: mailto:oss-security@einride.tech 41 | [vdp]: https://www.einride.tech/vulnerability-disclosure-policy 42 | -------------------------------------------------------------------------------- /aipcli/annotations.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | const ( 4 | moduleAnnotation = "aip_cli_annotation_module_name" 5 | serviceAnnotation = "aip_cli_annotation_service_name" 6 | methodAnnotation = "aip_cli_annotation_method_name" 7 | fieldBehaviorsAnnotation = "aip_cli_annotation_flag_field_behavior" 8 | fieldNameAnnotation = "aip_cli_annotation_flag_field_name" 9 | flagArgumentAnnotation = "aip_cli_annotation_flag_argument" 10 | flagHostAnnotation = "aip_cli_annotation_flag_host" 11 | ) 12 | -------------------------------------------------------------------------------- /aipcli/command.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | "google.golang.org/protobuf/encoding/protojson" 8 | "google.golang.org/protobuf/proto" 9 | "google.golang.org/protobuf/reflect/protoreflect" 10 | ) 11 | 12 | // NewCommand initializes a new *cobra.Command with AIP CLI flags and help functions. 13 | func NewCommand(cmd *cobra.Command, commands ...*cobra.Command) *cobra.Command { 14 | initPersistentFlags(cmd) 15 | cmd.SetHelpFunc(helpFunc) 16 | cmd.SetUsageFunc(usageFunc) 17 | deduplicateServiceCommandUses(commands) 18 | cmd.AddCommand(commands...) 19 | return cmd 20 | } 21 | 22 | // NewModuleCommand initializes a new *cobra.Command for a CLI module. 23 | // A module is a collection of services with a common CLI config. 24 | func NewModuleCommand( 25 | use string, 26 | short string, 27 | config Config, 28 | commands ...*cobra.Command, 29 | ) *cobra.Command { 30 | cmd := NewCommand(&cobra.Command{ 31 | Use: use, 32 | Short: short, 33 | Annotations: map[string]string{ 34 | moduleAnnotation: use, 35 | }, 36 | }, commands...) 37 | setConfig(cmd, config) 38 | return cmd 39 | } 40 | 41 | // NewServiceCommand initializes a new *cobra.Command for the provided gRPC service. 42 | func NewServiceCommand( 43 | config Config, 44 | service protoreflect.ServiceDescriptor, 45 | comments map[protoreflect.FullName]string, 46 | commands ...*cobra.Command, 47 | ) *cobra.Command { 48 | cmd := NewCommand(&cobra.Command{ 49 | Use: serviceUse(service.FullName()), 50 | Short: initialUpperCase(trimFieldComment(comments[service.FullName()])), 51 | Long: trimLongComment(comments[service.FullName()]), 52 | Annotations: map[string]string{ 53 | serviceAnnotation: string(service.FullName()), 54 | }, 55 | }, commands...) 56 | setConfig(cmd, config) 57 | return cmd 58 | } 59 | 60 | // NewMethodCommand initializes a new *cobra.Command for the provided gRPC method. 61 | func NewMethodCommand( 62 | config Config, 63 | method protoreflect.MethodDescriptor, 64 | in proto.Message, 65 | out proto.Message, 66 | comments map[protoreflect.FullName]string, 67 | ) *cobra.Command { 68 | cmd := NewCommand(&cobra.Command{ 69 | Use: methodUse(method), 70 | Short: initialUpperCase(trimFieldComment(comments[method.FullName()])), 71 | Long: trimLongComment(comments[method.FullName()]), 72 | PersistentPreRun: func(cmd *cobra.Command, _ []string) { 73 | initContext(cmd, config) 74 | setDefaultHostFromProto(cmd, method) 75 | }, 76 | Annotations: map[string]string{ 77 | methodAnnotation: string(method.FullName()), 78 | }, 79 | }) 80 | setConfig(cmd, config) 81 | fromFile := cmd.Flags().StringP("from-file", "f", "", "path to a JSON file containing the request payload") 82 | _ = cmd.MarkFlagFilename("from-file", "json") 83 | setFlags(comments, cmd, nil, in.ProtoReflect().Descriptor(), in.ProtoReflect) 84 | cmd.RunE = func(cmd *cobra.Command, _ []string) error { 85 | if cmd.Flags().Changed("from-file") { 86 | data, err := os.ReadFile(*fromFile) 87 | if err != nil { 88 | return err 89 | } 90 | if err := protojson.Unmarshal(data, in); err != nil { 91 | return err 92 | } 93 | } 94 | return invoke(cmd, methodURI(method), in, out) 95 | } 96 | return cmd 97 | } 98 | -------------------------------------------------------------------------------- /aipcli/command_test.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "testing" 5 | 6 | "google.golang.org/protobuf/reflect/protoreflect" 7 | "gotest.tools/v3/assert" 8 | ) 9 | 10 | func Test_qualifiedServiceUse(t *testing.T) { 11 | for _, tt := range []struct { 12 | name string 13 | expected []string 14 | }{ 15 | { 16 | name: "google.iam.v1.IAMPolicy", 17 | expected: []string{ 18 | "iam-policy", 19 | "iam-policy-v1", 20 | "iam-policy-v1-iam", 21 | "iam-policy-v1-iam-google", 22 | }, 23 | }, 24 | } { 25 | t.Run(tt.name, func(t *testing.T) { 26 | actual := make([]string, 0, len(tt.expected)) 27 | var i int 28 | for { 29 | result, ok := qualifiedServiceUse(protoreflect.FullName(tt.name), i) 30 | if !ok { 31 | break 32 | } 33 | actual = append(actual, result) 34 | i++ 35 | } 36 | assert.DeepEqual(t, tt.expected, actual) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /aipcli/completion.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/spf13/cobra" 8 | "go.einride.tech/aip-cli/internal/protoshell" 9 | "google.golang.org/protobuf/reflect/protoreflect" 10 | ) 11 | 12 | type CompletionFunc func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) 13 | 14 | func getResourceNameActiveHelp(comment string, patterns ...string) string { 15 | result := trimFieldComment(comment) 16 | filteredPatterns := make([]string, 0, len(patterns)) 17 | for _, pattern := range patterns { 18 | if !strings.Contains(result, pattern) { 19 | filteredPatterns = append(filteredPatterns, pattern) 20 | } 21 | } 22 | if len(filteredPatterns) > 0 { 23 | result += " (" + strings.Join(patterns, " or ") + ")" 24 | } 25 | return result 26 | } 27 | 28 | func fieldCompletionFunc(comment string) CompletionFunc { 29 | return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { 30 | return cobra.AppendActiveHelp(nil, trimFieldComment(comment)), 31 | cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp 32 | } 33 | } 34 | 35 | func enumFieldCompletionFunc(comment string, values protoreflect.EnumValueDescriptors) CompletionFunc { 36 | return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { 37 | return cobra.AppendActiveHelp(protoshell.CompleteEnumValue(toComplete, values), trimFieldComment(comment)), 38 | cobra.ShellCompDirectiveNoFileComp 39 | } 40 | } 41 | 42 | func timestampCompletionFunc(comment string) CompletionFunc { 43 | return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { 44 | activeHelp := trimFieldComment(comment) 45 | activeHelp += " [tip: prefix with = to evaluate a CEL expression, e.g. ='now()-duration(\"2h\")']" 46 | return cobra.AppendActiveHelp(nil, activeHelp), 47 | cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp 48 | } 49 | } 50 | 51 | func resourceNameCompletionFunc(comment string, patterns ...string) CompletionFunc { 52 | return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { 53 | result := make([]string, 0, len(patterns)) 54 | for _, pattern := range patterns { 55 | if completion, ok := protoshell.CompleteResourceName(toComplete, pattern); ok { 56 | result = append(result, fmt.Sprintf("%s\t%s", completion, pattern)) 57 | } 58 | } 59 | result = cobra.AppendActiveHelp(result, getResourceNameActiveHelp(comment, patterns...)) 60 | return result, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp 61 | } 62 | } 63 | 64 | func resourceNameListCompletionFunc(comment string, patterns ...string) CompletionFunc { 65 | return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { 66 | toCompleteElements := strings.Split(toComplete, ",") 67 | lastToCompleteElement := toCompleteElements[len(toCompleteElements)-1] 68 | result := make([]string, 0, len(patterns)) 69 | for _, pattern := range patterns { 70 | if elementCompletion, ok := protoshell.CompleteResourceName(lastToCompleteElement, pattern); ok { 71 | var completion string 72 | if len(toCompleteElements) > 1 { 73 | completion = strings.Join( 74 | append(toCompleteElements[:len(toCompleteElements)-1], elementCompletion), 75 | ",", 76 | ) 77 | } else { 78 | completion = elementCompletion 79 | } 80 | result = append(result, fmt.Sprintf("%s\t%s", completion, pattern)) 81 | } 82 | } 83 | result = cobra.AppendActiveHelp(result, getResourceNameActiveHelp(comment, patterns...)) 84 | return result, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /aipcli/completion_test.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | -------------------------------------------------------------------------------- /aipcli/config.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "google.golang.org/genproto/googleapis/api/annotations" 9 | "google.golang.org/protobuf/proto" 10 | "google.golang.org/protobuf/reflect/protoreflect" 11 | ) 12 | 13 | // Config is the configuration for the protoc-gen-go-aip-cli plugin. 14 | type Config struct { 15 | // Hosts is a map from host ID to host address. 16 | Hosts map[string]string 17 | // DefaultHost is the host ID of the default host. 18 | DefaultHost string 19 | // RootPath is where the root file will be located. 20 | RootPath string 21 | // Root is the name of the root module package. 22 | Root string 23 | // GoogleCloudIdentityTokens indicates if Google Cloud Identity Tokens should be automatically generated. 24 | GoogleCloudIdentityTokens bool 25 | // Uses in a predetermined file relative to local config path for the Identity Token. 26 | CachedIdentityTokenPath string 27 | } 28 | 29 | const ( 30 | addressFlag = "address" 31 | tokenFlag = "token" 32 | insecureFlag = "insecure" 33 | verboseFlag = "verbose" 34 | forceTraceFlag = "force-trace" 35 | ) 36 | 37 | type contextKey struct{} 38 | 39 | type contextValue struct { 40 | config Config 41 | defaultHostFromProto string 42 | } 43 | 44 | func initContext(cmd *cobra.Command, config Config) { 45 | ctx := cmd.Context() 46 | if ctx == nil { 47 | ctx = context.Background() 48 | } 49 | cmd.SetContext(context.WithValue(ctx, contextKey{}, &contextValue{ 50 | config: config, 51 | })) 52 | cmd.SetOut(os.Stdout) 53 | } 54 | 55 | func initPersistentFlags(cmd *cobra.Command) { 56 | if cmd.PersistentFlags().Lookup(addressFlag) == nil { 57 | cmd.PersistentFlags().String(addressFlag, "", "Connect to address") 58 | } 59 | if cmd.PersistentFlags().Lookup(insecureFlag) == nil { 60 | cmd.PersistentFlags().Bool(insecureFlag, false, "Make insecure connection (only on localhost)") 61 | } 62 | if cmd.PersistentFlags().Lookup(tokenFlag) == nil { 63 | cmd.PersistentFlags().String(tokenFlag, "", "Authenticate with a bearer token") 64 | } 65 | if cmd.PersistentFlags().Lookup(verboseFlag) == nil { 66 | cmd.PersistentFlags().BoolP(verboseFlag, "v", false, "Enable verbose output") 67 | } 68 | if cmd.PersistentFlags().Lookup(forceTraceFlag) == nil { 69 | cmd.PersistentFlags().BoolP(forceTraceFlag, "", false, "Enable forced Google Cloud Trace header") 70 | } 71 | if cmd.PersistentFlags().Lookup("help") == nil { 72 | cmd.PersistentFlags().BoolP("help", "h", false, "Show help for command") 73 | } 74 | } 75 | 76 | func setConfig(cmd *cobra.Command, config Config) { 77 | initContext(cmd, config) 78 | for host, hostAddress := range config.Hosts { 79 | if cmd.PersistentFlags().Lookup(host) == nil { 80 | help := "Connect to " + host + " host (" + hostAddress + ")" 81 | if host == config.DefaultHost { 82 | help += " [default]" 83 | } 84 | cmd.PersistentFlags().Bool(host, false, help) 85 | _ = cmd.PersistentFlags().SetAnnotation(host, flagHostAnnotation, []string{hostAddress}) 86 | } 87 | } 88 | } 89 | 90 | func setDefaultHostFromProto(cmd *cobra.Command, method protoreflect.MethodDescriptor) { 91 | if value, ok := cmd.Context().Value(contextKey{}).(*contextValue); ok { 92 | if defaultHost, ok := proto.GetExtension( 93 | method.Parent().Options(), 94 | annotations.E_DefaultHost, 95 | ).(string); ok { 96 | value.defaultHostFromProto = defaultHost 97 | } 98 | } 99 | } 100 | 101 | func defaultHostFromProto(cmd *cobra.Command) (string, bool) { 102 | result := cmd.Context().Value(contextKey{}).(*contextValue).defaultHostFromProto 103 | return result, result != "" 104 | } 105 | 106 | func getContext(cmd *cobra.Command) (context.Context, bool) { 107 | ctx := cmd.Context() 108 | if ctx != nil { 109 | return ctx, true 110 | } 111 | parent := cmd.Parent() 112 | for parent != nil { 113 | ctx = parent.Context() 114 | if ctx != nil { 115 | return ctx, true 116 | } 117 | parent = parent.Parent() 118 | } 119 | return nil, false 120 | } 121 | 122 | func GetConfig(cmd *cobra.Command) Config { 123 | ctx, ok := getContext(cmd) 124 | if !ok { 125 | return Config{} 126 | } 127 | if value, ok := ctx.Value(contextKey{}).(*contextValue); ok { 128 | return value.config 129 | } 130 | return Config{} 131 | } 132 | 133 | func getAddress(cmd *cobra.Command) (string, bool) { 134 | if flagAddress, err := cmd.Flags().GetString(addressFlag); err == nil && flagAddress != "" { 135 | return flagAddress, true 136 | } 137 | for host, hostAddress := range GetConfig(cmd).Hosts { 138 | if useHost, err := cmd.Flags().GetBool(host); err == nil && useHost { 139 | return hostAddress, true 140 | } 141 | } 142 | if defaultHostFromConfig := GetConfig(cmd).DefaultHost; defaultHostFromConfig != "" { 143 | for host, hostAddress := range GetConfig(cmd).Hosts { 144 | if host == defaultHostFromConfig { 145 | return hostAddress, true 146 | } 147 | } 148 | } 149 | return defaultHostFromProto(cmd) 150 | } 151 | 152 | func getToken(cmd *cobra.Command) (string, bool) { 153 | if flagToken, err := cmd.Flags().GetString(tokenFlag); err == nil && flagToken != "" { 154 | return flagToken, true 155 | } 156 | 157 | if GetConfig(cmd).CachedIdentityTokenPath != "" { 158 | tokenFile := GetConfig(cmd).CachedIdentityTokenPath 159 | identityToken, err := identityTokenFromConfigFile(tokenFile) 160 | if err != nil { 161 | return "", false 162 | } 163 | return identityToken, true 164 | } 165 | 166 | if GetConfig(cmd).GoogleCloudIdentityTokens { 167 | return gcloudAuthPrintIdentityToken() 168 | } 169 | return "", false 170 | } 171 | 172 | func isInsecure(cmd *cobra.Command) bool { 173 | result, err := cmd.Flags().GetBool(insecureFlag) 174 | return result && err == nil 175 | } 176 | 177 | func IsVerbose(cmd *cobra.Command) bool { 178 | result, err := cmd.Flags().GetBool(verboseFlag) 179 | return result && err == nil 180 | } 181 | 182 | func isForceTrace(cmd *cobra.Command) bool { 183 | result, err := cmd.Flags().GetBool(forceTraceFlag) 184 | return result && err == nil 185 | } 186 | -------------------------------------------------------------------------------- /aipcli/dial.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/x509" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "fmt" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/spf13/cobra" 14 | "golang.org/x/oauth2" 15 | "google.golang.org/grpc" 16 | "google.golang.org/grpc/credentials" 17 | "google.golang.org/grpc/credentials/insecure" 18 | "google.golang.org/grpc/credentials/oauth" 19 | "google.golang.org/grpc/metadata" 20 | "google.golang.org/protobuf/reflect/protoreflect" 21 | ) 22 | 23 | func dial(cmd *cobra.Command) (*grpc.ClientConn, error) { 24 | if isInsecure(cmd) { 25 | return dialInsecure(cmd) 26 | } 27 | address, ok := getAddress(cmd) 28 | if !ok { 29 | return nil, fmt.Errorf("dial: no address") 30 | } 31 | if IsVerbose(cmd) { 32 | cmd.PrintErrln(">> address:", address) 33 | } 34 | var opts []grpc.DialOption 35 | if token, ok := getToken(cmd); ok { 36 | opts = append( 37 | opts, 38 | grpc.WithPerRPCCredentials( 39 | oauth.TokenSource{ 40 | TokenSource: oauth2.StaticTokenSource( 41 | &oauth2.Token{ 42 | AccessToken: token, 43 | TokenType: "Bearer", 44 | }, 45 | ), 46 | }, 47 | ), 48 | ) 49 | } 50 | if isForceTrace(cmd) { 51 | opts = append(opts, withForceTrace(cmd)) 52 | } 53 | systemCertPool, err := x509.SystemCertPool() 54 | if err != nil { 55 | return nil, err 56 | } 57 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(systemCertPool, ""))) 58 | return grpc.NewClient(withDefaultPort(address, 443), opts...) 59 | } 60 | 61 | func dialInsecure(cmd *cobra.Command) (*grpc.ClientConn, error) { 62 | token, _ := getToken(cmd) 63 | address, hasAddress := getAddress(cmd) 64 | if !hasAddress { 65 | return nil, fmt.Errorf("must provide --address with --insecure") 66 | } 67 | if IsVerbose(cmd) { 68 | cmd.PrintErrln(">> insecure address:", address) 69 | } 70 | opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} 71 | if token != "" { 72 | opts = append(opts, grpc.WithPerRPCCredentials(insecureTokenCredentials(token))) 73 | } 74 | if isForceTrace(cmd) { 75 | opts = append(opts, withForceTrace(cmd)) 76 | } 77 | return grpc.NewClient(withDefaultPort(address, 443), opts...) 78 | } 79 | 80 | func methodURI(method protoreflect.MethodDescriptor) string { 81 | return "/" + 82 | string(method.Parent().(protoreflect.ServiceDescriptor).FullName()) + 83 | "/" + string(method.Name()) 84 | } 85 | 86 | type insecureTokenCredentials string 87 | 88 | func (c insecureTokenCredentials) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { 89 | return map[string]string{ 90 | "authorization": "Bearer " + string(c), 91 | }, nil 92 | } 93 | 94 | func (insecureTokenCredentials) RequireTransportSecurity() bool { 95 | return false 96 | } 97 | 98 | func withDefaultPort(target string, port int) string { 99 | parts := strings.Split(target, ":") 100 | if len(parts) == 1 { 101 | return target + ":" + strconv.Itoa(port) 102 | } 103 | return target 104 | } 105 | 106 | func withForceTrace(cmd *cobra.Command) grpc.DialOption { 107 | traceID := generateTraceID() 108 | spanID := generateSpanID() 109 | return grpc.WithUnaryInterceptor(func( 110 | ctx context.Context, 111 | method string, 112 | req interface{}, 113 | reply interface{}, 114 | cc *grpc.ClientConn, 115 | invoker grpc.UnaryInvoker, 116 | opts ...grpc.CallOption, 117 | ) error { 118 | // See https://cloud.google.com/trace/docs/setup#force-trace 119 | const header = "x-cloud-trace-context" 120 | value := fmt.Sprintf("%s/%d;o=1", traceID, spanID) 121 | cmd.PrintErrln(">> trace ID:", traceID) 122 | if IsVerbose(cmd) { 123 | cmd.PrintErrln(">> span ID:", spanID) 124 | cmd.PrintErrln(">> trace header:", header, "=", value) 125 | } 126 | ctx = metadata.AppendToOutgoingContext(ctx, header, value) 127 | return invoker(ctx, method, req, reply, cc, opts...) 128 | }) 129 | } 130 | 131 | func generateTraceID() string { 132 | var id [16]byte 133 | _, _ = rand.Read(id[:]) // panics on error 134 | return hex.EncodeToString(id[:]) 135 | } 136 | 137 | func generateSpanID() uint64 { 138 | var id [8]byte 139 | _, _ = rand.Read(id[:]) // panics on error 140 | return binary.LittleEndian.Uint64(id[:]) 141 | } 142 | -------------------------------------------------------------------------------- /aipcli/doc.go: -------------------------------------------------------------------------------- 1 | // Package aipcli provides runtime APIs for generated CLIs. 2 | package aipcli 3 | -------------------------------------------------------------------------------- /aipcli/flags.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/spf13/cobra" 11 | "github.com/spf13/pflag" 12 | protovalue "go.einride.tech/aip-cli/internal/protoflag" 13 | "go.einride.tech/aip/reflect/aipreflect" 14 | "google.golang.org/genproto/googleapis/api/annotations" 15 | "google.golang.org/protobuf/proto" 16 | "google.golang.org/protobuf/reflect/protoreflect" 17 | "google.golang.org/protobuf/reflect/protoregistry" 18 | ) 19 | 20 | const ( 21 | FieldResourceName = "name" 22 | ) 23 | 24 | func setFlags( 25 | comments map[protoreflect.FullName]string, 26 | cmd *cobra.Command, 27 | parentFields []protoreflect.FieldDescriptor, 28 | msg protoreflect.MessageDescriptor, 29 | mutable func() protoreflect.Message, 30 | ) { 31 | for i := 0; i < msg.Fields().Len(); i++ { 32 | field := msg.Fields().Get(i) 33 | switch field.Kind() { 34 | case protoreflect.MessageKind: 35 | switch field.Message().FullName() { 36 | case "google.protobuf.Duration": 37 | if !field.IsList() { 38 | addFlag(cmd, field, parentFields, comments[field.FullName()], protovalue.Duration(mutable, field)) 39 | } 40 | // TODO: Implement support for repeated durations. 41 | case "google.protobuf.Timestamp": 42 | if !field.IsList() { 43 | addFlag(cmd, field, parentFields, comments[field.FullName()], protovalue.Timestamp(mutable, field)) 44 | } 45 | // TODO: Implement support for repeated timestamps. 46 | case "google.protobuf.FieldMask": 47 | if !field.IsList() { 48 | addFlag(cmd, field, parentFields, comments[field.FullName()], protovalue.FieldMask(mutable, field)) 49 | } 50 | // Repeated field masks is intentionally not supported. 51 | default: 52 | switch { 53 | case field.IsMap(): 54 | switch { 55 | case field.MapKey().Kind() == protoreflect.StringKind && 56 | field.MapValue().Kind() == protoreflect.StringKind: 57 | addFlag( 58 | cmd, 59 | field, 60 | parentFields, 61 | comments[field.FullName()], 62 | protovalue.MapStringString(mutable, field), 63 | ) 64 | default: 65 | // TODO: Implement support for more map types. 66 | } 67 | case field.IsList(): 68 | // Repeated nested messages not supported. 69 | default: 70 | setFlags( 71 | comments, 72 | cmd, 73 | append(parentFields, field), 74 | field.Message(), 75 | func() protoreflect.Message { 76 | return mutable().Mutable(field).Message() 77 | }, 78 | ) 79 | } 80 | } 81 | case protoreflect.EnumKind: 82 | if field.IsList() { 83 | addFlag(cmd, field, parentFields, comments[field.FullName()], protovalue.EnumList(mutable, field)) 84 | } else { 85 | addFlag(cmd, field, parentFields, comments[field.FullName()], protovalue.Enum(mutable, field)) 86 | } 87 | case protoreflect.StringKind, protoreflect.BoolKind, protoreflect.BytesKind, protoreflect.DoubleKind, 88 | protoreflect.FloatKind, protoreflect.Int64Kind, protoreflect.Int32Kind: 89 | setPrimitiveFlag(comments, cmd, parentFields, mutable, field) 90 | } 91 | } 92 | } 93 | 94 | func setPrimitiveFlag( 95 | comments map[protoreflect.FullName]string, 96 | cmd *cobra.Command, 97 | parentFields []protoreflect.FieldDescriptor, 98 | mutable func() protoreflect.Message, 99 | field protoreflect.FieldDescriptor, 100 | ) { 101 | var value pflag.Value 102 | switch field.Kind() { 103 | case protoreflect.BoolKind: 104 | if field.IsList() { 105 | value = protovalue.PrimitiveList[bool](mutable, field, protoreflect.ValueOfBool, strconv.ParseBool) 106 | } else { 107 | value = protovalue.Primitive[bool](mutable, field, protoreflect.ValueOfBool, strconv.ParseBool) 108 | } 109 | case protoreflect.StringKind: 110 | parser := func(s string) (string, error) { 111 | return s, nil 112 | } 113 | if field.IsList() { 114 | value = protovalue.PrimitiveList[string](mutable, field, protoreflect.ValueOfString, parser) 115 | } else { 116 | value = protovalue.Primitive[string](mutable, field, protoreflect.ValueOfString, parser) 117 | } 118 | case protoreflect.BytesKind: 119 | value = protovalue.Primitive[[]byte](mutable, field, protoreflect.ValueOfBytes, base64.URLEncoding.DecodeString) 120 | case protoreflect.DoubleKind: 121 | parser := func(s string) (float64, error) { 122 | return strconv.ParseFloat(s, 64) 123 | } 124 | if field.IsList() { 125 | value = protovalue.PrimitiveList[float64](mutable, field, protoreflect.ValueOfFloat64, parser) 126 | } else { 127 | value = protovalue.Primitive[float64](mutable, field, protoreflect.ValueOfFloat64, parser) 128 | } 129 | case protoreflect.FloatKind: 130 | parser := func(s string) (float32, error) { 131 | d, err := strconv.ParseFloat(s, 32) 132 | if err != nil { 133 | return 0, err 134 | } 135 | return float32(d), nil 136 | } 137 | if field.IsList() { 138 | value = protovalue.PrimitiveList[float32](mutable, field, protoreflect.ValueOfFloat32, parser) 139 | } else { 140 | value = protovalue.Primitive[float32](mutable, field, protoreflect.ValueOfFloat32, parser) 141 | } 142 | case protoreflect.Int64Kind: 143 | parser := func(s string) (int64, error) { 144 | return strconv.ParseInt(s, 10, 64) 145 | } 146 | if field.IsList() { 147 | value = protovalue.PrimitiveList[int64](mutable, field, protoreflect.ValueOfInt64, parser) 148 | } else { 149 | value = protovalue.Primitive[int64](mutable, field, protoreflect.ValueOfInt64, parser) 150 | } 151 | case protoreflect.Int32Kind: 152 | parser := func(s string) (int32, error) { 153 | i64, err := strconv.ParseInt(s, 10, 32) 154 | if err != nil { 155 | return 0, err 156 | } 157 | return int32(i64), nil 158 | } 159 | if field.IsList() { 160 | value = protovalue.PrimitiveList[int32](mutable, field, protoreflect.ValueOfInt32, parser) 161 | } else { 162 | value = protovalue.Primitive[int32](mutable, field, protoreflect.ValueOfInt32, parser) 163 | } 164 | default: 165 | panic(fmt.Errorf("unhandled primitive kind: %v", field.Kind())) // shouldn't happen 166 | } 167 | addFlag(cmd, field, parentFields, comments[field.FullName()], value) 168 | } 169 | 170 | func addFlag( 171 | cmd *cobra.Command, 172 | field protoreflect.FieldDescriptor, 173 | parentFields []protoreflect.FieldDescriptor, 174 | comment string, 175 | value pflag.Value, 176 | ) { 177 | flag := &pflag.Flag{ 178 | Name: flagName(field, parentFields), 179 | Usage: trimFieldComment(comment), 180 | Value: value, 181 | Annotations: map[string][]string{ 182 | fieldNameAnnotation: {string(field.FullName())}, 183 | }, 184 | } 185 | cmd.Flags().AddFlag(flag) 186 | _ = cmd.Flags().SetAnnotation(flag.Name, flagArgumentAnnotation, []string{}) 187 | annotateFlagWithFieldBehaviors(flag, field) 188 | markRequiredFlags(cmd, flag, field) 189 | hideOutputOnlyFields(cmd, flag, field) 190 | registerCompletion(cmd, flag, field, comment) 191 | hideImmutableForUpdateMethods(cmd, flag, field) 192 | hideNameForCreateMethods(cmd, flag, field) 193 | hideETagForCreateMethods(cmd, flag, field) 194 | } 195 | 196 | func markRequiredFlags( 197 | cmd *cobra.Command, 198 | flag *pflag.Flag, 199 | field protoreflect.FieldDescriptor, 200 | ) { 201 | if isMethodType(cmd, "Update") { 202 | // Update methods have no required fields due to field masks. 203 | return 204 | } 205 | if os.Getenv("AIP_CLI_DISABLE_FIELD_BEHAVIOR") == "true" { 206 | // A secret escape hatch for ignoring required fields. 207 | return 208 | } 209 | if fieldBehaviors, ok := proto.GetExtension( 210 | field.Options(), 211 | annotations.E_FieldBehavior, 212 | ).([]annotations.FieldBehavior); ok { 213 | for _, fieldBehavior := range fieldBehaviors { 214 | if fieldBehavior == annotations.FieldBehavior_REQUIRED { 215 | _ = cmd.MarkFlagRequired(flag.Name) 216 | return 217 | } 218 | } 219 | } 220 | } 221 | 222 | func hideOutputOnlyFields( 223 | cmd *cobra.Command, 224 | flag *pflag.Flag, 225 | field protoreflect.FieldDescriptor, 226 | ) { 227 | if isMethodType(cmd, "Update") && field.Name() == FieldResourceName { 228 | // Update methods need to be able to specify the resource name to update 229 | return 230 | } 231 | if fieldBehaviors, ok := proto.GetExtension( 232 | field.Options(), 233 | annotations.E_FieldBehavior, 234 | ).([]annotations.FieldBehavior); ok { 235 | for _, fieldBehavior := range fieldBehaviors { 236 | if fieldBehavior == annotations.FieldBehavior_OUTPUT_ONLY { 237 | _ = cmd.Flags().MarkHidden(flag.Name) 238 | return 239 | } 240 | } 241 | } 242 | } 243 | 244 | func annotateFlagWithFieldBehaviors( 245 | flag *pflag.Flag, 246 | field protoreflect.FieldDescriptor, 247 | ) { 248 | if fieldBehaviors, ok := proto.GetExtension( 249 | field.Options(), 250 | annotations.E_FieldBehavior, 251 | ).([]annotations.FieldBehavior); ok { 252 | for _, fieldBehavior := range fieldBehaviors { 253 | if flag.Annotations == nil { 254 | flag.Annotations = map[string][]string{} 255 | } 256 | flag.Annotations[fieldBehaviorsAnnotation] = append( 257 | flag.Annotations[fieldBehaviorsAnnotation], 258 | fieldBehavior.String(), 259 | ) 260 | } 261 | } 262 | } 263 | 264 | func registerCompletion( 265 | cmd *cobra.Command, 266 | flag *pflag.Flag, 267 | field protoreflect.FieldDescriptor, 268 | comment string, 269 | ) { 270 | // resource name fields 271 | if !field.IsList() && field.Name() == FieldResourceName && field.Kind() == protoreflect.StringKind { 272 | if resourceDescriptor, ok := proto.GetExtension( 273 | field.Parent().Options(), 274 | annotations.E_Resource, 275 | ).(*annotations.ResourceDescriptor); ok && resourceDescriptor.GetType() != "" { 276 | var didRegisterCompletion bool 277 | aipreflect.RangeResourceDescriptorsInPackage( 278 | protoregistry.GlobalFiles, 279 | field.ParentFile().Package(), 280 | func(resource *annotations.ResourceDescriptor) bool { 281 | if resource.GetType() == resourceDescriptor.GetType() && len(resource.GetPattern()) > 0 { 282 | didRegisterCompletion = true 283 | _ = cmd.RegisterFlagCompletionFunc( 284 | flag.Name, 285 | resourceNameCompletionFunc(comment, resource.GetPattern()...), 286 | ) 287 | return false 288 | } 289 | return true 290 | }, 291 | ) 292 | if didRegisterCompletion { 293 | return 294 | } 295 | } 296 | } 297 | // resource reference fields 298 | if field.Kind() == protoreflect.StringKind { 299 | if resourceReference, ok := proto.GetExtension( 300 | field.Options(), 301 | annotations.E_ResourceReference, 302 | ).(*annotations.ResourceReference); ok && resourceReference.GetType() != "" { 303 | var didRegisterCompletion bool 304 | completionFunc := resourceNameCompletionFunc 305 | if field.IsList() { 306 | completionFunc = resourceNameListCompletionFunc 307 | } 308 | aipreflect.RangeResourceDescriptorsInPackage( 309 | protoregistry.GlobalFiles, 310 | field.ParentFile().Package(), 311 | func(resource *annotations.ResourceDescriptor) bool { 312 | if resource.GetType() == resourceReference.GetType() && len(resource.GetPattern()) > 0 { 313 | _ = cmd.RegisterFlagCompletionFunc( 314 | flag.Name, 315 | completionFunc(comment, resource.GetPattern()...), 316 | ) 317 | didRegisterCompletion = true 318 | return false 319 | } 320 | return true 321 | }, 322 | ) 323 | if didRegisterCompletion { 324 | return 325 | } 326 | } 327 | } 328 | if field.Kind() == protoreflect.MessageKind && field.Message().FullName() == "google.protobuf.Timestamp" { 329 | _ = cmd.RegisterFlagCompletionFunc(flag.Name, timestampCompletionFunc(comment)) 330 | return 331 | } 332 | if field.Kind() == protoreflect.EnumKind && !field.IsList() { 333 | _ = cmd.RegisterFlagCompletionFunc(flag.Name, enumFieldCompletionFunc(comment, field.Enum().Values())) 334 | return 335 | } 336 | // default: register active help with field comment 337 | _ = cmd.RegisterFlagCompletionFunc(flag.Name, fieldCompletionFunc(comment)) 338 | } 339 | 340 | func hideImmutableForUpdateMethods( 341 | cmd *cobra.Command, 342 | flag *pflag.Flag, 343 | field protoreflect.FieldDescriptor, 344 | ) { 345 | if !isMethodType(cmd, "Update") { 346 | return 347 | } 348 | if fieldBehaviors, ok := proto.GetExtension( 349 | field.Options(), 350 | annotations.E_FieldBehavior, 351 | ).([]annotations.FieldBehavior); ok { 352 | for _, fieldBehavior := range fieldBehaviors { 353 | if fieldBehavior == annotations.FieldBehavior_IMMUTABLE { 354 | _ = cmd.Flags().MarkHidden(flag.Name) 355 | } 356 | } 357 | } 358 | } 359 | 360 | func hideNameForCreateMethods(cmd *cobra.Command, flag *pflag.Flag, field protoreflect.FieldDescriptor) { 361 | if isMethodType(cmd, "Create") && field.Name() == FieldResourceName { 362 | _ = cmd.Flags().MarkHidden(flag.Name) 363 | } 364 | } 365 | 366 | func hideETagForCreateMethods(cmd *cobra.Command, flag *pflag.Flag, field protoreflect.FieldDescriptor) { 367 | if isMethodType(cmd, "Create") && field.Name() == "etag" { 368 | _ = cmd.Flags().MarkHidden(flag.Name) 369 | } 370 | } 371 | 372 | func flagName(field protoreflect.FieldDescriptor, parentFields []protoreflect.FieldDescriptor) string { 373 | var result strings.Builder 374 | for _, parentField := range parentFields { 375 | _, _ = result.WriteString(string(parentField.Name())) 376 | _ = result.WriteByte('.') 377 | } 378 | _, _ = result.WriteString(string(field.Name())) 379 | return strings.ReplaceAll(result.String(), "_", "-") 380 | } 381 | 382 | func isMethodType(cmd *cobra.Command, methodType string) bool { 383 | return strings.HasPrefix(string(protoreflect.FullName(cmd.Annotations[methodAnnotation]).Name()), methodType) 384 | } 385 | -------------------------------------------------------------------------------- /aipcli/help.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "strings" 5 | "text/tabwriter" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/pflag" 9 | ) 10 | 11 | func helpFunc(cmd *cobra.Command, _ []string) { 12 | _ = usageFunc(cmd) 13 | } 14 | 15 | func usageFunc(cmd *cobra.Command) error { 16 | out := cmd.ErrOrStderr() 17 | defer cmd.SetErr(out) 18 | tw := tabwriter.NewWriter(out, 2, 0, 2, ' ', 0) 19 | cmd.SetErr(tw) 20 | if cmd.Short != "" { 21 | cmd.PrintErrln() 22 | if cmd.Long != "" { 23 | cmd.PrintErrln(cmd.Long) 24 | } else { 25 | cmd.PrintErrln(cmd.Short) 26 | } 27 | } 28 | cmd.PrintErrln() 29 | cmd.PrintErrln("USAGE") 30 | cmd.PrintErrln(" ", cmd.Use, "") 31 | if methods := getCommands(cmd, commandHasAnnotation(methodAnnotation)); len(methods) > 0 { 32 | cmd.PrintErrln() 33 | cmd.PrintErrln("METHOD COMMANDS") 34 | printCommands(cmd, methods) 35 | } 36 | if services := getCommands(cmd, commandHasAnnotation(serviceAnnotation)); len(services) > 0 { 37 | cmd.PrintErrln() 38 | cmd.PrintErrln("SERVICE COMMANDS") 39 | printCommands(cmd, services) 40 | } 41 | if modules := getCommands(cmd, commandHasAnnotation(moduleAnnotation)); len(modules) > 0 { 42 | cmd.PrintErrln() 43 | cmd.PrintErrln("MODULE COMMANDS") 44 | printCommands(cmd, modules) 45 | } 46 | if otherCommands := getCommands(cmd, func(cmd *cobra.Command) bool { 47 | _, isModule := cmd.Annotations[moduleAnnotation] 48 | _, isService := cmd.Annotations[serviceAnnotation] 49 | _, isMethod := cmd.Annotations[methodAnnotation] 50 | return !isModule && !isService && !isMethod 51 | }); len(otherCommands) > 0 { 52 | cmd.PrintErrln() 53 | cmd.PrintErrln("OTHER COMMANDS") 54 | printCommands(cmd, otherCommands) 55 | } 56 | if argumentFlags := getFlags(cmd, isArgumentFlag); len(argumentFlags) > 0 { 57 | cmd.PrintErrln() 58 | cmd.PrintErrln("ARGUMENT FLAGS") 59 | printFlags(cmd, argumentFlags) 60 | } 61 | if hostFlags := getFlags(cmd, isHostFlag); len(hostFlags) > 0 { 62 | cmd.PrintErrln() 63 | cmd.PrintErrln("HOST FLAGS") 64 | printFlags(cmd, hostFlags) 65 | } 66 | if connectionFlags := getFlags(cmd, isConnectionFlag); len(connectionFlags) > 0 { 67 | cmd.PrintErrln() 68 | cmd.PrintErrln("CONNECTION FLAGS") 69 | printFlags(cmd, connectionFlags) 70 | } 71 | if otherFlags := getFlags(cmd, func(_ *cobra.Command, flag *pflag.Flag) bool { 72 | return !isConnectionFlag(cmd, flag) && !isArgumentFlag(cmd, flag) && !isHostFlag(cmd, flag) 73 | }); len(otherFlags) > 0 { 74 | cmd.PrintErrln() 75 | cmd.PrintErrln("OTHER FLAGS") 76 | printFlags(cmd, otherFlags) 77 | } 78 | return tw.Flush() 79 | } 80 | 81 | func printCommands(cmd *cobra.Command, commands []*cobra.Command) { 82 | for _, command := range commands { 83 | cmd.PrintErrln(" " + command.Name() + "\t" + command.Short) 84 | } 85 | } 86 | 87 | func printFlags(cmd *cobra.Command, flags []*pflag.Flag) { 88 | var hasShorthand bool 89 | for _, flag := range flags { 90 | if flag.Shorthand != "" { 91 | hasShorthand = true 92 | break 93 | } 94 | } 95 | var maxFieldBehaviors int 96 | for _, flag := range flags { 97 | if curr := len(flag.Annotations[fieldBehaviorsAnnotation]); curr > maxFieldBehaviors { 98 | maxFieldBehaviors = curr 99 | } 100 | } 101 | for _, flag := range flags { 102 | if flag.Hidden { 103 | continue 104 | } 105 | var line strings.Builder 106 | _, _ = line.WriteString(" ") 107 | if hasShorthand { 108 | if flag.Shorthand == "" { 109 | _, _ = line.WriteString(" ") 110 | } else { 111 | _, _ = line.WriteString("-" + flag.Shorthand) 112 | } 113 | _, _ = line.WriteString(" ") 114 | } 115 | _, _ = line.WriteString("--" + flag.Name) 116 | _, _ = line.WriteString("\t") 117 | if fieldBehaviors := flag.Annotations[fieldBehaviorsAnnotation]; len(fieldBehaviors) > 0 { 118 | line.WriteString("[") 119 | for _, fieldBehavior := range fieldBehaviors { 120 | line.WriteString(fieldBehavior[:1]) 121 | } 122 | line.WriteString("]") 123 | } 124 | _, _ = line.WriteString("\t") 125 | _, _ = line.WriteString(flag.Value.Type()) 126 | _, _ = line.WriteString("\t") 127 | _, _ = line.WriteString(flag.Usage) 128 | if flag.DefValue != "" && flag.DefValue != "false" { 129 | _, _ = line.WriteString(" (" + flag.DefValue + ")") 130 | } 131 | cmd.PrintErrln(line.String()) 132 | } 133 | } 134 | 135 | func isConnectionFlag(_ *cobra.Command, flag *pflag.Flag) bool { 136 | switch flag.Name { 137 | case addressFlag, insecureFlag, tokenFlag, forceTraceFlag: 138 | return true 139 | } 140 | return false 141 | } 142 | 143 | func isHostFlag(cmd *cobra.Command, flag *pflag.Flag) bool { 144 | for host := range GetConfig(cmd).Hosts { 145 | if flag.Name == host { 146 | return true 147 | } 148 | } 149 | return false 150 | } 151 | 152 | func isArgumentFlag(_ *cobra.Command, flag *pflag.Flag) bool { 153 | _, ok := flag.Annotations[flagArgumentAnnotation] 154 | return ok 155 | } 156 | 157 | func commandHasAnnotation(annotation string) func(command *cobra.Command) bool { 158 | return func(command *cobra.Command) bool { 159 | _, ok := command.Annotations[annotation] 160 | return ok 161 | } 162 | } 163 | 164 | func getCommands(cmd *cobra.Command, fn func(*cobra.Command) bool) []*cobra.Command { 165 | cmds := cmd.Commands() 166 | result := make([]*cobra.Command, 0, len(cmds)) 167 | for _, subCmd := range cmds { 168 | if fn(subCmd) { 169 | result = append(result, subCmd) 170 | } 171 | } 172 | return result 173 | } 174 | 175 | func getFlags(cmd *cobra.Command, fn func(*cobra.Command, *pflag.Flag) bool) []*pflag.Flag { 176 | flags := cmd.Flags() 177 | flags.SortFlags = false 178 | result := make([]*pflag.Flag, 0, flags.NFlag()) 179 | flags.VisitAll(func(flag *pflag.Flag) { 180 | if fn(cmd, flag) { 181 | result = append(result, flag) 182 | } 183 | }) 184 | return result 185 | } 186 | -------------------------------------------------------------------------------- /aipcli/iam.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "cloud.google.com/go/iam/apiv1/iampb" 8 | "github.com/spf13/cobra" 9 | "google.golang.org/protobuf/encoding/protojson" 10 | ) 11 | 12 | // NewIAMModuleCommand returns a *cobra.Command for standard IAM operations. 13 | func NewIAMModuleCommand(name string, config Config) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: name, 16 | Short: "Identity and Access Management (IAM)", 17 | Long: strings.TrimSpace(` 18 | Manage Identity and Access Management (IAM) policies. 19 | 20 | Access control is applied when a principal (user or service account), takes 21 | some action on a resource exposed by a service. Resources, identified by 22 | URI-like names, are the unit of access control specification. Service 23 | implementations can choose the granularity of access control and the 24 | supported permissions for their resources. 25 | 26 | For example one database service may allow access control to be 27 | specified only at the Table level, whereas another might allow access control 28 | to also be specified at the Column level. 29 | 30 | This is intentionally not a CRUD style API because access control policies 31 | are created and deleted implicitly with the resources to which they are 32 | attached. 33 | `), 34 | Annotations: map[string]string{ 35 | moduleAnnotation: name, 36 | }, 37 | } 38 | initPersistentFlags(cmd) 39 | setConfig(cmd, config) 40 | cmd.AddCommand(newSetIAMPolicyCommand(config)) 41 | cmd.AddCommand(newGetIAMPolicyCommand(config)) 42 | cmd.AddCommand(newAddIAMPolicyBindingCommand(config)) 43 | cmd.AddCommand(newRemoveIAMPolicyBindingCommand(config)) 44 | return cmd 45 | } 46 | 47 | func newGetIAMPolicyCommand(config Config) *cobra.Command { 48 | cmd := &cobra.Command{ 49 | Use: "get-iam-policy", 50 | Short: "Get the IAM policy for a resource", 51 | Long: strings.TrimSpace(` 52 | Get the IAM policy associated with a resource. 53 | 54 | The output includes an "etag" identifier that is used to check for 55 | concurrent policy updates. An edited policy should include the same 56 | etag so that set-iam-policy applies the changes to the correct policy 57 | version. 58 | 59 | This command can fail for the following reasons: 60 | 61 | ▪ The resource specified does not exist. 62 | ▪ The active account does not have permission to access the given 63 | resource's IAM policies. 64 | `), 65 | PersistentPreRun: func(cmd *cobra.Command, _ []string) { 66 | initContext(cmd, config) 67 | }, 68 | Annotations: map[string]string{ 69 | methodAnnotation: "google.iam.v1.IAMPolicy.GetIamPolicy", 70 | }, 71 | } 72 | initPersistentFlags(cmd) 73 | setConfig(cmd, config) 74 | resource := cmd.Flags().String("resource", "", "Resource for which the policy is being requested") 75 | _ = cmd.MarkFlagRequired("resource") 76 | _ = cmd.RegisterFlagCompletionFunc("resource", completeResource) 77 | cmd.RunE = func(cmd *cobra.Command, _ []string) error { 78 | return invoke( 79 | cmd, 80 | "/google.iam.v1.IAMPolicy/GetIamPolicy", 81 | &iampb.GetIamPolicyRequest{Resource: *resource}, 82 | &iampb.Policy{}, 83 | ) 84 | } 85 | return cmd 86 | } 87 | 88 | func newSetIAMPolicyCommand(config Config) *cobra.Command { 89 | cmd := &cobra.Command{ 90 | Use: "set-iam-policy", 91 | Short: "Set the IAM policy for a resource", 92 | Long: strings.TrimSpace(` 93 | Set the IAM policy associated with a resource. 94 | 95 | This command can fail for the following reasons: 96 | 97 | ▪ The resource specified does not exist. 98 | 99 | ▪ The specified policy contains invalid or non-existent 100 | roles or members. 101 | 102 | ▪ The active account does not have permission to 103 | access the given resource's IAM policies. 104 | `), 105 | PersistentPreRun: func(cmd *cobra.Command, _ []string) { 106 | initContext(cmd, config) 107 | }, 108 | Annotations: map[string]string{ 109 | methodAnnotation: "google.iam.v1.IAMPolicy.SetIAMPolicy", 110 | }, 111 | } 112 | initPersistentFlags(cmd) 113 | setConfig(cmd, config) 114 | resource := cmd.Flags().String("resource", "", "Resource name for which the policy is being specified") 115 | _ = cmd.MarkFlagRequired("resource") 116 | _ = cmd.RegisterFlagCompletionFunc("resource", completeResource) 117 | _ = cmd.Flags().SetAnnotation("resource", flagArgumentAnnotation, []string{}) 118 | policyFile := cmd.Flags().String("policy-file", "", "Path to a local JSON file containing a valid policy") 119 | _ = cmd.MarkFlagRequired("policy-file") 120 | _ = cmd.MarkFlagFilename("policy-file", "json") 121 | _ = cmd.Flags().SetAnnotation("policy-file", flagArgumentAnnotation, []string{}) 122 | cmd.RunE = func(cmd *cobra.Command, _ []string) error { 123 | data, err := os.ReadFile(*policyFile) 124 | if err != nil { 125 | return err 126 | } 127 | var policy iampb.Policy 128 | if err := protojson.Unmarshal(data, &policy); err != nil { 129 | return err 130 | } 131 | return invoke( 132 | cmd, 133 | "/google.iam.v1.IAMPolicy/SetIamPolicy", 134 | &iampb.SetIamPolicyRequest{Resource: *resource, Policy: &policy}, 135 | &iampb.Policy{}, 136 | ) 137 | } 138 | return cmd 139 | } 140 | 141 | func newAddIAMPolicyBindingCommand(config Config) *cobra.Command { 142 | cmd := &cobra.Command{ 143 | Use: "add-iam-policy-binding", 144 | Short: "Add an IAM policy binding to the IAM policy of a resource", 145 | Long: strings.TrimSpace(` 146 | Adds an IAM policy binding to the IAM policy of a resource. 147 | 148 | One binding consists of a member, a role, and an optional condition. 149 | 150 | This command can fail for the following reasons: 151 | 152 | ▪ The resource specified does not exist. 153 | ▪ The active account does not have permission to access the given 154 | resource's IAM policies. 155 | `), 156 | PersistentPreRun: func(cmd *cobra.Command, _ []string) { 157 | initContext(cmd, config) 158 | }, 159 | Annotations: map[string]string{ 160 | methodAnnotation: "add-iam-policy-binding", // custom method 161 | }, 162 | } 163 | initPersistentFlags(cmd) 164 | setConfig(cmd, config) 165 | resource := cmd.Flags().String("resource", "", "Resource name for which the policy binding is being added") 166 | _ = cmd.MarkFlagRequired("resource") 167 | _ = cmd.RegisterFlagCompletionFunc("resource", completeResource) 168 | _ = cmd.Flags().SetAnnotation("resource", flagArgumentAnnotation, nil) 169 | member := cmd.Flags().String("member", "", "Principal to add the binding for") 170 | _ = cmd.MarkFlagRequired("member") 171 | _ = cmd.RegisterFlagCompletionFunc("member", completeMember) 172 | _ = cmd.Flags().SetAnnotation("member", flagArgumentAnnotation, nil) 173 | role := cmd.Flags().String("role", "", "Role name to assign to the principal") 174 | _ = cmd.MarkFlagRequired("role") 175 | _ = cmd.RegisterFlagCompletionFunc("role", completeRole) 176 | _ = cmd.Flags().SetAnnotation("role", flagArgumentAnnotation, nil) 177 | cmd.RunE = func(cmd *cobra.Command, _ []string) error { 178 | var policy iampb.Policy 179 | if err := invoke( 180 | cmd, 181 | "/google.iam.v1.IAMPolicy/GetIamPolicy", 182 | &iampb.GetIamPolicyRequest{Resource: *resource}, 183 | &policy, 184 | ); err != nil { 185 | return err 186 | } 187 | addBinding(&policy, *member, *role) 188 | return invoke( 189 | cmd, 190 | "/google.iam.v1.IAMPolicy/SetIamPolicy", 191 | &iampb.SetIamPolicyRequest{Resource: *resource, Policy: &policy}, 192 | &iampb.Policy{}, 193 | ) 194 | } 195 | return cmd 196 | } 197 | 198 | func newRemoveIAMPolicyBindingCommand(config Config) *cobra.Command { 199 | cmd := &cobra.Command{ 200 | Use: "remove-iam-policy-binding", 201 | Short: "Remove an IAM policy binding from the IAM policy of a resource", 202 | Long: strings.TrimSpace(` 203 | Removes an IAM policy binding from the IAM policy of a resource. 204 | One binding consists of a member, a role, and an optional condition. 205 | 206 | This command can fail for the following reasons: 207 | 208 | ▪ The resource specified does not exist. 209 | ▪ The active account does not have permission to access the given 210 | resource's IAM policies. 211 | `), 212 | PersistentPreRun: func(cmd *cobra.Command, _ []string) { 213 | initContext(cmd, config) 214 | }, 215 | Annotations: map[string]string{ 216 | methodAnnotation: "remove-iam-policy-binding", // custom method 217 | }, 218 | } 219 | initPersistentFlags(cmd) 220 | setConfig(cmd, config) 221 | resource := cmd.Flags().String("resource", "", "Resource name for which the policy binding is being removed") 222 | _ = cmd.MarkFlagRequired("resource") 223 | _ = cmd.RegisterFlagCompletionFunc("resource", completeResource) 224 | _ = cmd.Flags().SetAnnotation("resource", flagArgumentAnnotation, nil) 225 | member := cmd.Flags().String("member", "", "Principal to remove the binding for") 226 | _ = cmd.MarkFlagRequired("member") 227 | _ = cmd.RegisterFlagCompletionFunc("member", completeMember) 228 | _ = cmd.Flags().SetAnnotation("member", flagArgumentAnnotation, nil) 229 | role := cmd.Flags().String("role", "", "Role name to remove the principal from") 230 | _ = cmd.MarkFlagRequired("role") 231 | _ = cmd.RegisterFlagCompletionFunc("role", completeRole) 232 | _ = cmd.Flags().SetAnnotation("role", flagArgumentAnnotation, nil) 233 | cmd.RunE = func(cmd *cobra.Command, _ []string) error { 234 | var policy iampb.Policy 235 | if err := invoke( 236 | cmd, 237 | "/google.iam.v1.IAMPolicy/GetIamPolicy", 238 | &iampb.GetIamPolicyRequest{Resource: *resource}, 239 | &policy, 240 | ); err != nil { 241 | return err 242 | } 243 | removeBinding(&policy, *member, *role) 244 | return invoke( 245 | cmd, 246 | "/google.iam.v1.IAMPolicy/SetIamPolicy", 247 | &iampb.SetIamPolicyRequest{Resource: *resource, Policy: &policy}, 248 | &iampb.Policy{}, 249 | ) 250 | } 251 | return cmd 252 | } 253 | 254 | func completeResource(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { 255 | return cobra.AppendActiveHelp( 256 | nil, 257 | "Please enter a valid resource name. Example: resources/1234", 258 | ), cobra.ShellCompDirectiveNoFileComp 259 | } 260 | 261 | func completeMember(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { 262 | return cobra.AppendActiveHelp( 263 | nil, 264 | "Please enter a valid member. Example: email:foo@example.com", 265 | ), cobra.ShellCompDirectiveNoFileComp 266 | } 267 | 268 | func completeRole(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { 269 | // TODO: Use the QueryGrantableRoles method to auto-complete grantable roles. 270 | return cobra.AppendActiveHelp( 271 | nil, 272 | "Please enter a valid role name. Example: roles/example.admin", 273 | ), cobra.ShellCompDirectiveNoFileComp 274 | } 275 | 276 | func addBinding(policy *iampb.Policy, member, role string) { 277 | // look for existing binding with this role and member 278 | for _, binding := range policy.GetBindings() { 279 | if binding.GetRole() == role { 280 | for _, bindingMember := range binding.GetMembers() { 281 | if bindingMember == member { 282 | // already have a binding with this role and member 283 | return 284 | } 285 | } 286 | // already have a binding with this role, but not the member 287 | binding.Members = append(binding.Members, member) 288 | return 289 | } 290 | } 291 | // add a new binding with this role and member 292 | policy.Bindings = append(policy.Bindings, &iampb.Binding{ 293 | Role: role, 294 | Members: []string{member}, 295 | }) 296 | } 297 | 298 | func removeBinding(policy *iampb.Policy, member, role string) { 299 | for _, binding := range policy.GetBindings() { 300 | if binding.GetRole() == role { 301 | binding.Members = removeMember(binding.GetMembers(), member) 302 | if len(binding.GetMembers()) == 0 { 303 | policy.Bindings = removeRole(policy.GetBindings(), role) 304 | } 305 | return 306 | } 307 | } 308 | } 309 | 310 | func removeMember(members []string, member string) []string { 311 | for i, candidate := range members { 312 | if candidate == member { 313 | return append(members[:i], members[i+1:]...) 314 | } 315 | } 316 | return members 317 | } 318 | 319 | func removeRole(bindings []*iampb.Binding, role string) []*iampb.Binding { 320 | for i, binding := range bindings { 321 | if binding.GetRole() == role { 322 | return append(bindings[:i], bindings[i+1:]...) 323 | } 324 | } 325 | return bindings 326 | } 327 | -------------------------------------------------------------------------------- /aipcli/init.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | //nolint:gochecknoinits // need to initialize Cobra's global state 6 | func init() { 7 | cobra.EnableCommandSorting = false 8 | } 9 | -------------------------------------------------------------------------------- /aipcli/invoke.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/cobra" 7 | "google.golang.org/grpc/status" 8 | "google.golang.org/protobuf/encoding/protojson" 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | func invoke(cmd *cobra.Command, uri string, request, response proto.Message) error { 13 | conn, err := dial(cmd) 14 | if err != nil { 15 | return err 16 | } 17 | if IsVerbose(cmd) { 18 | for _, line := range strings.Split(format(request), "\n") { 19 | cmd.PrintErrln(">>", line) 20 | } 21 | } 22 | if err := conn.Invoke(cmd.Context(), uri, request, response); err != nil { 23 | for _, detail := range status.Convert(err).Details() { 24 | cmd.PrintErrln(detail) 25 | } 26 | return err // let Cobra print the error when exiting 27 | } 28 | cmd.Println(format(response)) 29 | return nil 30 | } 31 | 32 | func format(msg proto.Message) string { 33 | return protojson.MarshalOptions{ 34 | Multiline: true, 35 | Indent: " ", 36 | EmitUnpopulated: true, 37 | }.Format(msg) 38 | } 39 | -------------------------------------------------------------------------------- /aipcli/token.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "os/exec" 7 | "path" 8 | "strings" 9 | ) 10 | 11 | func gcloudAuthPrintIdentityToken() (string, bool) { 12 | if _, err := exec.LookPath("gcloud"); err != nil { 13 | return "", false 14 | } 15 | var stdout strings.Builder 16 | cmd := exec.Command("gcloud", "auth", "print-identity-token") 17 | cmd.Stdout = &stdout 18 | if err := cmd.Run(); err != nil { 19 | return "", false 20 | } 21 | return strings.TrimSpace(stdout.String()), true 22 | } 23 | 24 | type IdentityTokenFile struct { 25 | IdentityToken string 26 | } 27 | 28 | func identityTokenFromConfigFile(tokenFile string) (string, error) { 29 | configDir, err := os.UserConfigDir() 30 | if err != nil { 31 | return "", err 32 | } 33 | configFile := path.Join(configDir, tokenFile) 34 | file, err := os.ReadFile(configFile) 35 | if err != nil { 36 | return "", err 37 | } 38 | var identityFile IdentityTokenFile 39 | err = json.Unmarshal(file, &identityFile) 40 | if err != nil { 41 | return "", err 42 | } 43 | return identityFile.IdentityToken, nil 44 | } 45 | -------------------------------------------------------------------------------- /aipcli/use.go: -------------------------------------------------------------------------------- 1 | package aipcli 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "sort" 7 | "strings" 8 | "unicode" 9 | "unicode/utf8" 10 | 11 | "github.com/spf13/cobra" 12 | "github.com/stoewer/go-strcase" 13 | "go.einride.tech/aip-cli/internal/protoversion" 14 | "google.golang.org/protobuf/reflect/protoreflect" 15 | ) 16 | 17 | func compareFullNames(lhs, rhs string) int { 18 | lhsParts, rhsParts := reverse(strings.Split(lhs, ".")), reverse(strings.Split(rhs, ".")) 19 | stop := len(lhsParts) 20 | if len(rhsParts) < stop { 21 | stop = len(rhsParts) 22 | } 23 | for i := 0; i < stop; i++ { 24 | if lhsVersion, err := protoversion.Parse(lhsParts[i]); err == nil { 25 | if rhsVersion, err := protoversion.Parse(rhsParts[i]); err == nil { 26 | return protoversion.Compare(lhsVersion, rhsVersion) 27 | } 28 | } 29 | switch { 30 | case lhsParts[i] < rhsParts[i]: 31 | return -1 32 | case lhsParts[i] > rhsParts[i]: 33 | return 1 34 | } 35 | } 36 | switch { 37 | case len(lhsParts) < len(rhsParts): 38 | return -1 39 | case len(rhsParts) < len(lhsParts): 40 | return 1 41 | default: 42 | return 0 43 | } 44 | } 45 | 46 | func deduplicateServiceCommandUses(cmds []*cobra.Command) { 47 | sorted := make([]*cobra.Command, 0, len(cmds)) 48 | sorted = append(sorted, cmds...) 49 | sort.Slice(sorted, func(i, j int) bool { 50 | // sort descending to get shorter use for higher version 51 | return compareFullNames( 52 | sorted[i].Annotations[serviceAnnotation], 53 | sorted[j].Annotations[serviceAnnotation], 54 | ) > 0 55 | }) 56 | uses := make(map[string]struct{}, len(cmds)) 57 | for _, cmd := range sorted { 58 | serviceName := protoreflect.FullName(cmd.Annotations[serviceAnnotation]) 59 | if serviceName == "" { 60 | uses[cmd.Use] = struct{}{} 61 | continue 62 | } 63 | var i int 64 | for { 65 | use, ok := qualifiedServiceUse(serviceName, i) 66 | if !ok { 67 | break 68 | } 69 | _, isUsed := uses[use] 70 | if !isUsed { 71 | uses[use] = struct{}{} 72 | cmd.Use = use 73 | break 74 | } 75 | i++ 76 | } 77 | } 78 | } 79 | 80 | func serviceUse(service protoreflect.FullName) string { 81 | result := string(service.Name()) 82 | result = strings.TrimSuffix(result, "Service") 83 | result = strcase.KebabCase(result) 84 | return result 85 | } 86 | 87 | func qualifiedServiceUse(service protoreflect.FullName, n int) (string, bool) { 88 | trim := service.Parent() 89 | for i := 0; i < n; i++ { 90 | if trim == "" { 91 | return "", false 92 | } 93 | trim = trim.Parent() 94 | } 95 | result := strings.TrimPrefix(string(service), string(trim)+".") 96 | result = strings.TrimSuffix(result, "Service") 97 | result = strings.Join(reverse(strings.Split(result, ".")), "-") 98 | result = strcase.KebabCase(result) 99 | return result, true 100 | } 101 | 102 | func reverse(ss []string) []string { 103 | last := len(ss) - 1 104 | for i := 0; i < len(ss)/2; i++ { 105 | ss[i], ss[last-i] = ss[last-i], ss[i] 106 | } 107 | return ss 108 | } 109 | 110 | func methodUse(method protoreflect.MethodDescriptor) string { 111 | result := string(method.Name()) 112 | result = strcase.KebabCase(result) 113 | return result 114 | } 115 | 116 | func trimLongComment(s string) string { 117 | var result strings.Builder 118 | sc := bufio.NewScanner(strings.NewReader(s)) 119 | sc.Split(bufio.ScanLines) 120 | for sc.Scan() { 121 | _, _ = result.Write(bytes.TrimSpace(sc.Bytes())) 122 | _ = result.WriteByte('\n') 123 | } 124 | return strings.TrimSpace(result.String()) 125 | } 126 | 127 | func trimFieldComment(comment string) string { 128 | result := comment 129 | // Clean up comment line breaks and whitespace. 130 | result = strings.ReplaceAll(result, "//", "") 131 | result = strings.ReplaceAll(result, "\n", " ") 132 | result = strings.TrimSpace(result) 133 | result = strings.ReplaceAll(result, " ", " ") 134 | result = strings.ReplaceAll(result, " ", " ") 135 | // Cut out first sentence. 136 | if i := strings.IndexByte(result, '.'); i != -1 { 137 | result = result[:i] 138 | } 139 | // Trim manually documented field behavior. 140 | result = strings.TrimPrefix(result, "REQUIRED: ") 141 | result = strings.TrimPrefix(result, "Required: ") 142 | result = strings.TrimPrefix(result, "The") 143 | result = strings.TrimPrefix(result, "the") 144 | result = strings.TrimSpace(result) 145 | result = initialUpperCase(result) 146 | return result 147 | } 148 | 149 | func initialUpperCase(s string) string { 150 | r, size := utf8.DecodeRuneInString(s) 151 | if size == utf8.RuneError { 152 | return s 153 | } 154 | return string(unicode.ToUpper(r)) + s[size:] 155 | } 156 | -------------------------------------------------------------------------------- /cmd/examplectl/gen/einride/example/freight/v1/freight_service_cli.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-aip-cli. DO NOT EDIT. 2 | package freightv1 3 | 4 | import ( 5 | cobra "github.com/spf13/cobra" 6 | aipcli "go.einride.tech/aip-cli/aipcli" 7 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 8 | ) 9 | 10 | func NewFreightServiceCommand(config aipcli.Config) *cobra.Command { 11 | return aipcli.NewServiceCommand( 12 | config, 13 | File_einride_example_freight_v1_freight_service_proto. 14 | Services().ByName("FreightService"), 15 | map[protoreflect.FullName]string{ 16 | "einride.example.freight.v1.FreightService": " This API represents a simple freight service.\n\n It defines the following resource model:\n\n - The API has a collection of Shipper resources.\n\n - Each Shipper has a collection of Site resources.\n\n - Each Shipper has a collection of Shipment resources.\n", 17 | }, 18 | aipcli.NewMethodCommand( 19 | config, 20 | File_einride_example_freight_v1_freight_service_proto. 21 | Services().ByName("FreightService").Methods().ByName("GetShipper"), 22 | &GetShipperRequest{}, 23 | &Shipper{}, 24 | map[protoreflect.FullName]string{ 25 | "einride.example.freight.v1.FreightService.GetShipper": " Get a shipper.\n\n See: https://google.aip.dev/131 (Standard methods: Get).\n", 26 | "einride.example.freight.v1.GetShipperRequest.name": " The resource name of the shipper to retrieve.\n Format: shippers/{shipper}\n", 27 | }, 28 | ), 29 | aipcli.NewMethodCommand( 30 | config, 31 | File_einride_example_freight_v1_freight_service_proto. 32 | Services().ByName("FreightService").Methods().ByName("ListShippers"), 33 | &ListShippersRequest{}, 34 | &ListShippersResponse{}, 35 | map[protoreflect.FullName]string{ 36 | "einride.example.freight.v1.FreightService.ListShippers": " List shippers.\n\n See: https://google.aip.dev/132 (Standard methods: List).\n", 37 | "einride.example.freight.v1.ListShippersRequest.page_size": " Requested page size. Server may return fewer shippers than requested.\n If unspecified, server will pick an appropriate default.\n", 38 | "einride.example.freight.v1.ListShippersRequest.page_token": " A token identifying a page of results the server should return.\n Typically, this is the value of\n [ListShippersResponse.next_page_token][einride.example.freight.v1.ListShippersResponse.next_page_token]\n returned from the previous call to `ListShippers` method.\n", 39 | }, 40 | ), 41 | aipcli.NewMethodCommand( 42 | config, 43 | File_einride_example_freight_v1_freight_service_proto. 44 | Services().ByName("FreightService").Methods().ByName("CreateShipper"), 45 | &CreateShipperRequest{}, 46 | &Shipper{}, 47 | map[protoreflect.FullName]string{ 48 | "einride.example.freight.v1.CreateShipperRequest.shipper": " The shipper to create.\n", 49 | "einride.example.freight.v1.FreightService.CreateShipper": " Create a shipper.\n\n See: https://google.aip.dev/133 (Standard methods: Create).\n", 50 | "einride.example.freight.v1.Shipper.create_time": " The creation timestamp of the shipper.\n", 51 | "einride.example.freight.v1.Shipper.delete_time": " The deletion timestamp of the shipper.\n", 52 | "einride.example.freight.v1.Shipper.display_name": " The display name of the shipper.\n", 53 | "einride.example.freight.v1.Shipper.name": " The resource name of the shipper.\n", 54 | "einride.example.freight.v1.Shipper.update_time": " The last update timestamp of the shipper.\n\n Updated when create/update/delete operation is performed.\n", 55 | "google.protobuf.Timestamp.nanos": " Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive.\n", 56 | "google.protobuf.Timestamp.seconds": " Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive.\n", 57 | }, 58 | ), 59 | aipcli.NewMethodCommand( 60 | config, 61 | File_einride_example_freight_v1_freight_service_proto. 62 | Services().ByName("FreightService").Methods().ByName("UpdateShipper"), 63 | &UpdateShipperRequest{}, 64 | &Shipper{}, 65 | map[protoreflect.FullName]string{ 66 | "einride.example.freight.v1.FreightService.UpdateShipper": " Update a shipper.\n\n See: https://google.aip.dev/134 (Standard methods: Update).\n", 67 | "einride.example.freight.v1.Shipper.create_time": " The creation timestamp of the shipper.\n", 68 | "einride.example.freight.v1.Shipper.delete_time": " The deletion timestamp of the shipper.\n", 69 | "einride.example.freight.v1.Shipper.display_name": " The display name of the shipper.\n", 70 | "einride.example.freight.v1.Shipper.name": " The resource name of the shipper.\n", 71 | "einride.example.freight.v1.Shipper.update_time": " The last update timestamp of the shipper.\n\n Updated when create/update/delete operation is performed.\n", 72 | "einride.example.freight.v1.UpdateShipperRequest.shipper": " The shipper to update with. The name must match or be empty.\n The shipper's `name` field is used to identify the shipper to be updated.\n Format: shippers/{shipper}\n", 73 | "einride.example.freight.v1.UpdateShipperRequest.update_mask": " The list of fields to be updated.\n", 74 | "google.protobuf.FieldMask.paths": " The set of field mask paths.\n", 75 | "google.protobuf.Timestamp.nanos": " Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive.\n", 76 | "google.protobuf.Timestamp.seconds": " Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive.\n", 77 | }, 78 | ), 79 | aipcli.NewMethodCommand( 80 | config, 81 | File_einride_example_freight_v1_freight_service_proto. 82 | Services().ByName("FreightService").Methods().ByName("DeleteShipper"), 83 | &DeleteShipperRequest{}, 84 | &Shipper{}, 85 | map[protoreflect.FullName]string{ 86 | "einride.example.freight.v1.DeleteShipperRequest.name": " The resource name of the shipper to delete.\n Format: shippers/{shipper}\n", 87 | "einride.example.freight.v1.FreightService.DeleteShipper": " Delete a shipper.\n\n See: https://google.aip.dev/135 (Standard methods: Delete).\n See: https://google.aip.dev/164 (Soft delete).\n", 88 | }, 89 | ), 90 | aipcli.NewMethodCommand( 91 | config, 92 | File_einride_example_freight_v1_freight_service_proto. 93 | Services().ByName("FreightService").Methods().ByName("GetSite"), 94 | &GetSiteRequest{}, 95 | &Site{}, 96 | map[protoreflect.FullName]string{ 97 | "einride.example.freight.v1.FreightService.GetSite": " Get a site.\n\n See: https://google.aip.dev/131 (Standard methods: Get).\n", 98 | "einride.example.freight.v1.GetSiteRequest.name": " The resource name of the site to retrieve.\n Format: shippers/{shipper}/sites/{site}\n", 99 | }, 100 | ), 101 | aipcli.NewMethodCommand( 102 | config, 103 | File_einride_example_freight_v1_freight_service_proto. 104 | Services().ByName("FreightService").Methods().ByName("ListSites"), 105 | &ListSitesRequest{}, 106 | &ListSitesResponse{}, 107 | map[protoreflect.FullName]string{ 108 | "einride.example.freight.v1.FreightService.ListSites": " List sites for a shipper.\n\n See: https://google.aip.dev/132 (Standard methods: List).\n", 109 | "einride.example.freight.v1.ListSitesRequest.page_size": " Requested page size. Server may return fewer sites than requested.\n If unspecified, server will pick an appropriate default.\n", 110 | "einride.example.freight.v1.ListSitesRequest.page_token": " A token identifying a page of results the server should return.\n Typically, this is the value of\n [ListSitesResponse.next_page_token][einride.example.freight.v1.ListSitesResponse.next_page_token]\n returned from the previous call to `ListSites` method.\n", 111 | "einride.example.freight.v1.ListSitesRequest.parent": " The resource name of the parent, which owns this collection of sites.\n Format: shippers/{shipper}\n", 112 | "einride.example.freight.v1.ListSitesRequest.skip": " Number of resource to skip in the request.\n * A request with no page token and a skip value of 30 returns a single\n page of results starting with the 31st result.\n * A request with a page token corresponding to the 51st result (because the\n first 50 results were returned on the first page) and a skip value of 30\n returns a single page of results starting with the 81st result.\n", 113 | }, 114 | ), 115 | aipcli.NewMethodCommand( 116 | config, 117 | File_einride_example_freight_v1_freight_service_proto. 118 | Services().ByName("FreightService").Methods().ByName("CreateSite"), 119 | &CreateSiteRequest{}, 120 | &Site{}, 121 | map[protoreflect.FullName]string{ 122 | "einride.example.freight.v1.CreateSiteRequest.parent": " The resource name of the parent shipper for which this site will be created.\n Format: shippers/{shipper}\n", 123 | "einride.example.freight.v1.CreateSiteRequest.site": " The site to create.\n", 124 | "einride.example.freight.v1.FreightService.CreateSite": " Create a site.\n\n See: https://google.aip.dev/133 (Standard methods: Create).\n", 125 | "einride.example.freight.v1.Site.create_time": " The creation timestamp of the site.\n", 126 | "einride.example.freight.v1.Site.delete_time": " The deletion timestamp of the site.\n", 127 | "einride.example.freight.v1.Site.display_name": " The display name of the site.\n", 128 | "einride.example.freight.v1.Site.lat_lng": " The geographic location of the site.\n", 129 | "einride.example.freight.v1.Site.name": " The resource name of the site.\n", 130 | "einride.example.freight.v1.Site.update_time": " The last update timestamp of the site.\n\n Updated when create/update/delete operation is performed.\n", 131 | "google.protobuf.Timestamp.nanos": " Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive.\n", 132 | "google.protobuf.Timestamp.seconds": " Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive.\n", 133 | "google.type.LatLng.latitude": " The latitude in degrees. It must be in the range [-90.0, +90.0].\n", 134 | "google.type.LatLng.longitude": " The longitude in degrees. It must be in the range [-180.0, +180.0].\n", 135 | }, 136 | ), 137 | aipcli.NewMethodCommand( 138 | config, 139 | File_einride_example_freight_v1_freight_service_proto. 140 | Services().ByName("FreightService").Methods().ByName("UpdateSite"), 141 | &UpdateSiteRequest{}, 142 | &Site{}, 143 | map[protoreflect.FullName]string{ 144 | "einride.example.freight.v1.FreightService.UpdateSite": " Update a site.\n\n See: https://google.aip.dev/134 (Standard methods: Update).\n", 145 | "einride.example.freight.v1.Site.create_time": " The creation timestamp of the site.\n", 146 | "einride.example.freight.v1.Site.delete_time": " The deletion timestamp of the site.\n", 147 | "einride.example.freight.v1.Site.display_name": " The display name of the site.\n", 148 | "einride.example.freight.v1.Site.lat_lng": " The geographic location of the site.\n", 149 | "einride.example.freight.v1.Site.name": " The resource name of the site.\n", 150 | "einride.example.freight.v1.Site.update_time": " The last update timestamp of the site.\n\n Updated when create/update/delete operation is performed.\n", 151 | "einride.example.freight.v1.UpdateSiteRequest.site": " The site to update with. The name must match or be empty.\n The site's `name` field is used to identify the site to be updated.\n Format: shippers/{shipper}/sites/{site}\n", 152 | "einride.example.freight.v1.UpdateSiteRequest.update_mask": " The list of fields to be updated.\n", 153 | "google.protobuf.FieldMask.paths": " The set of field mask paths.\n", 154 | "google.protobuf.Timestamp.nanos": " Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive.\n", 155 | "google.protobuf.Timestamp.seconds": " Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive.\n", 156 | "google.type.LatLng.latitude": " The latitude in degrees. It must be in the range [-90.0, +90.0].\n", 157 | "google.type.LatLng.longitude": " The longitude in degrees. It must be in the range [-180.0, +180.0].\n", 158 | }, 159 | ), 160 | aipcli.NewMethodCommand( 161 | config, 162 | File_einride_example_freight_v1_freight_service_proto. 163 | Services().ByName("FreightService").Methods().ByName("DeleteSite"), 164 | &DeleteSiteRequest{}, 165 | &Site{}, 166 | map[protoreflect.FullName]string{ 167 | "einride.example.freight.v1.DeleteSiteRequest.name": " The resource name of the site to delete.\n Format: shippers/{shipper}/sites/{site}\n", 168 | "einride.example.freight.v1.FreightService.DeleteSite": " Delete a site.\n\n See: https://google.aip.dev/135 (Standard methods: Delete).\n See: https://google.aip.dev/164 (Soft delete).\n", 169 | }, 170 | ), 171 | aipcli.NewMethodCommand( 172 | config, 173 | File_einride_example_freight_v1_freight_service_proto. 174 | Services().ByName("FreightService").Methods().ByName("BatchGetSites"), 175 | &BatchGetSitesRequest{}, 176 | &BatchGetSitesResponse{}, 177 | map[protoreflect.FullName]string{ 178 | "einride.example.freight.v1.BatchGetSitesRequest.names": " The names of the sites to retrieve.\n A maximum of 1000 sites can be retrieved in a batch.\n Format: `shippers/{shipper}/sites/{site}`\n", 179 | "einride.example.freight.v1.BatchGetSitesRequest.parent": " The parent resource shared by all sites being retrieved.\n If this is set, the parent of all of the sites specified in `names`\n must match this field.\n Format: `shippers/{shipper}`\n", 180 | "einride.example.freight.v1.FreightService.BatchGetSites": " Batch get sites.\n\n See: https://google.aip.dev/231 (Batch methods: Get).\n", 181 | }, 182 | ), 183 | aipcli.NewMethodCommand( 184 | config, 185 | File_einride_example_freight_v1_freight_service_proto. 186 | Services().ByName("FreightService").Methods().ByName("GetShipment"), 187 | &GetShipmentRequest{}, 188 | &Shipment{}, 189 | map[protoreflect.FullName]string{ 190 | "einride.example.freight.v1.FreightService.GetShipment": " Get a shipment.\n\n See: https://google.aip.dev/131 (Standard methods: Get).\n", 191 | "einride.example.freight.v1.GetShipmentRequest.name": " The resource name of the shipment to retrieve.\n Format: shippers/{shipper}/shipments/{shipment}\n", 192 | }, 193 | ), 194 | aipcli.NewMethodCommand( 195 | config, 196 | File_einride_example_freight_v1_freight_service_proto. 197 | Services().ByName("FreightService").Methods().ByName("ListShipments"), 198 | &ListShipmentsRequest{}, 199 | &ListShipmentsResponse{}, 200 | map[protoreflect.FullName]string{ 201 | "einride.example.freight.v1.FreightService.ListShipments": " List shipments for a shipper.\n\n See: https://google.aip.dev/132 (Standard methods: List).\n", 202 | "einride.example.freight.v1.ListShipmentsRequest.page_size": " Requested page size. Server may return fewer shipments than requested.\n If unspecified, server will pick an appropriate default.\n", 203 | "einride.example.freight.v1.ListShipmentsRequest.page_token": " A token identifying a page of results the server should return.\n Typically, this is the value of\n [ListShipmentsResponse.next_page_token][einride.example.freight.v1.ListShipmentsResponse.next_page_token]\n returned from the previous call to `ListShipments` method.\n", 204 | "einride.example.freight.v1.ListShipmentsRequest.parent": " The resource name of the parent, which owns this collection of shipments.\n Format: shippers/{shipper}\n", 205 | }, 206 | ), 207 | aipcli.NewMethodCommand( 208 | config, 209 | File_einride_example_freight_v1_freight_service_proto. 210 | Services().ByName("FreightService").Methods().ByName("CreateShipment"), 211 | &CreateShipmentRequest{}, 212 | &Shipment{}, 213 | map[protoreflect.FullName]string{ 214 | "einride.example.freight.v1.CreateShipmentRequest.parent": " The resource name of the parent shipper for which this shipment will be created.\n Format: shippers/{shipper}\n", 215 | "einride.example.freight.v1.CreateShipmentRequest.shipment": " The shipment to create.\n", 216 | "einride.example.freight.v1.FreightService.CreateShipment": " Create a shipment.\n\n See: https://google.aip.dev/133 (Standard methods: Create).\n", 217 | "einride.example.freight.v1.LineItem.quantity": " The quantity of the line item.\n", 218 | "einride.example.freight.v1.LineItem.title": " The title of the line item.\n", 219 | "einride.example.freight.v1.LineItem.volume_m3": " The volume of the line item in cubic meters.\n", 220 | "einride.example.freight.v1.LineItem.weight_kg": " The weight of the line item in kilograms.\n", 221 | "einride.example.freight.v1.Shipment.AnnotationsEntry.key": "", 222 | "einride.example.freight.v1.Shipment.AnnotationsEntry.value": "", 223 | "einride.example.freight.v1.Shipment.annotations": " Annotations of the shipment.\n", 224 | "einride.example.freight.v1.Shipment.create_time": " The creation timestamp of the shipment.\n", 225 | "einride.example.freight.v1.Shipment.delete_time": " The deletion timestamp of the shipment.\n", 226 | "einride.example.freight.v1.Shipment.delivery_earliest_time": " The earliest delivery time of the shipment at the destination site.\n", 227 | "einride.example.freight.v1.Shipment.delivery_latest_time": " The latest delivery time of the shipment at the destination site.\n", 228 | "einride.example.freight.v1.Shipment.destination_site": " The resource name of the destination site of the shipment.\n Format: shippers/{shipper}/sites/{site}\n", 229 | "einride.example.freight.v1.Shipment.line_items": " The line items of the shipment.\n", 230 | "einride.example.freight.v1.Shipment.name": " The resource name of the shipment.\n", 231 | "einride.example.freight.v1.Shipment.origin_site": " The resource name of the origin site of the shipment.\n Format: shippers/{shipper}/sites/{site}\n", 232 | "einride.example.freight.v1.Shipment.pickup_earliest_time": " The earliest pickup time of the shipment at the origin site.\n", 233 | "einride.example.freight.v1.Shipment.pickup_latest_time": " The latest pickup time of the shipment at the origin site.\n", 234 | "einride.example.freight.v1.Shipment.update_time": " The last update timestamp of the shipment.\n\n Updated when create/update/delete operation is shipment.\n", 235 | "google.protobuf.Timestamp.nanos": " Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive.\n", 236 | "google.protobuf.Timestamp.seconds": " Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive.\n", 237 | }, 238 | ), 239 | aipcli.NewMethodCommand( 240 | config, 241 | File_einride_example_freight_v1_freight_service_proto. 242 | Services().ByName("FreightService").Methods().ByName("UpdateShipment"), 243 | &UpdateShipmentRequest{}, 244 | &Shipment{}, 245 | map[protoreflect.FullName]string{ 246 | "einride.example.freight.v1.FreightService.UpdateShipment": " Update a shipment.\n\n See: https://google.aip.dev/134 (Standard methods: Update).\n", 247 | "einride.example.freight.v1.LineItem.quantity": " The quantity of the line item.\n", 248 | "einride.example.freight.v1.LineItem.title": " The title of the line item.\n", 249 | "einride.example.freight.v1.LineItem.volume_m3": " The volume of the line item in cubic meters.\n", 250 | "einride.example.freight.v1.LineItem.weight_kg": " The weight of the line item in kilograms.\n", 251 | "einride.example.freight.v1.Shipment.AnnotationsEntry.key": "", 252 | "einride.example.freight.v1.Shipment.AnnotationsEntry.value": "", 253 | "einride.example.freight.v1.Shipment.annotations": " Annotations of the shipment.\n", 254 | "einride.example.freight.v1.Shipment.create_time": " The creation timestamp of the shipment.\n", 255 | "einride.example.freight.v1.Shipment.delete_time": " The deletion timestamp of the shipment.\n", 256 | "einride.example.freight.v1.Shipment.delivery_earliest_time": " The earliest delivery time of the shipment at the destination site.\n", 257 | "einride.example.freight.v1.Shipment.delivery_latest_time": " The latest delivery time of the shipment at the destination site.\n", 258 | "einride.example.freight.v1.Shipment.destination_site": " The resource name of the destination site of the shipment.\n Format: shippers/{shipper}/sites/{site}\n", 259 | "einride.example.freight.v1.Shipment.line_items": " The line items of the shipment.\n", 260 | "einride.example.freight.v1.Shipment.name": " The resource name of the shipment.\n", 261 | "einride.example.freight.v1.Shipment.origin_site": " The resource name of the origin site of the shipment.\n Format: shippers/{shipper}/sites/{site}\n", 262 | "einride.example.freight.v1.Shipment.pickup_earliest_time": " The earliest pickup time of the shipment at the origin site.\n", 263 | "einride.example.freight.v1.Shipment.pickup_latest_time": " The latest pickup time of the shipment at the origin site.\n", 264 | "einride.example.freight.v1.Shipment.update_time": " The last update timestamp of the shipment.\n\n Updated when create/update/delete operation is shipment.\n", 265 | "einride.example.freight.v1.UpdateShipmentRequest.shipment": " The shipment to update with. The name must match or be empty.\n The shipment's `name` field is used to identify the shipment to be updated.\n Format: shippers/{shipper}/shipments/{shipment}\n", 266 | "einride.example.freight.v1.UpdateShipmentRequest.update_mask": " The list of fields to be updated.\n", 267 | "google.protobuf.FieldMask.paths": " The set of field mask paths.\n", 268 | "google.protobuf.Timestamp.nanos": " Non-negative fractions of a second at nanosecond resolution. Negative\n second values with fractions must still have non-negative nanos values\n that count forward in time. Must be from 0 to 999,999,999\n inclusive.\n", 269 | "google.protobuf.Timestamp.seconds": " Represents seconds of UTC time since Unix epoch\n 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n 9999-12-31T23:59:59Z inclusive.\n", 270 | }, 271 | ), 272 | aipcli.NewMethodCommand( 273 | config, 274 | File_einride_example_freight_v1_freight_service_proto. 275 | Services().ByName("FreightService").Methods().ByName("DeleteShipment"), 276 | &DeleteShipmentRequest{}, 277 | &Shipment{}, 278 | map[protoreflect.FullName]string{ 279 | "einride.example.freight.v1.DeleteShipmentRequest.name": " The resource name of the shipment to delete.\n Format: shippers/{shipper}/shipments/{shipment}\n", 280 | "einride.example.freight.v1.FreightService.DeleteShipment": " Delete a shipment.\n\n See: https://google.aip.dev/135 (Standard methods: Delete).\n See: https://google.aip.dev/164 (Soft delete).\n", 281 | }, 282 | ), 283 | ) 284 | } 285 | -------------------------------------------------------------------------------- /cmd/examplectl/gen/einride/example/freight/v1/shipment.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.35.1 4 | // protoc (unknown) 5 | // source: einride/example/freight/v1/shipment.proto 6 | 7 | package freightv1 8 | 9 | import ( 10 | _ "google.golang.org/genproto/googleapis/api/annotations" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" 14 | reflect "reflect" 15 | sync "sync" 16 | ) 17 | 18 | const ( 19 | // Verify that this generated code is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 21 | // Verify that runtime/protoimpl is sufficiently up-to-date. 22 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 23 | ) 24 | 25 | // A shipment represents transportation of goods between an origin 26 | // [site][einride.example.freight.v1.Site] and a destination 27 | // [site][einride.example.freight.v1.Site]. 28 | type Shipment struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | // The resource name of the shipment. 34 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 35 | // The creation timestamp of the shipment. 36 | CreateTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"` 37 | // The last update timestamp of the shipment. 38 | // 39 | // Updated when create/update/delete operation is shipment. 40 | UpdateTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` 41 | // The deletion timestamp of the shipment. 42 | DeleteTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=delete_time,json=deleteTime,proto3" json:"delete_time,omitempty"` 43 | // The resource name of the origin site of the shipment. 44 | // Format: shippers/{shipper}/sites/{site} 45 | OriginSite string `protobuf:"bytes,5,opt,name=origin_site,json=originSite,proto3" json:"origin_site,omitempty"` 46 | // The resource name of the destination site of the shipment. 47 | // Format: shippers/{shipper}/sites/{site} 48 | DestinationSite string `protobuf:"bytes,6,opt,name=destination_site,json=destinationSite,proto3" json:"destination_site,omitempty"` 49 | // The earliest pickup time of the shipment at the origin site. 50 | PickupEarliestTime *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=pickup_earliest_time,json=pickupEarliestTime,proto3" json:"pickup_earliest_time,omitempty"` 51 | // The latest pickup time of the shipment at the origin site. 52 | PickupLatestTime *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=pickup_latest_time,json=pickupLatestTime,proto3" json:"pickup_latest_time,omitempty"` 53 | // The earliest delivery time of the shipment at the destination site. 54 | DeliveryEarliestTime *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=delivery_earliest_time,json=deliveryEarliestTime,proto3" json:"delivery_earliest_time,omitempty"` 55 | // The latest delivery time of the shipment at the destination site. 56 | DeliveryLatestTime *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=delivery_latest_time,json=deliveryLatestTime,proto3" json:"delivery_latest_time,omitempty"` 57 | // The line items of the shipment. 58 | LineItems []*LineItem `protobuf:"bytes,11,rep,name=line_items,json=lineItems,proto3" json:"line_items,omitempty"` 59 | // Annotations of the shipment. 60 | Annotations map[string]string `protobuf:"bytes,12,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 61 | } 62 | 63 | func (x *Shipment) Reset() { 64 | *x = Shipment{} 65 | mi := &file_einride_example_freight_v1_shipment_proto_msgTypes[0] 66 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 67 | ms.StoreMessageInfo(mi) 68 | } 69 | 70 | func (x *Shipment) String() string { 71 | return protoimpl.X.MessageStringOf(x) 72 | } 73 | 74 | func (*Shipment) ProtoMessage() {} 75 | 76 | func (x *Shipment) ProtoReflect() protoreflect.Message { 77 | mi := &file_einride_example_freight_v1_shipment_proto_msgTypes[0] 78 | if x != nil { 79 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 80 | if ms.LoadMessageInfo() == nil { 81 | ms.StoreMessageInfo(mi) 82 | } 83 | return ms 84 | } 85 | return mi.MessageOf(x) 86 | } 87 | 88 | // Deprecated: Use Shipment.ProtoReflect.Descriptor instead. 89 | func (*Shipment) Descriptor() ([]byte, []int) { 90 | return file_einride_example_freight_v1_shipment_proto_rawDescGZIP(), []int{0} 91 | } 92 | 93 | func (x *Shipment) GetName() string { 94 | if x != nil { 95 | return x.Name 96 | } 97 | return "" 98 | } 99 | 100 | func (x *Shipment) GetCreateTime() *timestamppb.Timestamp { 101 | if x != nil { 102 | return x.CreateTime 103 | } 104 | return nil 105 | } 106 | 107 | func (x *Shipment) GetUpdateTime() *timestamppb.Timestamp { 108 | if x != nil { 109 | return x.UpdateTime 110 | } 111 | return nil 112 | } 113 | 114 | func (x *Shipment) GetDeleteTime() *timestamppb.Timestamp { 115 | if x != nil { 116 | return x.DeleteTime 117 | } 118 | return nil 119 | } 120 | 121 | func (x *Shipment) GetOriginSite() string { 122 | if x != nil { 123 | return x.OriginSite 124 | } 125 | return "" 126 | } 127 | 128 | func (x *Shipment) GetDestinationSite() string { 129 | if x != nil { 130 | return x.DestinationSite 131 | } 132 | return "" 133 | } 134 | 135 | func (x *Shipment) GetPickupEarliestTime() *timestamppb.Timestamp { 136 | if x != nil { 137 | return x.PickupEarliestTime 138 | } 139 | return nil 140 | } 141 | 142 | func (x *Shipment) GetPickupLatestTime() *timestamppb.Timestamp { 143 | if x != nil { 144 | return x.PickupLatestTime 145 | } 146 | return nil 147 | } 148 | 149 | func (x *Shipment) GetDeliveryEarliestTime() *timestamppb.Timestamp { 150 | if x != nil { 151 | return x.DeliveryEarliestTime 152 | } 153 | return nil 154 | } 155 | 156 | func (x *Shipment) GetDeliveryLatestTime() *timestamppb.Timestamp { 157 | if x != nil { 158 | return x.DeliveryLatestTime 159 | } 160 | return nil 161 | } 162 | 163 | func (x *Shipment) GetLineItems() []*LineItem { 164 | if x != nil { 165 | return x.LineItems 166 | } 167 | return nil 168 | } 169 | 170 | func (x *Shipment) GetAnnotations() map[string]string { 171 | if x != nil { 172 | return x.Annotations 173 | } 174 | return nil 175 | } 176 | 177 | // A shipment line item. 178 | type LineItem struct { 179 | state protoimpl.MessageState 180 | sizeCache protoimpl.SizeCache 181 | unknownFields protoimpl.UnknownFields 182 | 183 | // The title of the line item. 184 | Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` 185 | // The quantity of the line item. 186 | Quantity float32 `protobuf:"fixed32,2,opt,name=quantity,proto3" json:"quantity,omitempty"` 187 | // The weight of the line item in kilograms. 188 | WeightKg float32 `protobuf:"fixed32,3,opt,name=weight_kg,json=weightKg,proto3" json:"weight_kg,omitempty"` 189 | // The volume of the line item in cubic meters. 190 | VolumeM3 float32 `protobuf:"fixed32,4,opt,name=volume_m3,json=volumeM3,proto3" json:"volume_m3,omitempty"` 191 | } 192 | 193 | func (x *LineItem) Reset() { 194 | *x = LineItem{} 195 | mi := &file_einride_example_freight_v1_shipment_proto_msgTypes[1] 196 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 197 | ms.StoreMessageInfo(mi) 198 | } 199 | 200 | func (x *LineItem) String() string { 201 | return protoimpl.X.MessageStringOf(x) 202 | } 203 | 204 | func (*LineItem) ProtoMessage() {} 205 | 206 | func (x *LineItem) ProtoReflect() protoreflect.Message { 207 | mi := &file_einride_example_freight_v1_shipment_proto_msgTypes[1] 208 | if x != nil { 209 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 210 | if ms.LoadMessageInfo() == nil { 211 | ms.StoreMessageInfo(mi) 212 | } 213 | return ms 214 | } 215 | return mi.MessageOf(x) 216 | } 217 | 218 | // Deprecated: Use LineItem.ProtoReflect.Descriptor instead. 219 | func (*LineItem) Descriptor() ([]byte, []int) { 220 | return file_einride_example_freight_v1_shipment_proto_rawDescGZIP(), []int{1} 221 | } 222 | 223 | func (x *LineItem) GetTitle() string { 224 | if x != nil { 225 | return x.Title 226 | } 227 | return "" 228 | } 229 | 230 | func (x *LineItem) GetQuantity() float32 { 231 | if x != nil { 232 | return x.Quantity 233 | } 234 | return 0 235 | } 236 | 237 | func (x *LineItem) GetWeightKg() float32 { 238 | if x != nil { 239 | return x.WeightKg 240 | } 241 | return 0 242 | } 243 | 244 | func (x *LineItem) GetVolumeM3() float32 { 245 | if x != nil { 246 | return x.VolumeM3 247 | } 248 | return 0 249 | } 250 | 251 | var File_einride_example_freight_v1_shipment_proto protoreflect.FileDescriptor 252 | 253 | var file_einride_example_freight_v1_shipment_proto_rawDesc = []byte{ 254 | 0x0a, 0x29, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 255 | 0x65, 0x2f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x68, 0x69, 256 | 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x65, 0x69, 0x6e, 257 | 0x72, 0x69, 0x64, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 0x72, 0x65, 258 | 0x69, 0x67, 0x68, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 259 | 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 260 | 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 261 | 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 262 | 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 263 | 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 264 | 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa3, 0x08, 0x0a, 0x08, 0x53, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 265 | 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 266 | 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 267 | 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 268 | 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 269 | 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x03, 0x52, 0x0a, 0x63, 0x72, 270 | 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 271 | 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 272 | 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 273 | 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x03, 0x52, 274 | 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x64, 275 | 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 276 | 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 277 | 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 278 | 0x01, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 279 | 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x18, 0x05, 0x20, 280 | 0x01, 0x28, 0x09, 0x42, 0x2a, 0xe2, 0x41, 0x01, 0x02, 0xfa, 0x41, 0x23, 0x0a, 0x21, 0x66, 0x72, 281 | 0x65, 0x69, 0x67, 0x68, 0x74, 0x2d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x65, 0x69, 282 | 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x53, 0x69, 0x74, 0x65, 0x52, 283 | 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x53, 0x69, 0x74, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x64, 284 | 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x18, 285 | 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2a, 0xe2, 0x41, 0x01, 0x02, 0xfa, 0x41, 0x23, 0x0a, 0x21, 286 | 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 287 | 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x53, 0x69, 0x74, 288 | 0x65, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 289 | 0x74, 0x65, 0x12, 0x52, 0x0a, 0x14, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x65, 0x61, 0x72, 290 | 0x6c, 0x69, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 291 | 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 292 | 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 293 | 0x01, 0x02, 0x52, 0x12, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x61, 0x72, 0x6c, 0x69, 0x65, 294 | 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 295 | 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 296 | 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 297 | 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 298 | 0xe2, 0x41, 0x01, 0x02, 0x52, 0x10, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x4c, 0x61, 0x74, 0x65, 299 | 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x56, 0x0a, 0x16, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 300 | 0x72, 0x79, 0x5f, 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 301 | 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 302 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 303 | 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x02, 0x52, 0x14, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 304 | 0x72, 0x79, 0x45, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x52, 305 | 0x0a, 0x14, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x73, 306 | 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 307 | 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 308 | 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x02, 0x52, 0x12, 309 | 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x54, 0x69, 310 | 0x6d, 0x65, 0x12, 0x43, 0x0a, 0x0a, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 311 | 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 312 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 313 | 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x6c, 0x69, 314 | 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x57, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 315 | 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x65, 316 | 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 317 | 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x6d, 0x65, 318 | 0x6e, 0x74, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 319 | 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 320 | 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 321 | 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 322 | 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 323 | 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 324 | 0x3a, 0x68, 0xea, 0x41, 0x65, 0x0a, 0x25, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2d, 0x65, 325 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x74, 326 | 0x65, 0x63, 0x68, 0x2f, 0x53, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x73, 0x68, 327 | 0x69, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x73, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 0x7d, 328 | 0x2f, 0x73, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x73, 0x68, 0x69, 0x70, 329 | 0x6d, 0x65, 0x6e, 0x74, 0x7d, 0x2a, 0x09, 0x73, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x73, 330 | 0x32, 0x08, 0x73, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x76, 0x0a, 0x08, 0x4c, 0x69, 331 | 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 332 | 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x08, 333 | 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 334 | 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x65, 0x69, 0x67, 335 | 0x68, 0x74, 0x5f, 0x6b, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x77, 0x65, 0x69, 336 | 0x67, 0x68, 0x74, 0x4b, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x5f, 337 | 0x6d, 0x33, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 338 | 0x4d, 0x33, 0x42, 0x8b, 0x02, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 339 | 0x64, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 0x72, 0x65, 0x69, 0x67, 340 | 0x68, 0x74, 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x53, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x50, 341 | 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x6f, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 342 | 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x61, 0x69, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 343 | 0x63, 0x6d, 0x64, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x63, 0x74, 0x6c, 0x2f, 0x67, 344 | 0x65, 0x6e, 0x2f, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 345 | 0x6c, 0x65, 0x2f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x66, 0x72, 346 | 0x65, 0x69, 0x67, 0x68, 0x74, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x45, 0x45, 0x46, 0xaa, 0x02, 0x1a, 347 | 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 348 | 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x1a, 0x45, 0x69, 0x6e, 349 | 0x72, 0x69, 0x64, 0x65, 0x5c, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5c, 0x46, 0x72, 0x65, 350 | 0x69, 0x67, 0x68, 0x74, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x26, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 351 | 0x65, 0x5c, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5c, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 352 | 0x74, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 353 | 0xea, 0x02, 0x1d, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x3a, 0x3a, 0x45, 0x78, 0x61, 0x6d, 354 | 0x70, 0x6c, 0x65, 0x3a, 0x3a, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x3a, 0x56, 0x31, 355 | 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 356 | } 357 | 358 | var ( 359 | file_einride_example_freight_v1_shipment_proto_rawDescOnce sync.Once 360 | file_einride_example_freight_v1_shipment_proto_rawDescData = file_einride_example_freight_v1_shipment_proto_rawDesc 361 | ) 362 | 363 | func file_einride_example_freight_v1_shipment_proto_rawDescGZIP() []byte { 364 | file_einride_example_freight_v1_shipment_proto_rawDescOnce.Do(func() { 365 | file_einride_example_freight_v1_shipment_proto_rawDescData = protoimpl.X.CompressGZIP(file_einride_example_freight_v1_shipment_proto_rawDescData) 366 | }) 367 | return file_einride_example_freight_v1_shipment_proto_rawDescData 368 | } 369 | 370 | var file_einride_example_freight_v1_shipment_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 371 | var file_einride_example_freight_v1_shipment_proto_goTypes = []any{ 372 | (*Shipment)(nil), // 0: einride.example.freight.v1.Shipment 373 | (*LineItem)(nil), // 1: einride.example.freight.v1.LineItem 374 | nil, // 2: einride.example.freight.v1.Shipment.AnnotationsEntry 375 | (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp 376 | } 377 | var file_einride_example_freight_v1_shipment_proto_depIdxs = []int32{ 378 | 3, // 0: einride.example.freight.v1.Shipment.create_time:type_name -> google.protobuf.Timestamp 379 | 3, // 1: einride.example.freight.v1.Shipment.update_time:type_name -> google.protobuf.Timestamp 380 | 3, // 2: einride.example.freight.v1.Shipment.delete_time:type_name -> google.protobuf.Timestamp 381 | 3, // 3: einride.example.freight.v1.Shipment.pickup_earliest_time:type_name -> google.protobuf.Timestamp 382 | 3, // 4: einride.example.freight.v1.Shipment.pickup_latest_time:type_name -> google.protobuf.Timestamp 383 | 3, // 5: einride.example.freight.v1.Shipment.delivery_earliest_time:type_name -> google.protobuf.Timestamp 384 | 3, // 6: einride.example.freight.v1.Shipment.delivery_latest_time:type_name -> google.protobuf.Timestamp 385 | 1, // 7: einride.example.freight.v1.Shipment.line_items:type_name -> einride.example.freight.v1.LineItem 386 | 2, // 8: einride.example.freight.v1.Shipment.annotations:type_name -> einride.example.freight.v1.Shipment.AnnotationsEntry 387 | 9, // [9:9] is the sub-list for method output_type 388 | 9, // [9:9] is the sub-list for method input_type 389 | 9, // [9:9] is the sub-list for extension type_name 390 | 9, // [9:9] is the sub-list for extension extendee 391 | 0, // [0:9] is the sub-list for field type_name 392 | } 393 | 394 | func init() { file_einride_example_freight_v1_shipment_proto_init() } 395 | func file_einride_example_freight_v1_shipment_proto_init() { 396 | if File_einride_example_freight_v1_shipment_proto != nil { 397 | return 398 | } 399 | type x struct{} 400 | out := protoimpl.TypeBuilder{ 401 | File: protoimpl.DescBuilder{ 402 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 403 | RawDescriptor: file_einride_example_freight_v1_shipment_proto_rawDesc, 404 | NumEnums: 0, 405 | NumMessages: 3, 406 | NumExtensions: 0, 407 | NumServices: 0, 408 | }, 409 | GoTypes: file_einride_example_freight_v1_shipment_proto_goTypes, 410 | DependencyIndexes: file_einride_example_freight_v1_shipment_proto_depIdxs, 411 | MessageInfos: file_einride_example_freight_v1_shipment_proto_msgTypes, 412 | }.Build() 413 | File_einride_example_freight_v1_shipment_proto = out.File 414 | file_einride_example_freight_v1_shipment_proto_rawDesc = nil 415 | file_einride_example_freight_v1_shipment_proto_goTypes = nil 416 | file_einride_example_freight_v1_shipment_proto_depIdxs = nil 417 | } 418 | -------------------------------------------------------------------------------- /cmd/examplectl/gen/einride/example/freight/v1/shipper.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.35.1 4 | // protoc (unknown) 5 | // source: einride/example/freight/v1/shipper.proto 6 | 7 | package freightv1 8 | 9 | import ( 10 | _ "google.golang.org/genproto/googleapis/api/annotations" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" 14 | reflect "reflect" 15 | sync "sync" 16 | ) 17 | 18 | const ( 19 | // Verify that this generated code is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 21 | // Verify that runtime/protoimpl is sufficiently up-to-date. 22 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 23 | ) 24 | 25 | // A shipper is a supplier or owner of goods to be transported. 26 | type Shipper struct { 27 | state protoimpl.MessageState 28 | sizeCache protoimpl.SizeCache 29 | unknownFields protoimpl.UnknownFields 30 | 31 | // The resource name of the shipper. 32 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 33 | // The creation timestamp of the shipper. 34 | CreateTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"` 35 | // The last update timestamp of the shipper. 36 | // 37 | // Updated when create/update/delete operation is performed. 38 | UpdateTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` 39 | // The deletion timestamp of the shipper. 40 | DeleteTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=delete_time,json=deleteTime,proto3" json:"delete_time,omitempty"` 41 | // The display name of the shipper. 42 | DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` 43 | } 44 | 45 | func (x *Shipper) Reset() { 46 | *x = Shipper{} 47 | mi := &file_einride_example_freight_v1_shipper_proto_msgTypes[0] 48 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 49 | ms.StoreMessageInfo(mi) 50 | } 51 | 52 | func (x *Shipper) String() string { 53 | return protoimpl.X.MessageStringOf(x) 54 | } 55 | 56 | func (*Shipper) ProtoMessage() {} 57 | 58 | func (x *Shipper) ProtoReflect() protoreflect.Message { 59 | mi := &file_einride_example_freight_v1_shipper_proto_msgTypes[0] 60 | if x != nil { 61 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 62 | if ms.LoadMessageInfo() == nil { 63 | ms.StoreMessageInfo(mi) 64 | } 65 | return ms 66 | } 67 | return mi.MessageOf(x) 68 | } 69 | 70 | // Deprecated: Use Shipper.ProtoReflect.Descriptor instead. 71 | func (*Shipper) Descriptor() ([]byte, []int) { 72 | return file_einride_example_freight_v1_shipper_proto_rawDescGZIP(), []int{0} 73 | } 74 | 75 | func (x *Shipper) GetName() string { 76 | if x != nil { 77 | return x.Name 78 | } 79 | return "" 80 | } 81 | 82 | func (x *Shipper) GetCreateTime() *timestamppb.Timestamp { 83 | if x != nil { 84 | return x.CreateTime 85 | } 86 | return nil 87 | } 88 | 89 | func (x *Shipper) GetUpdateTime() *timestamppb.Timestamp { 90 | if x != nil { 91 | return x.UpdateTime 92 | } 93 | return nil 94 | } 95 | 96 | func (x *Shipper) GetDeleteTime() *timestamppb.Timestamp { 97 | if x != nil { 98 | return x.DeleteTime 99 | } 100 | return nil 101 | } 102 | 103 | func (x *Shipper) GetDisplayName() string { 104 | if x != nil { 105 | return x.DisplayName 106 | } 107 | return "" 108 | } 109 | 110 | var File_einride_example_freight_v1_shipper_proto protoreflect.FileDescriptor 111 | 112 | var file_einride_example_freight_v1_shipper_proto_rawDesc = []byte{ 113 | 0x0a, 0x28, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 114 | 0x65, 0x2f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x68, 0x69, 115 | 0x70, 0x70, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x65, 0x69, 0x6e, 0x72, 116 | 0x69, 0x64, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 0x72, 0x65, 0x69, 117 | 0x67, 0x68, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 118 | 0x70, 0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 119 | 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 120 | 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 121 | 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 122 | 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 123 | 0x6f, 0x74, 0x6f, 0x22, 0xe1, 0x02, 0x0a, 0x07, 0x53, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 0x12, 124 | 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 125 | 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 126 | 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 127 | 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 128 | 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x03, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 129 | 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 130 | 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 131 | 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 132 | 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x03, 0x52, 0x0a, 0x75, 133 | 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x64, 0x65, 0x6c, 134 | 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 135 | 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 136 | 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x03, 137 | 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0c, 138 | 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 139 | 0x28, 0x09, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x02, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 140 | 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x50, 0xea, 0x41, 0x4d, 0x0a, 0x24, 0x66, 0x72, 0x65, 0x69, 141 | 0x67, 0x68, 0x74, 0x2d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x65, 0x69, 0x6e, 0x72, 142 | 0x69, 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x53, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 143 | 0x12, 0x12, 0x73, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x73, 0x68, 0x69, 0x70, 144 | 0x70, 0x65, 0x72, 0x7d, 0x2a, 0x08, 0x73, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 0x73, 0x32, 0x07, 145 | 0x73, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 0x42, 0x8a, 0x02, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 146 | 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 147 | 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x68, 0x69, 0x70, 148 | 0x70, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x6f, 0x2e, 0x65, 149 | 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x61, 0x69, 0x70, 0x2d, 150 | 0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x63, 151 | 0x74, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2f, 0x65, 152 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2f, 0x76, 153 | 0x31, 0x3b, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x45, 0x45, 154 | 0x46, 0xaa, 0x02, 0x1a, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x45, 0x78, 0x61, 0x6d, 155 | 0x70, 0x6c, 0x65, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x56, 0x31, 0xca, 0x02, 156 | 0x1a, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x5c, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 157 | 0x5c, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x26, 0x45, 0x69, 158 | 0x6e, 0x72, 0x69, 0x64, 0x65, 0x5c, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5c, 0x46, 0x72, 159 | 0x65, 0x69, 0x67, 0x68, 0x74, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 160 | 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1d, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x3a, 0x3a, 161 | 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3a, 0x3a, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 162 | 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 163 | } 164 | 165 | var ( 166 | file_einride_example_freight_v1_shipper_proto_rawDescOnce sync.Once 167 | file_einride_example_freight_v1_shipper_proto_rawDescData = file_einride_example_freight_v1_shipper_proto_rawDesc 168 | ) 169 | 170 | func file_einride_example_freight_v1_shipper_proto_rawDescGZIP() []byte { 171 | file_einride_example_freight_v1_shipper_proto_rawDescOnce.Do(func() { 172 | file_einride_example_freight_v1_shipper_proto_rawDescData = protoimpl.X.CompressGZIP(file_einride_example_freight_v1_shipper_proto_rawDescData) 173 | }) 174 | return file_einride_example_freight_v1_shipper_proto_rawDescData 175 | } 176 | 177 | var file_einride_example_freight_v1_shipper_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 178 | var file_einride_example_freight_v1_shipper_proto_goTypes = []any{ 179 | (*Shipper)(nil), // 0: einride.example.freight.v1.Shipper 180 | (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp 181 | } 182 | var file_einride_example_freight_v1_shipper_proto_depIdxs = []int32{ 183 | 1, // 0: einride.example.freight.v1.Shipper.create_time:type_name -> google.protobuf.Timestamp 184 | 1, // 1: einride.example.freight.v1.Shipper.update_time:type_name -> google.protobuf.Timestamp 185 | 1, // 2: einride.example.freight.v1.Shipper.delete_time:type_name -> google.protobuf.Timestamp 186 | 3, // [3:3] is the sub-list for method output_type 187 | 3, // [3:3] is the sub-list for method input_type 188 | 3, // [3:3] is the sub-list for extension type_name 189 | 3, // [3:3] is the sub-list for extension extendee 190 | 0, // [0:3] is the sub-list for field type_name 191 | } 192 | 193 | func init() { file_einride_example_freight_v1_shipper_proto_init() } 194 | func file_einride_example_freight_v1_shipper_proto_init() { 195 | if File_einride_example_freight_v1_shipper_proto != nil { 196 | return 197 | } 198 | type x struct{} 199 | out := protoimpl.TypeBuilder{ 200 | File: protoimpl.DescBuilder{ 201 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 202 | RawDescriptor: file_einride_example_freight_v1_shipper_proto_rawDesc, 203 | NumEnums: 0, 204 | NumMessages: 1, 205 | NumExtensions: 0, 206 | NumServices: 0, 207 | }, 208 | GoTypes: file_einride_example_freight_v1_shipper_proto_goTypes, 209 | DependencyIndexes: file_einride_example_freight_v1_shipper_proto_depIdxs, 210 | MessageInfos: file_einride_example_freight_v1_shipper_proto_msgTypes, 211 | }.Build() 212 | File_einride_example_freight_v1_shipper_proto = out.File 213 | file_einride_example_freight_v1_shipper_proto_rawDesc = nil 214 | file_einride_example_freight_v1_shipper_proto_goTypes = nil 215 | file_einride_example_freight_v1_shipper_proto_depIdxs = nil 216 | } 217 | -------------------------------------------------------------------------------- /cmd/examplectl/gen/einride/example/freight/v1/site.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.35.1 4 | // protoc (unknown) 5 | // source: einride/example/freight/v1/site.proto 6 | 7 | package freightv1 8 | 9 | import ( 10 | _ "google.golang.org/genproto/googleapis/api/annotations" 11 | latlng "google.golang.org/genproto/googleapis/type/latlng" 12 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 13 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 14 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" 15 | reflect "reflect" 16 | sync "sync" 17 | ) 18 | 19 | const ( 20 | // Verify that this generated code is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 22 | // Verify that runtime/protoimpl is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 24 | ) 25 | 26 | // A site is a node in a [shipper][einride.example.freight.v1.Shipper]'s 27 | // transport network. 28 | type Site struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | // The resource name of the site. 34 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 35 | // The creation timestamp of the site. 36 | CreateTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"` 37 | // The last update timestamp of the site. 38 | // 39 | // Updated when create/update/delete operation is performed. 40 | UpdateTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` 41 | // The deletion timestamp of the site. 42 | DeleteTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=delete_time,json=deleteTime,proto3" json:"delete_time,omitempty"` 43 | // The display name of the site. 44 | DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` 45 | // The geographic location of the site. 46 | LatLng *latlng.LatLng `protobuf:"bytes,6,opt,name=lat_lng,json=latLng,proto3" json:"lat_lng,omitempty"` 47 | } 48 | 49 | func (x *Site) Reset() { 50 | *x = Site{} 51 | mi := &file_einride_example_freight_v1_site_proto_msgTypes[0] 52 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 53 | ms.StoreMessageInfo(mi) 54 | } 55 | 56 | func (x *Site) String() string { 57 | return protoimpl.X.MessageStringOf(x) 58 | } 59 | 60 | func (*Site) ProtoMessage() {} 61 | 62 | func (x *Site) ProtoReflect() protoreflect.Message { 63 | mi := &file_einride_example_freight_v1_site_proto_msgTypes[0] 64 | if x != nil { 65 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 66 | if ms.LoadMessageInfo() == nil { 67 | ms.StoreMessageInfo(mi) 68 | } 69 | return ms 70 | } 71 | return mi.MessageOf(x) 72 | } 73 | 74 | // Deprecated: Use Site.ProtoReflect.Descriptor instead. 75 | func (*Site) Descriptor() ([]byte, []int) { 76 | return file_einride_example_freight_v1_site_proto_rawDescGZIP(), []int{0} 77 | } 78 | 79 | func (x *Site) GetName() string { 80 | if x != nil { 81 | return x.Name 82 | } 83 | return "" 84 | } 85 | 86 | func (x *Site) GetCreateTime() *timestamppb.Timestamp { 87 | if x != nil { 88 | return x.CreateTime 89 | } 90 | return nil 91 | } 92 | 93 | func (x *Site) GetUpdateTime() *timestamppb.Timestamp { 94 | if x != nil { 95 | return x.UpdateTime 96 | } 97 | return nil 98 | } 99 | 100 | func (x *Site) GetDeleteTime() *timestamppb.Timestamp { 101 | if x != nil { 102 | return x.DeleteTime 103 | } 104 | return nil 105 | } 106 | 107 | func (x *Site) GetDisplayName() string { 108 | if x != nil { 109 | return x.DisplayName 110 | } 111 | return "" 112 | } 113 | 114 | func (x *Site) GetLatLng() *latlng.LatLng { 115 | if x != nil { 116 | return x.LatLng 117 | } 118 | return nil 119 | } 120 | 121 | var File_einride_example_freight_v1_site_proto protoreflect.FileDescriptor 122 | 123 | var file_einride_example_freight_v1_site_proto_rawDesc = []byte{ 124 | 0x0a, 0x25, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 125 | 0x65, 0x2f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x69, 0x74, 126 | 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 127 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 128 | 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 129 | 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x2e, 0x70, 130 | 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 131 | 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 132 | 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 133 | 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 134 | 0x1a, 0x18, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x6c, 0x61, 135 | 0x74, 0x6c, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x90, 0x03, 0x0a, 0x04, 0x53, 136 | 0x69, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 137 | 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 138 | 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 139 | 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 140 | 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x03, 0x52, 0x0a, 141 | 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x75, 0x70, 142 | 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 143 | 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 144 | 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0xe2, 0x41, 0x01, 145 | 0x03, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 146 | 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 147 | 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 148 | 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 149 | 0xe2, 0x41, 0x01, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 150 | 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 151 | 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xe2, 0x41, 0x01, 0x02, 0x52, 0x0b, 0x64, 0x69, 152 | 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x6c, 0x61, 0x74, 153 | 0x5f, 0x6c, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x6f, 0x6f, 154 | 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x4c, 0x61, 0x74, 0x4c, 0x6e, 0x67, 0x52, 155 | 0x06, 0x6c, 0x61, 0x74, 0x4c, 0x6e, 0x67, 0x3a, 0x54, 0xea, 0x41, 0x51, 0x0a, 0x21, 0x66, 0x72, 156 | 0x65, 0x69, 0x67, 0x68, 0x74, 0x2d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x65, 0x69, 157 | 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x53, 0x69, 0x74, 0x65, 0x12, 158 | 0x1f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x73, 0x68, 0x69, 0x70, 0x70, 159 | 0x65, 0x72, 0x7d, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x73, 0x69, 0x74, 0x65, 0x7d, 160 | 0x2a, 0x05, 0x73, 0x69, 0x74, 0x65, 0x73, 0x32, 0x04, 0x73, 0x69, 0x74, 0x65, 0x42, 0x87, 0x02, 161 | 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x65, 0x78, 162 | 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x76, 0x31, 163 | 0x42, 0x09, 0x53, 0x69, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 164 | 0x6f, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x61, 165 | 0x69, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 166 | 0x6c, 0x65, 0x63, 0x74, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 167 | 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 168 | 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x76, 0x31, 0xa2, 0x02, 169 | 0x03, 0x45, 0x45, 0x46, 0xaa, 0x02, 0x1a, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x45, 170 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x56, 171 | 0x31, 0xca, 0x02, 0x1a, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x5c, 0x45, 0x78, 0x61, 0x6d, 172 | 0x70, 0x6c, 0x65, 0x5c, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5c, 0x56, 0x31, 0xe2, 0x02, 173 | 0x26, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x5c, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 174 | 0x5c, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 175 | 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1d, 0x45, 0x69, 0x6e, 0x72, 0x69, 0x64, 176 | 0x65, 0x3a, 0x3a, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3a, 0x3a, 0x46, 0x72, 0x65, 0x69, 177 | 0x67, 0x68, 0x74, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 178 | } 179 | 180 | var ( 181 | file_einride_example_freight_v1_site_proto_rawDescOnce sync.Once 182 | file_einride_example_freight_v1_site_proto_rawDescData = file_einride_example_freight_v1_site_proto_rawDesc 183 | ) 184 | 185 | func file_einride_example_freight_v1_site_proto_rawDescGZIP() []byte { 186 | file_einride_example_freight_v1_site_proto_rawDescOnce.Do(func() { 187 | file_einride_example_freight_v1_site_proto_rawDescData = protoimpl.X.CompressGZIP(file_einride_example_freight_v1_site_proto_rawDescData) 188 | }) 189 | return file_einride_example_freight_v1_site_proto_rawDescData 190 | } 191 | 192 | var file_einride_example_freight_v1_site_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 193 | var file_einride_example_freight_v1_site_proto_goTypes = []any{ 194 | (*Site)(nil), // 0: einride.example.freight.v1.Site 195 | (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp 196 | (*latlng.LatLng)(nil), // 2: google.type.LatLng 197 | } 198 | var file_einride_example_freight_v1_site_proto_depIdxs = []int32{ 199 | 1, // 0: einride.example.freight.v1.Site.create_time:type_name -> google.protobuf.Timestamp 200 | 1, // 1: einride.example.freight.v1.Site.update_time:type_name -> google.protobuf.Timestamp 201 | 1, // 2: einride.example.freight.v1.Site.delete_time:type_name -> google.protobuf.Timestamp 202 | 2, // 3: einride.example.freight.v1.Site.lat_lng:type_name -> google.type.LatLng 203 | 4, // [4:4] is the sub-list for method output_type 204 | 4, // [4:4] is the sub-list for method input_type 205 | 4, // [4:4] is the sub-list for extension type_name 206 | 4, // [4:4] is the sub-list for extension extendee 207 | 0, // [0:4] is the sub-list for field type_name 208 | } 209 | 210 | func init() { file_einride_example_freight_v1_site_proto_init() } 211 | func file_einride_example_freight_v1_site_proto_init() { 212 | if File_einride_example_freight_v1_site_proto != nil { 213 | return 214 | } 215 | type x struct{} 216 | out := protoimpl.TypeBuilder{ 217 | File: protoimpl.DescBuilder{ 218 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 219 | RawDescriptor: file_einride_example_freight_v1_site_proto_rawDesc, 220 | NumEnums: 0, 221 | NumMessages: 1, 222 | NumExtensions: 0, 223 | NumServices: 0, 224 | }, 225 | GoTypes: file_einride_example_freight_v1_site_proto_goTypes, 226 | DependencyIndexes: file_einride_example_freight_v1_site_proto_depIdxs, 227 | MessageInfos: file_einride_example_freight_v1_site_proto_msgTypes, 228 | }.Build() 229 | File_einride_example_freight_v1_site_proto = out.File 230 | file_einride_example_freight_v1_site_proto_rawDesc = nil 231 | file_einride_example_freight_v1_site_proto_goTypes = nil 232 | file_einride_example_freight_v1_site_proto_depIdxs = nil 233 | } 234 | -------------------------------------------------------------------------------- /cmd/examplectl/gen/root.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-aip-cli. DO NOT EDIT. 2 | package examplectl 3 | 4 | import ( 5 | cobra "github.com/spf13/cobra" 6 | aipcli "go.einride.tech/aip-cli/aipcli" 7 | v1 "go.einride.tech/aip-cli/cmd/examplectl/gen/einride/example/freight/v1" 8 | ) 9 | 10 | func NewModuleCommand(use string, short string, commands ...*cobra.Command) *cobra.Command { 11 | config := NewConfig() 12 | return aipcli.NewModuleCommand( 13 | use, 14 | short, 15 | config, 16 | append( 17 | []*cobra.Command{ 18 | v1.NewFreightServiceCommand(config), 19 | }, 20 | commands..., 21 | )..., 22 | ) 23 | } 24 | 25 | func NewConfig() aipcli.Config { 26 | return aipcli.Config{Hosts: map[string]string{}, DefaultHost: "", RootPath: "", Root: "examplectl", GoogleCloudIdentityTokens: true, CachedIdentityTokenPath: ""} 27 | } 28 | -------------------------------------------------------------------------------- /cmd/examplectl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "go.einride.tech/aip-cli/aipcli" 8 | examplectl "go.einride.tech/aip-cli/cmd/examplectl/gen" 9 | ) 10 | 11 | func main() { 12 | if err := examplectl.NewModuleCommand( 13 | "examplectl", 14 | "Example CLI tool", 15 | aipcli.NewIAMModuleCommand("iam", examplectl.NewConfig()), 16 | ).Execute(); err != nil { 17 | _, _ = fmt.Fprintln(os.Stderr, err) 18 | os.Exit(1) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cmd/protoc-gen-go-aip-cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/pflag" 5 | "go.einride.tech/aip-cli/internal/gengoaipcli" 6 | "google.golang.org/protobuf/compiler/protogen" 7 | ) 8 | 9 | func main() { 10 | flagSet := pflag.NewFlagSet("aip-cli", pflag.ContinueOnError) 11 | var config gengoaipcli.Config 12 | config.AddToFlagSet(flagSet) 13 | protogen.Options{ParamFunc: flagSet.Set}.Run(func(gen *protogen.Plugin) error { 14 | return gengoaipcli.Run(gen, config) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.einride.tech/aip-cli 2 | 3 | go 1.23 4 | 5 | require ( 6 | cloud.google.com/go/iam v1.2.1 7 | github.com/google/cel-go v0.21.0 8 | github.com/spf13/cobra v1.8.1 9 | github.com/spf13/pflag v1.0.5 10 | github.com/stoewer/go-strcase v1.3.0 11 | go.einride.tech/aip v0.68.0 12 | golang.org/x/oauth2 v0.23.0 13 | google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 14 | google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 15 | google.golang.org/grpc v1.67.1 16 | google.golang.org/protobuf v1.35.1 17 | gotest.tools/v3 v3.5.1 18 | ) 19 | 20 | require ( 21 | cloud.google.com/go/compute/metadata v0.5.0 // indirect 22 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 23 | github.com/google/go-cmp v0.6.0 // indirect 24 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 25 | golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect 26 | golang.org/x/net v0.33.0 // indirect 27 | golang.org/x/sys v0.28.0 // indirect 28 | golang.org/x/text v0.21.0 // indirect 29 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= 2 | cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= 3 | cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= 4 | cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= 5 | github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 6 | github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= 12 | github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= 13 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 14 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 15 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 16 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 20 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 21 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 22 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 23 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 24 | github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 25 | github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 26 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 27 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 28 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 29 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 30 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 31 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 32 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 33 | go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= 34 | go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= 35 | golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= 36 | golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 37 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 38 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 39 | golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= 40 | golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 41 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 42 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 43 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 44 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 45 | google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= 46 | google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= 47 | google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= 48 | google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= 49 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= 50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 51 | google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= 52 | google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 53 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 54 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 57 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 58 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 59 | gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= 60 | gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= 61 | -------------------------------------------------------------------------------- /internal/gengoaipcli/comments.go: -------------------------------------------------------------------------------- 1 | package gengoaipcli 2 | 3 | import "google.golang.org/protobuf/reflect/protoreflect" 4 | 5 | func collectDescriptorComments(comments map[protoreflect.FullName]string, desc protoreflect.Descriptor) { 6 | comments[desc.FullName()] = desc.ParentFile().SourceLocations().ByDescriptor(desc).LeadingComments 7 | } 8 | 9 | func collectMethodComments(comments map[protoreflect.FullName]string, method protoreflect.MethodDescriptor) { 10 | collectDescriptorComments(comments, method) 11 | collectMessageComments(comments, method.Input()) 12 | } 13 | 14 | func collectMessageComments(comments map[protoreflect.FullName]string, msg protoreflect.MessageDescriptor) { 15 | for i := 0; i < msg.Fields().Len(); i++ { 16 | field := msg.Fields().Get(i) 17 | collectDescriptorComments(comments, field) 18 | if field.Kind() == protoreflect.MessageKind { 19 | collectMessageComments(comments, field.Message()) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/gengoaipcli/config.go: -------------------------------------------------------------------------------- 1 | package gengoaipcli 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/spf13/pflag" 8 | "go.einride.tech/aip-cli/aipcli" 9 | ) 10 | 11 | // Config for the protoc-gen-go-aip-cli plugin. 12 | type Config aipcli.Config 13 | 14 | // AddToFlagSet adds the config to a pflag.FlagSet. 15 | func (c *Config) AddToFlagSet(flags *pflag.FlagSet) { 16 | c.Hosts = make(map[string]string) 17 | flags.Var(stringStringMap{value: c.Hosts}, "hosts", "mapping from alias to host") 18 | flags.StringVar(&c.DefaultHost, "default_host", "", "default host override") 19 | flags.StringVar(&c.Root, "root", "", "root command") 20 | flags.StringVar(&c.RootPath, "root_path", "", "where root will be located") 21 | flags.BoolVar(&c.GoogleCloudIdentityTokens, "gcloud_identity_tokens", false, "use gcloud to print identity tokens") 22 | flags.StringVar( 23 | &c.CachedIdentityTokenPath, 24 | "cached_identity_token_path", 25 | "", 26 | "a file relative to the local config folder that provides an IdentityToken used by the CLI", 27 | ) 28 | } 29 | 30 | // Validate the config. 31 | func (c *Config) Validate() error { 32 | if c.DefaultHost != "" && c.Hosts[c.DefaultHost] == "" { 33 | hosts := make([]string, 0, len(c.Hosts)) 34 | for host := range c.Hosts { 35 | hosts = append(hosts, host) 36 | } 37 | return fmt.Errorf("default_host (%s) must be one of hosts (%s)", c.DefaultHost, strings.Join(hosts, ",")) 38 | } 39 | 40 | if c.GoogleCloudIdentityTokens && c.CachedIdentityTokenPath != "" { 41 | return fmt.Errorf("gcloud_identity_tokens and cached_identity_token_path are mutually exclusive flags") 42 | } 43 | 44 | return nil 45 | } 46 | 47 | type stringStringMap struct { 48 | value map[string]string 49 | } 50 | 51 | func (m stringStringMap) Type() string { 52 | return "map" 53 | } 54 | 55 | func (m stringStringMap) String() string { 56 | return fmt.Sprintf("%v", m.value) 57 | } 58 | 59 | func (m stringStringMap) Set(s string) error { 60 | if m.value == nil { 61 | m.value = map[string]string{} 62 | } 63 | keyValue := strings.SplitN(s, "=", 2) 64 | if len(keyValue) != 2 { 65 | return fmt.Errorf("invalid pair: %s", s) 66 | } 67 | m.value[keyValue[0]] = keyValue[1] 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /internal/gengoaipcli/run.go: -------------------------------------------------------------------------------- 1 | package gengoaipcli 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "strings" 7 | 8 | "go.einride.tech/aip-cli/aipcli" 9 | "google.golang.org/protobuf/compiler/protogen" 10 | "google.golang.org/protobuf/reflect/protoregistry" 11 | ) 12 | 13 | const generateFilenameSuffix = "_cli.pb.go" 14 | 15 | func Run(gen *protogen.Plugin, config Config) error { 16 | var files protoregistry.Files 17 | for _, file := range gen.Files { 18 | if err := files.RegisterFile(file.Desc); err != nil { 19 | return err 20 | } 21 | } 22 | if err := config.Validate(); err != nil { 23 | return err 24 | } 25 | for _, f := range gen.Files { 26 | if f.Generate { 27 | if err := generateFile(gen, &files, f); err != nil { 28 | return err 29 | } 30 | } 31 | } 32 | if config.Root != "" { 33 | if err := generateRootModuleFile(gen, config); err != nil { 34 | return err 35 | } 36 | } 37 | return nil 38 | } 39 | 40 | func generateRootModuleFile(gen *protogen.Plugin, config Config) error { 41 | module, ok := getModuleParam(gen) 42 | if !ok { 43 | return fmt.Errorf("param root requires param module to be provided") 44 | } 45 | g := gen.NewGeneratedFile(path.Join(module, config.RootPath, "root.go"), "") 46 | generateGeneratedFileHeader(g) 47 | g.P("package ", config.Root) 48 | cobraCommand := g.QualifiedGoIdent(protogen.GoIdent{ 49 | GoImportPath: "github.com/spf13/cobra", 50 | GoName: "Command", 51 | }) 52 | cliConfig := g.QualifiedGoIdent(protogen.GoIdent{ 53 | GoImportPath: "go.einride.tech/aip-cli/aipcli", 54 | GoName: "Config", 55 | }) 56 | aipCLINewModuleCommand := g.QualifiedGoIdent(protogen.GoIdent{ 57 | GoImportPath: "go.einride.tech/aip-cli/aipcli", 58 | GoName: "NewModuleCommand", 59 | }) 60 | g.P() 61 | g.P("func NewModuleCommand(use string, short string, commands ...*", cobraCommand, ") *", cobraCommand, " {") 62 | g.P("config := NewConfig()") 63 | g.P("return ", aipCLINewModuleCommand, "(") 64 | g.P("use,") 65 | g.P("short,") 66 | g.P("config,") 67 | g.P("append(") 68 | g.P("[]*", cobraCommand, "{") 69 | for _, file := range gen.Files { 70 | if !file.Generate { 71 | continue 72 | } 73 | for _, service := range file.Services { 74 | newCommandFunction := g.QualifiedGoIdent(protogen.GoIdent{ 75 | GoImportPath: file.GoImportPath, 76 | GoName: "New" + service.GoName + "Command", 77 | }) 78 | g.P(newCommandFunction, "(config),") 79 | } 80 | } 81 | g.P("},") 82 | g.P("commands...,") 83 | g.P(")...,") 84 | g.P(")") 85 | g.P("}") 86 | g.P() 87 | g.P("func NewConfig() ", cliConfig, "{") 88 | g.P("return ", fmt.Sprintf("%#v", aipcli.Config(config))) 89 | g.P("}") 90 | return nil 91 | } 92 | 93 | func getModuleParam(gen *protogen.Plugin) (string, bool) { 94 | for _, param := range strings.Split(gen.Request.GetParameter(), ",") { 95 | var value string 96 | if i := strings.Index(param, "="); i >= 0 { 97 | value = param[i+1:] 98 | param = param[0:i] 99 | } 100 | if param == "module" { 101 | return value, true 102 | } 103 | } 104 | return "", false 105 | } 106 | 107 | func generateFile( 108 | gen *protogen.Plugin, 109 | files *protoregistry.Files, 110 | file *protogen.File, 111 | ) error { 112 | g := gen.NewGeneratedFile(file.GeneratedFilenamePrefix+generateFilenameSuffix, file.GoImportPath) 113 | g.Skip() 114 | generateGeneratedFileHeader(g) 115 | g.P("package ", file.GoPackageName) 116 | for _, service := range file.Services { 117 | g.Unskip() 118 | if err := (newServiceCommandCodeGenerator{ 119 | gen: gen, 120 | files: files, 121 | file: file, 122 | service: service, 123 | }.generateCode(g)); err != nil { 124 | return err 125 | } 126 | } 127 | return nil 128 | } 129 | 130 | func generateGeneratedFileHeader(g *protogen.GeneratedFile) { 131 | g.P() 132 | g.P("// Code generated by protoc-gen-go-aip-cli. DO NOT EDIT.") 133 | } 134 | -------------------------------------------------------------------------------- /internal/gengoaipcli/service.go: -------------------------------------------------------------------------------- 1 | package gengoaipcli 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | 7 | "google.golang.org/protobuf/compiler/protogen" 8 | "google.golang.org/protobuf/reflect/protoreflect" 9 | "google.golang.org/protobuf/reflect/protoregistry" 10 | ) 11 | 12 | type newServiceCommandCodeGenerator struct { 13 | gen *protogen.Plugin 14 | files *protoregistry.Files 15 | file *protogen.File 16 | service *protogen.Service 17 | } 18 | 19 | func (c newServiceCommandCodeGenerator) goName() string { 20 | return "New" + c.service.GoName + "Command" 21 | } 22 | 23 | func (c newServiceCommandCodeGenerator) generateCode(g *protogen.GeneratedFile) error { 24 | newServiceCommand := g.QualifiedGoIdent(protogen.GoIdent{ 25 | GoImportPath: "go.einride.tech/aip-cli/aipcli", 26 | GoName: "NewServiceCommand", 27 | }) 28 | newMethodCommand := g.QualifiedGoIdent(protogen.GoIdent{ 29 | GoImportPath: "go.einride.tech/aip-cli/aipcli", 30 | GoName: "NewMethodCommand", 31 | }) 32 | aipCLIConfig := g.QualifiedGoIdent(protogen.GoIdent{ 33 | GoImportPath: "go.einride.tech/aip-cli/aipcli", 34 | GoName: "Config", 35 | }) 36 | cobraCommand := g.QualifiedGoIdent(protogen.GoIdent{ 37 | GoImportPath: "github.com/spf13/cobra", 38 | GoName: "Command", 39 | }) 40 | protoreflectFullName := g.QualifiedGoIdent(protogen.GoIdent{ 41 | GoImportPath: "google.golang.org/protobuf/reflect/protoreflect", 42 | GoName: "FullName", 43 | }) 44 | g.P("func ", c.goName(), "(config ", aipCLIConfig, ") *", cobraCommand, " {") 45 | serviceComments := map[protoreflect.FullName]string{} 46 | collectDescriptorComments(serviceComments, c.service.Desc) 47 | g.P("return ", newServiceCommand, "(") 48 | g.P("config,") 49 | g.P(c.file.GoDescriptorIdent, ".") 50 | g.P("Services().ByName(\"", c.service.Desc.Name(), "\"),") 51 | g.P("map[", protoreflectFullName, "]string{") 52 | printCommentMapElements(g, serviceComments) 53 | g.P("},") 54 | for _, method := range c.service.Methods { 55 | methodComments := map[protoreflect.FullName]string{} 56 | collectMethodComments(methodComments, method.Desc) 57 | g.P(newMethodCommand, "(") 58 | g.P("config,") 59 | g.P(c.file.GoDescriptorIdent, ".") 60 | g.P("Services().ByName(\"", c.service.Desc.Name(), "\").Methods().ByName(\"", method.Desc.Name(), "\"),") 61 | g.P("&", method.Input.GoIdent, "{},") 62 | g.P("&", method.Output.GoIdent, "{},") 63 | g.P("map[", protoreflectFullName, "]string{") 64 | printCommentMapElements(g, methodComments) 65 | g.P("},") 66 | g.P("),") 67 | } 68 | g.P(")") 69 | g.P("}") 70 | return nil 71 | } 72 | 73 | func printCommentMapElements(g *protogen.GeneratedFile, comments map[protoreflect.FullName]string) { 74 | keys := make([]string, 0, len(comments)) 75 | for key := range comments { 76 | keys = append(keys, string(key)) 77 | } 78 | sort.Stable(sort.StringSlice(keys)) 79 | for _, key := range keys { 80 | g.P(strconv.Quote(key), ": ", strconv.Quote(comments[protoreflect.FullName(key)]), ",") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /internal/protoflag/duration.go: -------------------------------------------------------------------------------- 1 | package protovalue 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/spf13/pflag" 7 | "google.golang.org/protobuf/reflect/protoreflect" 8 | "google.golang.org/protobuf/types/known/durationpb" 9 | ) 10 | 11 | func Duration(mutable func() protoreflect.Message, field protoreflect.FieldDescriptor) pflag.Value { 12 | return durationValue{mutable: mutable, field: field} 13 | } 14 | 15 | type durationValue struct { 16 | mutable func() protoreflect.Message 17 | field protoreflect.FieldDescriptor 18 | } 19 | 20 | func (v durationValue) String() string { 21 | return "" 22 | } 23 | 24 | func (v durationValue) Type() string { 25 | return "duration" 26 | } 27 | 28 | func (v durationValue) Set(s string) error { 29 | duration, err := time.ParseDuration(s) 30 | if err != nil { 31 | return err 32 | } 33 | v.mutable().Set(v.field, protoreflect.ValueOf(durationpb.New(duration).ProtoReflect())) 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /internal/protoflag/enum.go: -------------------------------------------------------------------------------- 1 | package protovalue 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/pflag" 7 | "google.golang.org/protobuf/reflect/protoreflect" 8 | ) 9 | 10 | func Enum(mutable func() protoreflect.Message, field protoreflect.FieldDescriptor) pflag.Value { 11 | return enumValue{mutable: mutable, field: field} 12 | } 13 | 14 | type enumValue struct { 15 | mutable func() protoreflect.Message 16 | field protoreflect.FieldDescriptor 17 | } 18 | 19 | func (v enumValue) String() string { 20 | return "" 21 | } 22 | 23 | func (v enumValue) Type() string { 24 | return "enum[" + string(v.field.Enum().Name()) + "]" 25 | } 26 | 27 | func lookupEnum(field protoreflect.FieldDescriptor, name string) (protoreflect.EnumValueDescriptor, error) { 28 | value := field.Enum().Values().ByName(protoreflect.Name(name)) 29 | if value == nil { 30 | return nil, fmt.Errorf("no such value for %v: %v", field.Enum().Name(), name) 31 | } 32 | return value, nil 33 | } 34 | 35 | func (v enumValue) Set(s string) error { 36 | value, err := lookupEnum(v.field, s) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | v.mutable().Set(v.field, protoreflect.ValueOfEnum(value.Number())) 42 | return nil 43 | } 44 | 45 | func EnumList(mutable func() protoreflect.Message, field protoreflect.FieldDescriptor) pflag.Value { 46 | parser := func(s string) (protoreflect.EnumValueDescriptor, error) { 47 | return lookupEnum(field, s) 48 | } 49 | valueOf := func(v protoreflect.EnumValueDescriptor) protoreflect.Value { 50 | return protoreflect.ValueOfEnum(v.Number()) 51 | } 52 | return PrimitiveList[protoreflect.EnumValueDescriptor](mutable, field, valueOf, parser) 53 | } 54 | -------------------------------------------------------------------------------- /internal/protoflag/fieldmask.go: -------------------------------------------------------------------------------- 1 | package protovalue 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/pflag" 7 | "google.golang.org/protobuf/reflect/protoreflect" 8 | "google.golang.org/protobuf/types/known/fieldmaskpb" 9 | ) 10 | 11 | func FieldMask(mutable func() protoreflect.Message, field protoreflect.FieldDescriptor) pflag.Value { 12 | return fieldMaskValue{mutable: mutable, field: field} 13 | } 14 | 15 | type fieldMaskValue struct { 16 | mutable func() protoreflect.Message 17 | field protoreflect.FieldDescriptor 18 | } 19 | 20 | func (v fieldMaskValue) String() string { 21 | return "" 22 | } 23 | 24 | func (v fieldMaskValue) Type() string { 25 | return "field-mask" 26 | } 27 | 28 | func (v fieldMaskValue) Set(s string) error { 29 | values := strings.Split(s, ",") 30 | if len(values) == 0 { 31 | return nil 32 | } 33 | fieldMask := fieldmaskpb.FieldMask{ 34 | Paths: make([]string, 0, len(values)), 35 | } 36 | for _, value := range values { 37 | fieldMask.Paths = append(fieldMask.Paths, strings.TrimSpace(value)) 38 | } 39 | v.mutable().Set(v.field, protoreflect.ValueOf(fieldMask.ProtoReflect())) 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/protoflag/mapstringstring.go: -------------------------------------------------------------------------------- 1 | package protovalue 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/spf13/pflag" 8 | "google.golang.org/protobuf/reflect/protoreflect" 9 | ) 10 | 11 | func MapStringString(mutable func() protoreflect.Message, field protoreflect.FieldDescriptor) pflag.Value { 12 | return mapStringStringValue{mutable: mutable, field: field} 13 | } 14 | 15 | type mapStringStringValue struct { 16 | mutable func() protoreflect.Message 17 | field protoreflect.FieldDescriptor 18 | } 19 | 20 | func (v mapStringStringValue) String() string { 21 | return "" 22 | } 23 | 24 | func (v mapStringStringValue) Type() string { 25 | return "map" 26 | } 27 | 28 | func (v mapStringStringValue) Set(s string) error { 29 | pairs := strings.Split(s, ",") 30 | if len(pairs) == 0 { 31 | return nil 32 | } 33 | value := v.mutable().NewField(v.field) 34 | for _, pair := range pairs { 35 | keyValue := strings.SplitN(pair, "=", 2) 36 | if len(keyValue) != 2 { 37 | return fmt.Errorf("invalid map pair: %s", pair) 38 | } 39 | value.Map().Set(protoreflect.ValueOfString(keyValue[0]).MapKey(), protoreflect.ValueOfString(keyValue[1])) 40 | } 41 | v.mutable().Set(v.field, value) 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /internal/protoflag/primitive.go: -------------------------------------------------------------------------------- 1 | package protovalue 2 | 3 | import ( 4 | "github.com/spf13/pflag" 5 | "google.golang.org/protobuf/reflect/protoreflect" 6 | ) 7 | 8 | func Primitive[T any]( 9 | mutable func() protoreflect.Message, 10 | field protoreflect.FieldDescriptor, 11 | valueOf func(T) protoreflect.Value, 12 | parser func(string) (T, error), 13 | ) pflag.Value { 14 | return primitiveValue[T]{ 15 | mutable: mutable, 16 | field: field, 17 | valueOf: valueOf, 18 | parser: parser, 19 | } 20 | } 21 | 22 | type primitiveValue[T any] struct { 23 | mutable func() protoreflect.Message 24 | field protoreflect.FieldDescriptor 25 | valueOf func(T) protoreflect.Value 26 | parser func(string) (T, error) 27 | } 28 | 29 | func (v primitiveValue[T]) String() string { 30 | return "" 31 | } 32 | 33 | func (v primitiveValue[T]) Set(s string) error { 34 | parsed, err := v.parser(s) 35 | if err != nil { 36 | return err 37 | } 38 | v.mutable().Set(v.field, v.valueOf(parsed)) 39 | return nil 40 | } 41 | 42 | func (v primitiveValue[T]) Type() string { 43 | return v.field.Kind().String() 44 | } 45 | -------------------------------------------------------------------------------- /internal/protoflag/primitivelist.go: -------------------------------------------------------------------------------- 1 | package protovalue 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/pflag" 7 | "google.golang.org/protobuf/reflect/protoreflect" 8 | ) 9 | 10 | func PrimitiveList[T any]( 11 | mutable func() protoreflect.Message, 12 | field protoreflect.FieldDescriptor, 13 | valueOf func(T) protoreflect.Value, 14 | parser func(string) (T, error), 15 | ) pflag.Value { 16 | return primitiveListValue[T]{ 17 | mutable: mutable, 18 | field: field, 19 | parser: parser, 20 | valueOf: valueOf, 21 | } 22 | } 23 | 24 | type primitiveListValue[T any] struct { 25 | mutable func() protoreflect.Message 26 | field protoreflect.FieldDescriptor 27 | parser func(string) (T, error) 28 | valueOf func(T) protoreflect.Value 29 | } 30 | 31 | func (v primitiveListValue[T]) String() string { 32 | return "" 33 | } 34 | 35 | func (v primitiveListValue[T]) Set(s string) error { 36 | values := strings.Split(s, ",") 37 | if len(values) == 0 { 38 | return nil 39 | } 40 | msg := v.mutable() 41 | list := msg.NewField(v.field).List() 42 | for _, value := range values { 43 | parsed, err := v.parser(value) 44 | if err != nil { 45 | return err 46 | } 47 | list.Append(v.valueOf(parsed)) 48 | } 49 | msg.Set(v.field, protoreflect.ValueOfList(list)) 50 | return nil 51 | } 52 | 53 | func (v primitiveListValue[T]) Type() string { 54 | return "[" + v.field.Kind().String() + "]" 55 | } 56 | -------------------------------------------------------------------------------- /internal/protoflag/timestamp.go: -------------------------------------------------------------------------------- 1 | package protovalue 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/google/cel-go/cel" 9 | "github.com/google/cel-go/common/types" 10 | "github.com/google/cel-go/common/types/ref" 11 | "github.com/spf13/pflag" 12 | "google.golang.org/protobuf/reflect/protoreflect" 13 | "google.golang.org/protobuf/types/known/timestamppb" 14 | ) 15 | 16 | func Timestamp(mutable func() protoreflect.Message, field protoreflect.FieldDescriptor) pflag.Value { 17 | return timestampValue{mutable: mutable, field: field} 18 | } 19 | 20 | type timestampValue struct { 21 | mutable func() protoreflect.Message 22 | field protoreflect.FieldDescriptor 23 | } 24 | 25 | func (v timestampValue) String() string { 26 | return "" 27 | } 28 | 29 | func (v timestampValue) Type() string { 30 | return "timestamp" 31 | } 32 | 33 | func (v timestampValue) Set(s string) error { 34 | if strings.HasPrefix(s, "=") { 35 | return v.setExpression(strings.TrimPrefix(s, "=")) 36 | } 37 | timestamp, err := time.Parse(time.RFC3339, s) 38 | if err != nil { 39 | return err 40 | } 41 | v.mutable().Set(v.field, protoreflect.ValueOf(timestamppb.New(timestamp).ProtoReflect())) 42 | return nil 43 | } 44 | 45 | func (v timestampValue) setExpression(s string) error { 46 | env, err := cel.NewEnv( 47 | cel.Function( 48 | "now", 49 | cel.Overload( 50 | "now", 51 | nil, 52 | cel.TimestampType, 53 | cel.FunctionBinding(func(...ref.Val) ref.Val { 54 | return types.Timestamp{Time: time.Now()} 55 | }), 56 | ), 57 | ), 58 | ) 59 | if err != nil { 60 | return err 61 | } 62 | ast, issues := env.Compile(s) 63 | if issues.Err() != nil { 64 | return issues.Err() 65 | } 66 | if ast.OutputType() != cel.TimestampType { 67 | return fmt.Errorf("expected timestamp but got %v", ast.OutputType()) 68 | } 69 | program, err := env.Program(ast) 70 | if err != nil { 71 | return err 72 | } 73 | value, _, err := program.Eval(map[string]interface{}{}) 74 | if err != nil { 75 | return err 76 | } 77 | timestamp, ok := value.Value().(time.Time) 78 | if !ok { 79 | return fmt.Errorf("invalid timestamp") 80 | } 81 | v.mutable().Set(v.field, protoreflect.ValueOf(timestamppb.New(timestamp).ProtoReflect())) 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /internal/protoshell/enum.go: -------------------------------------------------------------------------------- 1 | package protoshell 2 | 3 | import ( 4 | "strings" 5 | 6 | "google.golang.org/protobuf/reflect/protoreflect" 7 | ) 8 | 9 | // CompleteEnumValue returns valid values to complete for the provided enum values. 10 | func CompleteEnumValue(toComplete string, values protoreflect.EnumValueDescriptors) []string { 11 | result := make([]string, 0, values.Len()) 12 | for i := 0; i < values.Len(); i++ { 13 | value := string(values.Get(i).Name()) 14 | if strings.HasPrefix(value, toComplete) { 15 | result = append(result, value) 16 | } 17 | } 18 | if len(result) == 0 { 19 | return nil 20 | } 21 | return result 22 | } 23 | -------------------------------------------------------------------------------- /internal/protoshell/enum_test.go: -------------------------------------------------------------------------------- 1 | package protoshell 2 | 3 | import ( 4 | "testing" 5 | 6 | ltype "google.golang.org/genproto/googleapis/logging/type" 7 | "google.golang.org/protobuf/reflect/protoreflect" 8 | "gotest.tools/v3/assert" 9 | ) 10 | 11 | func TestCompleteEnum(t *testing.T) { 12 | for _, tt := range []struct { 13 | name string 14 | toComplete string 15 | enum protoreflect.EnumValueDescriptors 16 | expected []string 17 | }{ 18 | { 19 | name: "empty", 20 | toComplete: "", 21 | enum: ltype.LogSeverity_DEFAULT.Descriptor().Values(), 22 | expected: []string{ 23 | "DEFAULT", "DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "CRITICAL", "ALERT", "EMERGENCY", 24 | }, 25 | }, 26 | 27 | { 28 | name: "prefix match", 29 | toComplete: "E", 30 | enum: ltype.LogSeverity_DEFAULT.Descriptor().Values(), 31 | expected: []string{"ERROR", "EMERGENCY"}, 32 | }, 33 | 34 | { 35 | name: "no match", 36 | toComplete: "Z", 37 | enum: ltype.LogSeverity_DEFAULT.Descriptor().Values(), 38 | expected: nil, 39 | }, 40 | 41 | { 42 | name: "exact match", 43 | toComplete: "DEFAULT", 44 | enum: ltype.LogSeverity_DEFAULT.Descriptor().Values(), 45 | expected: []string{"DEFAULT"}, 46 | }, 47 | } { 48 | t.Run(tt.name, func(t *testing.T) { 49 | actual := CompleteEnumValue(tt.toComplete, tt.enum) 50 | assert.DeepEqual(t, tt.expected, actual) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/protoshell/resourcename.go: -------------------------------------------------------------------------------- 1 | package protoshell 2 | 3 | import ( 4 | "strings" 5 | 6 | "go.einride.tech/aip/resourcename" 7 | ) 8 | 9 | // CompleteResourceName returns a value to complete for the provided resource name pattern. 10 | func CompleteResourceName(toComplete, pattern string) (string, bool) { 11 | toCompleteSegments := strings.Split(toComplete, "/") 12 | patternSegments := strings.Split(pattern, "/") 13 | if len(toCompleteSegments) > len(patternSegments) { 14 | return "", false 15 | } 16 | var result strings.Builder 17 | result.Grow(len(pattern)) 18 | for i, toCompleteSegment := range toCompleteSegments { 19 | patternSegment := patternSegments[i] 20 | if resourcename.Segment(patternSegment).IsVariable() { 21 | result.WriteString(toCompleteSegment) 22 | if i < len(toCompleteSegments)-1 { 23 | result.WriteByte('/') 24 | } 25 | continue 26 | } 27 | if toCompleteSegment == patternSegment { 28 | result.WriteString(patternSegment) 29 | if i < len(toCompleteSegments)-1 || i < len(patternSegments)-1 { 30 | result.WriteByte('/') 31 | } 32 | continue 33 | } 34 | if i < len(toCompleteSegments)-1 { 35 | return "", false 36 | } 37 | if !strings.HasPrefix(patternSegment, toCompleteSegment) { 38 | return "", false 39 | } 40 | result.WriteString(patternSegment) 41 | if i < len(patternSegments)-1 { 42 | result.WriteByte('/') 43 | } 44 | } 45 | return result.String(), result.String() != "" 46 | } 47 | -------------------------------------------------------------------------------- /internal/protoshell/resourcename_test.go: -------------------------------------------------------------------------------- 1 | package protoshell 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/v3/assert" 7 | ) 8 | 9 | func TestCompleteResourceName(t *testing.T) { 10 | for _, tt := range []struct { 11 | name string 12 | toComplete string 13 | pattern string 14 | completion string 15 | ok bool 16 | }{ 17 | { 18 | name: "mismatch", 19 | toComplete: "user", 20 | pattern: "shippers/{shipper}/shipments/{shipment}", 21 | completion: "", 22 | ok: false, 23 | }, 24 | 25 | { 26 | name: "empty input", 27 | toComplete: "", 28 | pattern: "shippers/{shipper}/shipments/{shipment}", 29 | completion: "shippers/", 30 | ok: true, 31 | }, 32 | 33 | { 34 | name: "partial segment with singleton pattern", 35 | toComplete: "ship", 36 | pattern: "shippers", 37 | completion: "shippers", 38 | ok: true, 39 | }, 40 | 41 | { 42 | name: "empty input with singleton pattern", 43 | toComplete: "", 44 | pattern: "shippers", 45 | completion: "shippers", 46 | ok: true, 47 | }, 48 | 49 | { 50 | name: "empty input with variable pattern", 51 | toComplete: "", 52 | pattern: "{shipper}", 53 | completion: "", 54 | ok: false, 55 | }, 56 | 57 | { 58 | name: "resource ID input with variable pattern", 59 | toComplete: "foo", 60 | pattern: "{shipper}", 61 | completion: "foo", 62 | ok: true, 63 | }, 64 | 65 | { 66 | name: "partial segment", 67 | toComplete: "shi", 68 | pattern: "shippers/{shipper}/shipments/{shipment}", 69 | completion: "shippers/", 70 | ok: true, 71 | }, 72 | 73 | { 74 | name: "full segment", 75 | toComplete: "shippers", 76 | pattern: "shippers/{shipper}/shipments/{shipment}", 77 | completion: "shippers/", 78 | ok: true, 79 | }, 80 | 81 | { 82 | name: "full segment with slash", 83 | toComplete: "shippers/", 84 | pattern: "shippers/{shipper}/shipments/{shipment}", 85 | completion: "shippers/", 86 | ok: true, 87 | }, 88 | 89 | { 90 | name: "partial resource ID", 91 | toComplete: "shippers/test", 92 | pattern: "shippers/{shipper}/shipments/{shipment}", 93 | completion: "shippers/test", 94 | ok: true, 95 | }, 96 | 97 | { 98 | name: "full resource ID", 99 | toComplete: "shippers/test/", 100 | pattern: "shippers/{shipper}/shipments/{shipment}", 101 | completion: "shippers/test/shipments/", 102 | ok: true, 103 | }, 104 | 105 | { 106 | name: "partial second collection", 107 | toComplete: "shippers/foo/ship", 108 | pattern: "shippers/{shipper}/shipments/{shipment}", 109 | completion: "shippers/foo/shipments/", 110 | ok: true, 111 | }, 112 | 113 | { 114 | name: "second collection mismatch", 115 | toComplete: "shippers/foo/sit", 116 | pattern: "shippers/{shipper}/shipments/{shipment}", 117 | completion: "", 118 | ok: false, 119 | }, 120 | 121 | { 122 | name: "partial second resource ID", 123 | toComplete: "shippers/foo/shipments/bar", 124 | pattern: "shippers/{shipper}/shipments/{shipment}", 125 | completion: "shippers/foo/shipments/bar", 126 | ok: true, 127 | }, 128 | } { 129 | t.Run(tt.name, func(t *testing.T) { 130 | actual, ok := CompleteResourceName(tt.toComplete, tt.pattern) 131 | assert.Equal(t, tt.ok, ok) 132 | assert.Equal(t, tt.completion, actual) 133 | }) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /internal/protoversion/compare.go: -------------------------------------------------------------------------------- 1 | package protoversion 2 | 3 | // Compare returns an integer comparing two versions. 4 | // The result will be 0 if a == b, -1 if a < b, and +1 if a > b. 5 | func Compare(a, b Version) int { 6 | switch { 7 | case a.Major < b.Major: 8 | return -1 9 | case a.Major > b.Major: 10 | return 1 11 | case a.Stability > b.Stability: 12 | return -1 13 | case a.Stability < b.Stability: 14 | return 1 15 | case a.Minor < b.Minor: 16 | return -1 17 | case a.Minor > b.Minor: 18 | return 1 19 | default: 20 | return 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/protoversion/compare_test.go: -------------------------------------------------------------------------------- 1 | package protoversion 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/v3/assert" 7 | ) 8 | 9 | func TestCompare(t *testing.T) { 10 | for _, tt := range []struct { 11 | name string 12 | a, b Version 13 | expected int 14 | }{ 15 | { 16 | name: "identical", 17 | a: Version{Major: 2, Stability: Beta, Minor: 2}, 18 | b: Version{Major: 2, Stability: Beta, Minor: 2}, 19 | expected: 0, 20 | }, 21 | 22 | { 23 | name: "lower minor", 24 | a: Version{Major: 2, Stability: Beta, Minor: 1}, 25 | b: Version{Major: 2, Stability: Beta, Minor: 2}, 26 | expected: -1, 27 | }, 28 | 29 | { 30 | name: "higher minor", 31 | a: Version{Major: 2, Stability: Beta, Minor: 2}, 32 | b: Version{Major: 2, Stability: Beta, Minor: 1}, 33 | expected: 1, 34 | }, 35 | 36 | { 37 | name: "lower stability", 38 | a: Version{Major: 2, Stability: Alpha, Minor: 2}, 39 | b: Version{Major: 2, Stability: Beta, Minor: 2}, 40 | expected: -1, 41 | }, 42 | 43 | { 44 | name: "higher stability", 45 | a: Version{Major: 2, Stability: Beta, Minor: 2}, 46 | b: Version{Major: 2, Stability: Alpha, Minor: 2}, 47 | expected: 1, 48 | }, 49 | 50 | { 51 | name: "lower minor higher stability", 52 | a: Version{Major: 2, Stability: Beta, Minor: 1}, 53 | b: Version{Major: 2, Stability: Alpha, Minor: 2}, 54 | expected: 1, 55 | }, 56 | 57 | { 58 | name: "stable vs alpha", 59 | a: Version{Major: 1}, 60 | b: Version{Major: 1, Stability: Beta, Minor: 1}, 61 | expected: 1, 62 | }, 63 | } { 64 | t.Run(tt.name, func(t *testing.T) { 65 | actual := Compare(tt.a, tt.b) 66 | assert.Equal(t, tt.expected, actual) 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /internal/protoversion/stabilitylevel.go: -------------------------------------------------------------------------------- 1 | package protoversion 2 | 3 | // StabilityLevel represents an AIP stability level. 4 | // See: https://google.aip.dev/181 5 | type StabilityLevel int 6 | 7 | const ( 8 | // A Stable component must be fully-supported over the lifetime of the major API version. 9 | Stable StabilityLevel = iota 10 | 11 | // A Beta component must be considered complete and ready to be declared stable, subject to public testing. 12 | Beta 13 | 14 | // An Alpha component undergoes rapid iteration with a known set of users who must be tolerant of change. 15 | Alpha 16 | ) 17 | -------------------------------------------------------------------------------- /internal/protoversion/version.go: -------------------------------------------------------------------------------- 1 | package protoversion 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Version represents a protobuf package version. 10 | type Version struct { 11 | // Major version of the package. 12 | Major int 13 | // Stability of the package. 14 | Stability StabilityLevel 15 | // Minor version of the package. 16 | Minor int 17 | } 18 | 19 | // Parse a version string. 20 | func Parse(s string) (Version, error) { 21 | if !strings.HasPrefix(s, "v") { 22 | return Version{}, fmt.Errorf("missing prefix 'v'") 23 | } 24 | withoutV := strings.TrimPrefix(s, "v") 25 | if strings.HasPrefix(withoutV, "0") { 26 | return Version{}, fmt.Errorf("version must not start with 0") 27 | } 28 | withoutMajor := strings.TrimLeft(withoutV, "0123456789") 29 | majorStr := withoutV[:len(withoutV)-len(withoutMajor)] 30 | major, err := strconv.Atoi(majorStr) 31 | if err != nil { 32 | return Version{}, fmt.Errorf("invalid major version: %w", err) 33 | } 34 | if len(withoutMajor) == 0 { 35 | return Version{Major: major}, nil 36 | } 37 | var withoutStabilityLevel string 38 | stabilityLevel := Stable 39 | switch { 40 | case strings.HasPrefix(withoutMajor, "alpha"): 41 | stabilityLevel = Alpha 42 | withoutStabilityLevel = strings.TrimPrefix(withoutMajor, "alpha") 43 | case strings.HasPrefix(withoutMajor, "beta"): 44 | stabilityLevel = Beta 45 | withoutStabilityLevel = strings.TrimPrefix(withoutMajor, "beta") 46 | } 47 | if len(withoutStabilityLevel) == 0 { 48 | return Version{Major: major, Stability: stabilityLevel}, nil 49 | } 50 | minor, err := strconv.Atoi(withoutStabilityLevel) 51 | if err != nil { 52 | return Version{}, fmt.Errorf("invalid minor version: %w", err) 53 | } 54 | return Version{ 55 | Major: major, 56 | Stability: stabilityLevel, 57 | Minor: minor, 58 | }, nil 59 | } 60 | -------------------------------------------------------------------------------- /internal/protoversion/version_test.go: -------------------------------------------------------------------------------- 1 | package protoversion 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/v3/assert" 7 | ) 8 | 9 | func TestParse(t *testing.T) { 10 | for _, tt := range []struct { 11 | version string 12 | expected Version 13 | errorContains string 14 | }{ 15 | { 16 | version: "v1", 17 | expected: Version{Major: 1}, 18 | }, 19 | 20 | { 21 | version: "v2", 22 | expected: Version{Major: 2, Stability: Stable}, 23 | }, 24 | 25 | { 26 | version: "v1beta1", 27 | expected: Version{Major: 1, Stability: Beta, Minor: 1}, 28 | }, 29 | 30 | { 31 | version: "v1beta2", 32 | expected: Version{Major: 1, Stability: Beta, Minor: 2}, 33 | }, 34 | 35 | { 36 | version: "v2alpha3", 37 | expected: Version{Major: 2, Stability: Alpha, Minor: 3}, 38 | }, 39 | 40 | { 41 | version: "foo", 42 | errorContains: "missing prefix 'v'", 43 | }, 44 | } { 45 | t.Run(tt.version, func(t *testing.T) { 46 | actual, err := Parse(tt.version) 47 | if tt.errorContains != "" { 48 | assert.ErrorContains(t, err, tt.errorContains) 49 | } else { 50 | assert.Equal(t, tt.expected, actual) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /proto/.gitignore: -------------------------------------------------------------------------------- 1 | tools/*/*/ 2 | build/ 3 | -------------------------------------------------------------------------------- /proto/Makefile: -------------------------------------------------------------------------------- 1 | # Code generated by go.einride.tech/sage. DO NOT EDIT. 2 | # To learn more, see ../.sage/main.go and https://github.com/einride/sage. 3 | 4 | .DEFAULT_GOAL := all 5 | 6 | cwd := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) 7 | sagefile := $(abspath $(cwd)/../.sage/bin/sagefile) 8 | 9 | # Setup Go. 10 | go := $(shell command -v go 2>/dev/null) 11 | export GOWORK ?= off 12 | ifndef go 13 | SAGE_GO_VERSION ?= 1.23.4 14 | export GOROOT := $(abspath $(cwd)/../.sage/tools/go/$(SAGE_GO_VERSION)/go) 15 | export PATH := $(PATH):$(GOROOT)/bin 16 | go := $(GOROOT)/bin/go 17 | os := $(shell uname | tr '[:upper:]' '[:lower:]') 18 | arch := $(shell uname -m) 19 | ifeq ($(arch),x86_64) 20 | arch := amd64 21 | endif 22 | $(go): 23 | $(info installing Go $(SAGE_GO_VERSION)...) 24 | @mkdir -p $(dir $(GOROOT)) 25 | @curl -sSL https://go.dev/dl/go$(SAGE_GO_VERSION).$(os)-$(arch).tar.gz | tar xz -C $(dir $(GOROOT)) 26 | @touch $(GOROOT)/go.mod 27 | @chmod +x $(go) 28 | endif 29 | 30 | .PHONY: $(sagefile) 31 | $(sagefile): $(go) 32 | @cd ../.sage && $(go) mod tidy && $(go) run . 33 | 34 | .PHONY: sage 35 | sage: 36 | @$(MAKE) $(sagefile) 37 | 38 | .PHONY: update-sage 39 | update-sage: $(go) 40 | @cd ../.sage && $(go) get -d go.einride.tech/sage@latest && $(go) mod tidy && $(go) run . 41 | 42 | .PHONY: clean-sage 43 | clean-sage: 44 | @git clean -fdx ../.sage/tools ../.sage/bin ../.sage/build 45 | 46 | .PHONY: all 47 | all: $(sagefile) 48 | @$(sagefile) Proto:All 49 | 50 | .PHONY: buf-format 51 | buf-format: $(sagefile) 52 | @$(sagefile) Proto:BufFormat 53 | 54 | .PHONY: buf-generate-example 55 | buf-generate-example: $(sagefile) 56 | @$(sagefile) Proto:BufGenerateExample 57 | 58 | .PHONY: buf-lint 59 | buf-lint: $(sagefile) 60 | @$(sagefile) Proto:BufLint 61 | 62 | .PHONY: buf-push 63 | buf-push: $(sagefile) 64 | @$(sagefile) Proto:BufPush 65 | 66 | .PHONY: clean-generated-proto 67 | clean-generated-proto: $(sagefile) 68 | @$(sagefile) Proto:CleanGeneratedProto 69 | 70 | .PHONY: protoc-gen-go 71 | protoc-gen-go: $(sagefile) 72 | @$(sagefile) Proto:ProtocGenGo 73 | 74 | .PHONY: protoc-gen-go-aipcli 75 | protoc-gen-go-aipcli: $(sagefile) 76 | @$(sagefile) Proto:ProtocGenGoAIPCLI 77 | -------------------------------------------------------------------------------- /proto/buf.gen.example.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | 3 | managed: 4 | enabled: true 5 | go_package_prefix: 6 | default: go.einride.tech/aip-cli/cmd/examplectl/gen 7 | except: 8 | - buf.build/googleapis/googleapis 9 | 10 | plugins: 11 | # The CLI generator requires the stubs generated by protoc-gen-go. 12 | - name: go 13 | out: cmd/examplectl/gen 14 | opt: module=go.einride.tech/aip-cli/cmd/examplectl/gen 15 | 16 | # The CLI generator optionally generates a root command and a main file 17 | # to the root of the output module. 18 | - name: go-aip-cli 19 | out: cmd/examplectl/gen 20 | strategy: all 21 | opt: 22 | - module=go.einride.tech/aip-cli/cmd/examplectl/gen 23 | - root=examplectl 24 | - gcloud_identity_tokens=true 25 | -------------------------------------------------------------------------------- /proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: einride 6 | repository: aip 7 | commit: 70af12bcc1644bc7b89c38abbef20e7b 8 | digest: shake256:dac4e760ae58058e0f58ddcb151d35657c9af9515dfb7831cfefa2288d397d1f5c798372b54c436ac681cdb07146e21233db75d832675fe9dbd83900e2647abb 9 | - remote: buf.build 10 | owner: einride 11 | repository: googleapis 12 | commit: 625fcaf6adb343be8ae8ef0ee7be7b77 13 | digest: shake256:5ffe3fd4047cb5e910f552f5b2e47f05360052ba6d25bee9179d03c2a47421c98cd642c3d28d0da62a6b2787b4ac035de39916086492b1cb3ce827efc971362c 14 | - remote: buf.build 15 | owner: googleapis 16 | repository: googleapis 17 | commit: 62f35d8aed1149c291d606d958a7ce32 18 | digest: shake256:c5f5c2401cf70b7c9719834954f31000a978397fdfebda861419bb4ab90fa8efae92710fddab0820533908a1e25ed692a8e119432b7b260c895087a4975b32f3 19 | -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | 3 | name: buf.build/einride/aip-cli 4 | 5 | deps: 6 | - buf.build/einride/aip 7 | 8 | lint: 9 | use: 10 | - DEFAULT 11 | -------------------------------------------------------------------------------- /proto/einride/aipcli/v1/values.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package einride.aipcli.v1; 4 | 5 | import "google/protobuf/duration.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | // Values for proto flag conversion. 9 | message Values { 10 | // Repeated string. 11 | repeated string repeated_string = 1; 12 | // Repeated string. 13 | repeated bool repeated_bool = 2; 14 | // Repeated int64. 15 | repeated int64 repeated_int64 = 3; 16 | // Repeated int32. 17 | repeated int32 repeated_int32 = 4; 18 | // Repeated double. 19 | repeated double repeated_double = 5; 20 | // Repeated float. 21 | repeated float repeated_float = 6; 22 | // Repeated timestamp. 23 | repeated google.protobuf.Timestamp repeated_times = 7; 24 | // Repeated duration. 25 | repeated google.protobuf.Duration repeated_durations = 8; 26 | // Map from string to string. 27 | map map_string_string = 9; 28 | // Map from string to int64. 29 | map map_string_int64 = 10; 30 | 31 | enum Enum { 32 | ENUM_UNSPECIFIED = 0; 33 | ENUM_ONE = 1; 34 | ENUM_TWO = 2; 35 | } 36 | // Repeated enum. 37 | repeated Enum repeated_enum = 11; 38 | } 39 | --------------------------------------------------------------------------------