From 4b8fbeffb6fecc14142e26739328ec07c35cc6b5 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Wed, 2 Aug 2023 11:28:43 +0300 Subject: [PATCH 1/2] Bootstrap repo, NexusService.StartOperation --- .github/workflows/ci.yml | 46 +++ .gitignore | 1 + LICENSE.md | 23 ++ README.md | 33 ++ api-linter.yaml | 11 + buf.yaml | 9 + cmd/install_deps.go | 58 +++ cmd/lint.go | 56 +++ cmd/logging.go | 47 +++ cmd/main.go | 20 + cmd/protos.go | 25 ++ cmd/test.go | 72 ++++ go.mod | 16 + go.sum | 22 + google/api/annotations.proto | 31 ++ google/api/field_behavior.proto | 90 +++++ google/api/http.proto | 379 ++++++++++++++++++ nexus/rpc/api/common/v1/common.proto | 13 + .../nexusservice/v1/request_response.proto | 65 +++ nexus/rpc/api/nexusservice/v1/service.proto | 22 + nexus/rpc/api/result/v1/result.proto | 42 ++ 21 files changed, 1081 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 api-linter.yaml create mode 100644 buf.yaml create mode 100644 cmd/install_deps.go create mode 100644 cmd/lint.go create mode 100644 cmd/logging.go create mode 100644 cmd/main.go create mode 100644 cmd/protos.go create mode 100644 cmd/test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 google/api/annotations.proto create mode 100644 google/api/field_behavior.proto create mode 100644 google/api/http.proto create mode 100644 nexus/rpc/api/common/v1/common.proto create mode 100644 nexus/rpc/api/nexusservice/v1/request_response.proto create mode 100644 nexus/rpc/api/nexusservice/v1/service.proto create mode 100644 nexus/rpc/api/result/v1/result.proto diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..795f73e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: Continuous Integration + +on: # rebuild any PRs and main branch changes + pull_request: + push: + branches: + - main + - "releases/*" + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + lint-and-test: + strategy: + fail-fast: false + matrix: + # Verify scripts run on any OS + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + # TODO: GH cache got messed up, need to figure out how to bump this version + # with: + # go-version: 1.20 + - name: Install protoc + uses: arduino/setup-protoc@v2 + with: + version: "23.x" + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Build CLI + run: go build -o ${{ startsWith(matrix.os, 'windows-') && 'scripts.exe' || './scripts' }} ./cmd + - name: Download tool dependencies + run: ${{ env.SCRIPTS }} install-deps + env: + SCRIPTS: ${{ startsWith(matrix.os, 'windows-') && 'start scripts.exe' || './scripts' }} + - name: Lint + run: ${{ env.SCRIPTS }} lint + env: + SCRIPTS: ${{ startsWith(matrix.os, 'windows-') && 'start scripts.exe' || './scripts' }} + - name: Test + run: ${{ env.SCRIPTS }} test + env: + SCRIPTS: ${{ startsWith(matrix.os, 'windows-') && 'start scripts.exe' || './scripts' }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b744996 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +scripts diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d160662 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,23 @@ +Nexus API + +MIT License + +Copyright (c) 2023 Temporal Technologies Inc. All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7934e9 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Nexus API + +Protocol Buffers API definitions for Nexus RPCs. + +### Prerequisites + +- [go](https://go.dev/doc/install) +- [protoc](https://grpc.io/docs/protoc-installation/) + +### Install lint and test tool dependencies + +```shell +go run ./cmd install-deps +``` + +### Lint + +Lint with: + +- [buf](https://buf.build/docs/installation) +- [api-linter](https://github.com/googleapis/api-linter) + +```shell +go run ./cmd lint +``` + +### Test compilation + +Test protoc compilation for select languages. + +```shell +go run ./cmd test +``` diff --git a/api-linter.yaml b/api-linter.yaml new file mode 100644 index 0000000..6c15d0a --- /dev/null +++ b/api-linter.yaml @@ -0,0 +1,11 @@ +- included_paths: + - "**/*.proto" + disabled_rules: + - "core::0192::has-comments" + - "core::0127::resource-name-extraction" + - "core::0136::http-uri-suffix" + +- included_paths: + - "dependencies/gogoproto/gogo.proto" + disabled_rules: + - "all" diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..9595102 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +version: v1 +breaking: + use: + - WIRE_JSON +lint: + use: + - DEFAULT + ignore: + - google diff --git a/cmd/install_deps.go b/cmd/install_deps.go new file mode 100644 index 0000000..6bfa4f9 --- /dev/null +++ b/cmd/install_deps.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "os" + "os/exec" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "go.uber.org/zap" +) + +var deps = []string{ + "github.com/bufbuild/buf/cmd/buf@v1.25.1", + "github.com/googleapis/api-linter/cmd/api-linter@v1.55.2", + "google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0", +} + +func installDepsCmd() *cobra.Command { + var i depsInstaller + cmd := &cobra.Command{ + Use: "install-deps", + Short: "Install tool dependencies", + Run: func(cmd *cobra.Command, args []string) { + if err := i.installDeps(cmd.Context()); err != nil { + i.logger.Fatal(err) + } + }, + } + i.addCLIFlags(cmd.Flags()) + return cmd +} + +type depsInstaller struct { + logger *zap.SugaredLogger + loggingOptions LoggingOptions +} + +func (l *depsInstaller) addCLIFlags(fs *pflag.FlagSet) { + l.loggingOptions.AddCLIFlags(fs) +} + +func (l *depsInstaller) installDeps(ctx context.Context) error { + l.logger = l.loggingOptions.MustCreateLogger() + + for _, dep := range deps { + args := []string{"install", dep} + + l.logger.Infow("Installing dependency", "dep", dep) + cmd := exec.CommandContext(ctx, "go", args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return err + } + } + return nil +} diff --git a/cmd/lint.go b/cmd/lint.go new file mode 100644 index 0000000..0a35163 --- /dev/null +++ b/cmd/lint.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "os" + "os/exec" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "go.uber.org/zap" +) + +func lintCmd() *cobra.Command { + var l linter + cmd := &cobra.Command{ + Use: "lint", + Short: "Lint proto files", + Run: func(cmd *cobra.Command, args []string) { + if err := l.lint(cmd.Context()); err != nil { + l.logger.Fatal(err) + } + }, + } + l.addCLIFlags(cmd.Flags()) + return cmd +} + +type linter struct { + logger *zap.SugaredLogger + loggingOptions LoggingOptions +} + +func (l *linter) addCLIFlags(fs *pflag.FlagSet) { + l.loggingOptions.AddCLIFlags(fs) +} + +func (l *linter) lint(ctx context.Context) error { + l.logger = l.loggingOptions.MustCreateLogger() + protoFiles, err := findProtos() + if err != nil { + return err + } + args := append([]string{"--set-exit-status", "--config", "./api-linter.yaml", "-I", "."}, protoFiles...) + l.logger.Infow("Running api-linter", "args", args) + cmd := exec.CommandContext(ctx, "api-linter", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return err + } + l.logger.Infow("Running buf lint") + cmd = exec.CommandContext(ctx, "buf", "lint") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/cmd/logging.go b/cmd/logging.go new file mode 100644 index 0000000..ed25ff1 --- /dev/null +++ b/cmd/logging.go @@ -0,0 +1,47 @@ +package main + +import ( + "log" + "os" + + "github.com/spf13/pflag" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// LoggingOptions for setting up the logger component +type LoggingOptions struct { + // Log level + LogLevel string + // Log encoding (console json) + LogEncoding string +} + +// BackupLogger is used in case we can't instantiate zap (it's nicer DX than panicking or using built-in `log`). +var BackupLogger = log.New(os.Stderr, "", 0) + +// MustCreateLogger sets up a zap logger or panics on error. +func (l *LoggingOptions) MustCreateLogger() *zap.SugaredLogger { + level, err := zap.ParseAtomicLevel(l.LogLevel) + if err != nil { + BackupLogger.Fatalf("Invalid log level: %v", err) + } + logger, err := zap.Config{ + Level: level, + Encoding: l.LogEncoding, + EncoderConfig: zap.NewDevelopmentEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + }.Build(zap.AddStacktrace(zapcore.FatalLevel)) + if err != nil { + BackupLogger.Fatalf("Failed to initialize logger: %v", err) + } + + return logger.Sugar() +} + +// AddCLIFlags adds the relevant flags to populate the options struct. +func (l *LoggingOptions) AddCLIFlags(fs *pflag.FlagSet) { + fs.StringVar(&l.LogLevel, "log-level", "info", "(debug info warn error panic fatal)") + fs.StringVar(&l.LogEncoding, "log-encoding", "console", "(console json)") +} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..f4611d6 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +func main() { + var rootCmd = &cobra.Command{ + Use: "api", + Short: "Scripts for the Nexus API repo", + } + + rootCmd.AddCommand(lintCmd()) + rootCmd.AddCommand(testCmd()) + rootCmd.AddCommand(installDepsCmd()) + + if err := rootCmd.Execute(); err != nil { + BackupLogger.Fatalf("Failed to run %s", err) + } +} diff --git a/cmd/protos.go b/cmd/protos.go new file mode 100644 index 0000000..4f801e7 --- /dev/null +++ b/cmd/protos.go @@ -0,0 +1,25 @@ +package main + +import ( + "os" + "path/filepath" + "runtime" +) + +func projectRoot() string { + _, filename, _, _ := runtime.Caller(0) + return filepath.Dir(filepath.Dir(filename)) +} + +func findProtos() ([]string, error) { + dir := filepath.Join(projectRoot(), "nexus") + files := []string{} + err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if filepath.Ext(path) == ".proto" { + files = append(files, path) + } + return nil + }) + + return files, err +} diff --git a/cmd/test.go b/cmd/test.go new file mode 100644 index 0000000..742f323 --- /dev/null +++ b/cmd/test.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "go.uber.org/zap" +) + +// Test build for these languages +var langs []string = []string{"go", "java"} + +func testCmd() *cobra.Command { + var t tester + cmd := &cobra.Command{ + Use: "test", + Short: "Test build proto files", + Run: func(cmd *cobra.Command, args []string) { + if err := t.test(cmd.Context()); err != nil { + t.logger.Fatal(err) + } + }, + } + t.addCLIFlags(cmd.Flags()) + return cmd +} + +type tester struct { + logger *zap.SugaredLogger + loggingOptions LoggingOptions +} + +func (l *tester) addCLIFlags(fs *pflag.FlagSet) { + l.loggingOptions.AddCLIFlags(fs) +} + +func (l *tester) test(ctx context.Context) error { + l.logger = l.loggingOptions.MustCreateLogger() + protoFiles, err := findProtos() + if err != nil { + return err + } + + tempDir, err := os.MkdirTemp("", "proto-build") + if err != nil { + return err + } + defer os.RemoveAll(tempDir) + + for _, lang := range langs { + langDir := filepath.Join(tempDir, lang) + if err := os.Mkdir(langDir, os.ModePerm); err != nil { + return err + } + args := []string{"--fatal_warnings", fmt.Sprintf("--%s_out", lang), langDir, "-I", projectRoot()} + args = append(args, protoFiles...) + + l.logger.Infow("Running protoc", "args", args) + cmd := exec.CommandContext(ctx, "protoc", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return err + } + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e527e47 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/nexus-rpc/api + +go 1.19 + +require ( + github.com/spf13/cobra v1.7.0 + github.com/spf13/pflag v1.0.5 + go.uber.org/zap v1.25.0 +) + +require ( + github.com/benbjohnson/clock v1.3.5 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f8982fc --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/google/api/annotations.proto b/google/api/annotations.proto new file mode 100644 index 0000000..efdab3d --- /dev/null +++ b/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/google/api/field_behavior.proto b/google/api/field_behavior.proto new file mode 100644 index 0000000..1a3a2f2 --- /dev/null +++ b/google/api/field_behavior.proto @@ -0,0 +1,90 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; +} diff --git a/google/api/http.proto b/google/api/http.proto new file mode 100644 index 0000000..31d867a --- /dev/null +++ b/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/nexus/rpc/api/common/v1/common.proto b/nexus/rpc/api/common/v1/common.proto new file mode 100644 index 0000000..42b7851 --- /dev/null +++ b/nexus/rpc/api/common/v1/common.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package nexus.rpc.api.common.v1; + +option go_package = "github.com/nexus-rpc/sdk-go/nexusapi/common/v1;common"; +option java_package = "nexus.rpc.api.common.v1"; +option java_outer_classname = "CommonProto"; +option java_multiple_files = true; + +message HeaderValues { + repeated string elements = 1; +} + diff --git a/nexus/rpc/api/nexusservice/v1/request_response.proto b/nexus/rpc/api/nexusservice/v1/request_response.proto new file mode 100644 index 0000000..ba348e2 --- /dev/null +++ b/nexus/rpc/api/nexusservice/v1/request_response.proto @@ -0,0 +1,65 @@ +syntax = "proto3"; + +package nexus.rpc.api.nexusservice.v1; + +import "google/api/field_behavior.proto"; +import "nexus/rpc/api/common/v1/common.proto"; +import "nexus/rpc/api/result/v1/result.proto"; + +option go_package = "github.com/nexus-rpc/sdk-go/nexusapi/nexusservice/v1;nexusservice"; +option java_package = "nexus.rpc.api.nexusservice.v1"; +option java_outer_classname = "RequestResponseProto"; +option java_multiple_files = true; + +// Start an operation by type and input. +message StartOperationRequest { + // Name of the service of this operation. + // When exposed over HTTP, service is be part of the URL. + // When exposed over gRPC, service will be part of the request body. It should be noted that a proxy would need to + // read and decode the request body in order to extract the service, which can potentially be an expensive operation. + string service = 1 [(google.api.field_behavior) = REQUIRED]; + + // Name of the operation. + string operation = 2 [(google.api.field_behavior) = REQUIRED]; + + map headers = 3 [(google.api.field_behavior) = OPTIONAL]; + + // Optional identifier that may be used to to reference this operation, e.g. to cancel it. + // If not provided, and the operation completes asynchronously, the handler will allocate an ID in the response. + string operation_id = 4 [(google.api.field_behavior) = OPTIONAL]; + + // Opaque request input. + // The content type and encoding of this input may be provided via request headers. + bytes body = 5 [(google.api.field_behavior) = OPTIONAL]; + + // Optional callback URL. + // If this operation is asynchronous and the handler supports callbacks, the handler should call this URL when the + // operation's result (succeeded, failed, canceled) becomes available. + string callback_uri = 6 [(google.api.field_behavior) = OPTIONAL]; +} + +message StartOperationResponse { + // The operation has been started and will complete asynchronously. + // Use the other NexusService methods to cancel it or get its status or result. + message Started { + map headers = 1; + + // Identifier that may be used to to reference this operation, e.g. to cancel it. + string operation_id = 2; + + // If the request specified a callback_uri and the handler supports callbacks for this operation, this flag will + // be set. + // It is up to the caller to decided how to get the outcome of this call in case the handler does not support + // callbacks. + bool callback_uri_supported = 3; + } + + // Embedded Result variants in addition to the asynchronous `Started` result. + // Embedding was chosen over wrapping to keep this field flat. + oneof result { + Started started = 1; + nexus.rpc.api.result.v1.Succeeded succeeded = 2; + nexus.rpc.api.result.v1.Failed failed = 3; + nexus.rpc.api.result.v1.Canceled canceled = 4; + } +} diff --git a/nexus/rpc/api/nexusservice/v1/service.proto b/nexus/rpc/api/nexusservice/v1/service.proto new file mode 100644 index 0000000..733b07d --- /dev/null +++ b/nexus/rpc/api/nexusservice/v1/service.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package nexus.rpc.api.nexusservice.v1; + +import "google/api/annotations.proto"; +import "nexus/rpc/api/nexusservice/v1/request_response.proto"; + +option go_package = "github.com/nexus-rpc/sdk-go/nexusapi/nexusservice/v1;nexusservice"; +option java_package = "nexus.rpc.api.nexusservice.v1"; +option java_outer_classname = "ServiceProto"; +option java_multiple_files = true; + +service NexusService { + // Start an arbirary length operation. + // The response of the operation may be delivered synchronously - inline, or asynchronously, via a provided callback. + rpc StartOperation (StartOperationRequest) returns (StartOperationResponse) { + option (google.api.http) = { + post: "/v1/services/{service}/operations/{operation}" + body: "*" + }; + } +} diff --git a/nexus/rpc/api/result/v1/result.proto b/nexus/rpc/api/result/v1/result.proto new file mode 100644 index 0000000..3349683 --- /dev/null +++ b/nexus/rpc/api/result/v1/result.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package nexus.rpc.api.result.v1; + +import "nexus/rpc/api/common/v1/common.proto"; + +option go_package = "github.com/nexus-rpc/sdk-go/nexusapi/result/v1;result"; +option java_package = "nexus.rpc.api.result.v1"; +option java_outer_classname = "ResultProto"; +option java_multiple_files = true; + +message Succeeded { + map headers = 1; + + bytes body = 2; +} + +message Failed { + map headers = 1; + + bytes body = 2; + // TODO: should we have built-in codes here? It could be useful when we translate to HTTP. + bool retryable = 3; +} + +message Canceled { + map headers = 1; + // Details of the cancelation. + // TODO: do we want a message as string here? + // Do we need details at all? + bytes body = 2; +} + +message Result { + // A result may be succeeded | failed| canceled. + // Note that these variants are duplicated in StartOperationResponse. + oneof variant { + Succeeded succeeded = 2; + Failed failed = 3; + Canceled canceled = 4; + } +} From 69fe7e88743773f69bdf99606b2d63e865b0d779 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Mon, 7 Aug 2023 17:32:32 -0700 Subject: [PATCH 2/2] Define entire API --- api-linter.yaml | 8 ++- buf.yaml | 2 + nexus/rpc/api/common/v1/common.proto | 7 ++ .../nexusservice/v1/request_response.proto | 64 +++++++++++++++++++ nexus/rpc/api/nexusservice/v1/service.proto | 41 +++++++++++- nexus/rpc/api/result/v1/result.proto | 2 +- 6 files changed, 121 insertions(+), 3 deletions(-) diff --git a/api-linter.yaml b/api-linter.yaml index 6c15d0a..bf1a51e 100644 --- a/api-linter.yaml +++ b/api-linter.yaml @@ -1,9 +1,15 @@ - included_paths: - "**/*.proto" disabled_rules: - - "core::0192::has-comments" - "core::0127::resource-name-extraction" + - "core::0131::method-signature" + - "core::0131::http-uri-name" + - "core::0131::request-name-required" + - "core::0131::request-required-fields" + - "core::0131::request-unknown-fields" + - "core::0136::http-body" - "core::0136::http-uri-suffix" + - "core::0192::has-comments" - included_paths: - "dependencies/gogoproto/gogo.proto" diff --git a/buf.yaml b/buf.yaml index 9595102..2c64a8f 100644 --- a/buf.yaml +++ b/buf.yaml @@ -5,5 +5,7 @@ breaking: lint: use: - DEFAULT + except: + - RPC_RESPONSE_STANDARD_NAME ignore: - google diff --git a/nexus/rpc/api/common/v1/common.proto b/nexus/rpc/api/common/v1/common.proto index 42b7851..af4018b 100644 --- a/nexus/rpc/api/common/v1/common.proto +++ b/nexus/rpc/api/common/v1/common.proto @@ -11,3 +11,10 @@ message HeaderValues { repeated string elements = 1; } +enum OperationState { + OPERATION_STATE_UNSPECIFIED = 0; + OPERATION_STATE_RUNNING = 1; + OPERATION_STATE_COMPLETED = 2; + OPERATION_STATE_FAILED = 3; + OPERATION_STATE_CANCELED = 4; +} diff --git a/nexus/rpc/api/nexusservice/v1/request_response.proto b/nexus/rpc/api/nexusservice/v1/request_response.proto index ba348e2..4c16111 100644 --- a/nexus/rpc/api/nexusservice/v1/request_response.proto +++ b/nexus/rpc/api/nexusservice/v1/request_response.proto @@ -63,3 +63,67 @@ message StartOperationResponse { nexus.rpc.api.result.v1.Canceled canceled = 4; } } + +message CancelOperationRequest { + // Name of the service of this operation. + string service = 1 [(google.api.field_behavior) = REQUIRED]; + + // Name of the operation. + string operation = 2 [(google.api.field_behavior) = REQUIRED]; + + // Identifier of the operation, either as provided by the caller in the StartOperationRequest or returned by the + // handler in StartOperationResponse. + string operation_id = 3 [(google.api.field_behavior) = REQUIRED]; + + map headers = 4 [(google.api.field_behavior) = OPTIONAL]; +} + +message CancelOperationResponse { +} + +message GetOperationResultRequest { + // Name of the service of this operation. + string service = 1 [(google.api.field_behavior) = REQUIRED]; + + // Name of the operation. + string operation = 2 [(google.api.field_behavior) = REQUIRED]; + + // Identifier of the operation, either as provided by the caller in the StartOperationRequest or returned by the + // handler in StartOperationResponse. + string operation_id = 3 [(google.api.field_behavior) = REQUIRED]; + + map headers = 4 [(google.api.field_behavior) = OPTIONAL]; + + // Whether to wait until a result is available or to immediately return an empty response. + // The handler should wait until just before the request deadline exceeds. + bool wait = 5 [(google.api.field_behavior) = OPTIONAL]; +} + +message GetOperationInfoRequest { + // Name of the service of this operation. + string service = 1 [(google.api.field_behavior) = REQUIRED]; + + // Name of the operation. + string operation = 2 [(google.api.field_behavior) = REQUIRED]; + + // Identifier of the operation, either as provided by the caller in the StartOperationRequest or returned by the + // handler in StartOperationResponse. + string operation_id = 3 [(google.api.field_behavior) = REQUIRED]; + + map headers = 4 [(google.api.field_behavior) = OPTIONAL]; +} + +message OperationInfo { + // State of the operation + nexus.rpc.api.common.v1.OperationState state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; +} + +message DeliverResultRequest { + // Completion token as provided via StartOperationRequest.callback_uri. + string token = 1 [(google.api.field_behavior) = REQUIRED]; + // Result of the operation. + nexus.rpc.api.result.v1.OperationResult result = 2 [(google.api.field_behavior) = REQUIRED]; +} + +message DeliverResultResponse { +} diff --git a/nexus/rpc/api/nexusservice/v1/service.proto b/nexus/rpc/api/nexusservice/v1/service.proto index 733b07d..1fee3d5 100644 --- a/nexus/rpc/api/nexusservice/v1/service.proto +++ b/nexus/rpc/api/nexusservice/v1/service.proto @@ -4,6 +4,7 @@ package nexus.rpc.api.nexusservice.v1; import "google/api/annotations.proto"; import "nexus/rpc/api/nexusservice/v1/request_response.proto"; +import "nexus/rpc/api/result/v1/result.proto"; option go_package = "github.com/nexus-rpc/sdk-go/nexusapi/nexusservice/v1;nexusservice"; option java_package = "nexus.rpc.api.nexusservice.v1"; @@ -15,7 +16,45 @@ service NexusService { // The response of the operation may be delivered synchronously - inline, or asynchronously, via a provided callback. rpc StartOperation (StartOperationRequest) returns (StartOperationResponse) { option (google.api.http) = { - post: "/v1/services/{service}/operations/{operation}" + post: "/api/v1/services/{service}/operations/{operation}/start" + body: "body" + additional_bindings { + post: "/api/v1/services/{service}/operations/{operation}/{operation_id}/start" + body: "body" + } + }; + } + + // Request to cancel an operation. + // The operation may be canceled or resolved with any other Result asynchronously. + // Handlers _should_ ignore multiple cancelations of the same operation and return successfully if the operation was + // already canceled. + rpc CancelOperation (CancelOperationRequest) returns (CancelOperationResponse) { + option (google.api.http) = { + post: "/api/v1/services/{service}/operations/{operation}/{operation_id}/cancel" + }; + } + + // Get the result of an operation. + // If a result is not ready yet, either long poll and wait for one to be available or return an empty response. + rpc GetOperationResult (GetOperationResultRequest) returns (nexus.rpc.api.result.v1.OperationResult) { + option (google.api.http) = { + get: "/api/v1/services/{service}/operations/{operation}/{operation_id}/result" + }; + } + + // Get information about an operation. + rpc GetOperationInfo (GetOperationInfoRequest) returns (OperationInfo) { + option (google.api.http) = { + get: "/api/v1/services/{service}/operations/{operation}/{operation_id}/info" + }; + } + + // Deliver the result of an operation + rpc DeliverResult (DeliverResultRequest) returns (DeliverResultResponse) { + option (google.api.http) = { + post: "/api/v1/callback" + // TODO: only the result body should be in the body body: "*" }; } diff --git a/nexus/rpc/api/result/v1/result.proto b/nexus/rpc/api/result/v1/result.proto index 3349683..0cefa3d 100644 --- a/nexus/rpc/api/result/v1/result.proto +++ b/nexus/rpc/api/result/v1/result.proto @@ -31,7 +31,7 @@ message Canceled { bytes body = 2; } -message Result { +message OperationResult { // A result may be succeeded | failed| canceled. // Note that these variants are duplicated in StartOperationResponse. oneof variant {