From b37d25e70d87126fdeaf39d2207f57e8fd744b51 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Wed, 18 Mar 2026 15:40:01 -0600 Subject: [PATCH 01/14] Add "exposed" operation for system nexus endpoint --- nexusannotations/v1/options.proto | 29 +++++++++++++++++++ temporal/api/workflowservice/v1/service.proto | 5 +++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 nexusannotations/v1/options.proto diff --git a/nexusannotations/v1/options.proto b/nexusannotations/v1/options.proto new file mode 100644 index 000000000..0f817e4a4 --- /dev/null +++ b/nexusannotations/v1/options.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package nexus.v1; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/bergundy/nexus-proto-annotations/go/nexus/v1"; + +extend google.protobuf.ServiceOptions { + optional ServiceOptions service = 8233; +} + +extend google.protobuf.MethodOptions { + optional OperationOptions operation = 8234; +} + +message OperationOptions { + // Nexus operation name (defaults to proto method name). + string name = 1; + // Tags to attach to the operation. Used by code generators to include and exclude operations. + repeated string tags = 2; +} + +message ServiceOptions { + // Nexus service name (defaults to proto service full name). + string name = 1; + // Tags to attach to the service. Used by code generators to include and exclude services. + repeated string tags = 2; +} \ No newline at end of file diff --git a/temporal/api/workflowservice/v1/service.proto b/temporal/api/workflowservice/v1/service.proto index 8b0eb2ce6..7810aa7d8 100644 --- a/temporal/api/workflowservice/v1/service.proto +++ b/temporal/api/workflowservice/v1/service.proto @@ -10,8 +10,9 @@ option ruby_package = "Temporalio::Api::WorkflowService::V1"; option csharp_namespace = "Temporalio.Api.WorkflowService.V1"; -import "temporal/api/workflowservice/v1/request_response.proto"; import "google/api/annotations.proto"; +import "nexusannotations/v1/options.proto"; +import "temporal/api/workflowservice/v1/request_response.proto"; // WorkflowService API defines how Temporal SDKs and other clients interact with the Temporal server // to create and interact with workflows and activities. @@ -385,6 +386,8 @@ service WorkflowService { // (-- api-linter: core::0136::prepositions=disabled // aip.dev/not-precedent: "With" is used to indicate combined operation. --) rpc SignalWithStartWorkflowExecution (SignalWithStartWorkflowExecutionRequest) returns (SignalWithStartWorkflowExecutionResponse) { + option (nexus.v1.operation).tags = "exposed"; + option (google.api.http) = { post: "/namespaces/{namespace}/workflows/{workflow_id}/signal-with-start/{signal_name}" body: "*" From b51776406409a2abb55c0871515dcc2e1820aeea Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Wed, 18 Mar 2026 15:47:36 -0600 Subject: [PATCH 02/14] linter fixes --- nexusannotations/v1/options.proto | 2 +- temporal/api/workflowservice/v1/service.proto | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nexusannotations/v1/options.proto b/nexusannotations/v1/options.proto index 0f817e4a4..a8ef63728 100644 --- a/nexusannotations/v1/options.proto +++ b/nexusannotations/v1/options.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package nexus.v1; +package nexusannotations.v1; import "google/protobuf/descriptor.proto"; diff --git a/temporal/api/workflowservice/v1/service.proto b/temporal/api/workflowservice/v1/service.proto index 53f4ce2db..9cd8b730d 100644 --- a/temporal/api/workflowservice/v1/service.proto +++ b/temporal/api/workflowservice/v1/service.proto @@ -488,7 +488,7 @@ service WorkflowService { // (-- api-linter: core::0136::prepositions=disabled // aip.dev/not-precedent: "With" is used to indicate combined operation. --) rpc SignalWithStartWorkflowExecution (SignalWithStartWorkflowExecutionRequest) returns (SignalWithStartWorkflowExecutionResponse) { - option (nexus.v1.operation).tags = "exposed"; + option (nexusannotations.v1.operation).tags = "exposed"; option (google.api.http) = { post: "/namespaces/{namespace}/workflows/{workflow_id}/signal-with-start/{signal_name}" From f0760b3a65d27f8c1ea8fe70f4797634cc24afc0 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Tue, 24 Mar 2026 12:56:09 -0600 Subject: [PATCH 03/14] remove local nexusannotations and use nexus-rpc repo --- nexusannotations/v1/options.proto | 29 ------------------- temporal/api/workflowservice/v1/service.proto | 2 +- 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 nexusannotations/v1/options.proto diff --git a/nexusannotations/v1/options.proto b/nexusannotations/v1/options.proto deleted file mode 100644 index a8ef63728..000000000 --- a/nexusannotations/v1/options.proto +++ /dev/null @@ -1,29 +0,0 @@ -syntax = "proto3"; - -package nexusannotations.v1; - -import "google/protobuf/descriptor.proto"; - -option go_package = "github.com/bergundy/nexus-proto-annotations/go/nexus/v1"; - -extend google.protobuf.ServiceOptions { - optional ServiceOptions service = 8233; -} - -extend google.protobuf.MethodOptions { - optional OperationOptions operation = 8234; -} - -message OperationOptions { - // Nexus operation name (defaults to proto method name). - string name = 1; - // Tags to attach to the operation. Used by code generators to include and exclude operations. - repeated string tags = 2; -} - -message ServiceOptions { - // Nexus service name (defaults to proto service full name). - string name = 1; - // Tags to attach to the service. Used by code generators to include and exclude services. - repeated string tags = 2; -} \ No newline at end of file diff --git a/temporal/api/workflowservice/v1/service.proto b/temporal/api/workflowservice/v1/service.proto index 9cd8b730d..7a0272ebd 100644 --- a/temporal/api/workflowservice/v1/service.proto +++ b/temporal/api/workflowservice/v1/service.proto @@ -11,7 +11,7 @@ option csharp_namespace = "Temporalio.Api.WorkflowService.V1"; import "google/api/annotations.proto"; -import "nexusannotations/v1/options.proto"; +import "nexus-rpc/nexus-proto-annotations/nexusannotations/v1/options.proto"; import "temporal/api/workflowservice/v1/request_response.proto"; import "temporal/api/protometa/v1/annotations.proto"; From 3f17a76966f2981481af4c835db197b2da7447bb Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Wed, 25 Mar 2026 14:15:41 -0600 Subject: [PATCH 04/14] add nexus annotations to buf.yaml --- buf.yaml | 1 + temporal/api/workflowservice/v1/service.proto | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/buf.yaml b/buf.yaml index e984c1439..83dacd99f 100644 --- a/buf.yaml +++ b/buf.yaml @@ -3,6 +3,7 @@ name: buf.build/temporalio/api deps: - buf.build/grpc-ecosystem/grpc-gateway - buf.build/googleapis/googleapis + - buf.build/temporalio/nexus-annotations build: excludes: # Buf won't accept a local dependency on the google protos but we need them diff --git a/temporal/api/workflowservice/v1/service.proto b/temporal/api/workflowservice/v1/service.proto index 7a0272ebd..ff9829271 100644 --- a/temporal/api/workflowservice/v1/service.proto +++ b/temporal/api/workflowservice/v1/service.proto @@ -11,9 +11,9 @@ option csharp_namespace = "Temporalio.Api.WorkflowService.V1"; import "google/api/annotations.proto"; -import "nexus-rpc/nexus-proto-annotations/nexusannotations/v1/options.proto"; -import "temporal/api/workflowservice/v1/request_response.proto"; +import "temporalio/nexus-annotations/nexusannotations/v1/options.proto"; import "temporal/api/protometa/v1/annotations.proto"; +import "temporal/api/workflowservice/v1/request_response.proto"; // WorkflowService API defines how Temporal SDKs and other clients interact with the Temporal server // to create and interact with workflows and activities. From ba43a5688afcda1b8b808b6213ad0f66fdf30bd9 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Wed, 25 Mar 2026 14:16:06 -0600 Subject: [PATCH 05/14] buf.lock --- buf.lock | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/buf.lock b/buf.lock index fccbfb89d..f43352bf2 100644 --- a/buf.lock +++ b/buf.lock @@ -4,10 +4,15 @@ deps: - remote: buf.build owner: googleapis repository: googleapis - commit: 28151c0d0a1641bf938a7672c500e01d - digest: shake256:49215edf8ef57f7863004539deff8834cfb2195113f0b890dd1f67815d9353e28e668019165b9d872395871eeafcbab3ccfdb2b5f11734d3cca95be9e8d139de + commit: 004180b77378443887d3b55cabc00384 + digest: shake256:d26c7c2fd95f0873761af33ca4a0c0d92c8577122b6feb74eb3b0a57ebe47a98ab24a209a0e91945ac4c77204e9da0c2de0020b2cedc27bdbcdea6c431eec69b - remote: buf.build owner: grpc-ecosystem repository: grpc-gateway - commit: 048ae6ff94ca4476b3225904b1078fad - digest: shake256:e5250bf2d999516c02206d757502b902e406f35c099d0e869dc3e4f923f6870fe0805a9974c27df0695462937eae90cd4d9db90bb9a03489412560baa74a87b6 + commit: 6467306b4f624747aaf6266762ee7a1c + digest: shake256:833d648b99b9d2c18b6882ef41aaeb113e76fc38de20dda810c588d133846e6593b4da71b388bcd921b1c7ab41c7acf8f106663d7301ae9e82ceab22cf64b1b7 + - remote: buf.build + owner: temporalio + repository: nexus-annotations + commit: 599b78404fbe4e78b833d527a1d0da40 + digest: shake256:1f41ef11ccbf31d7318b0fe1915550ba6567c99dc94694d60b117fc1ffc756290ba9766c58b403986f079e2b861b42538e5f8cf0495f744cd390d223b81854ca From ab1b41ddb7dbbff91a3bbb6fe25a8b24d92b92ed Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Wed, 25 Mar 2026 14:25:54 -0600 Subject: [PATCH 06/14] add target to Makefile for downloading updates --- Makefile | 7 ++++- buf.yaml | 2 ++ nexusannotations/v1/options.proto | 29 +++++++++++++++++++ temporal/api/workflowservice/v1/service.proto | 2 +- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 nexusannotations/v1/options.proto diff --git a/Makefile b/Makefile index b4b33bdb5..3d9580d2a 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ ci-build: install proto http-api-docs install: grpc-install api-linter-install buf-install # Run all linters and compile proto files. -proto: grpc http-api-docs +proto: sync-nexus-annotations grpc http-api-docs ######################################################################## ##### Variables ###### @@ -95,6 +95,11 @@ buf-install: printf $(COLOR) "Install/update buf..." go install github.com/bufbuild/buf/cmd/buf@v1.27.0 +##### Sync external proto dependencies ##### +sync-nexus-annotations: + printf $(COLOR) "Sync nexusannotations from buf.build/temporalio/nexus-annotations..." + buf export buf.build/temporalio/nexus-annotations --output . + ##### Linters ##### api-linter: printf $(COLOR) "Run api-linter..." diff --git a/buf.yaml b/buf.yaml index 83dacd99f..6b9d9eb57 100644 --- a/buf.yaml +++ b/buf.yaml @@ -9,6 +9,8 @@ build: # Buf won't accept a local dependency on the google protos but we need them # to run api-linter, so just tell buf it ignore it - google + # Same for nexusannotations - local copy for api-linter, BSR dep for buf + - nexusannotations breaking: use: - WIRE_JSON diff --git a/nexusannotations/v1/options.proto b/nexusannotations/v1/options.proto new file mode 100644 index 000000000..e137896bb --- /dev/null +++ b/nexusannotations/v1/options.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package nexusannotations.v1; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/nexus-rpc/nexus-proto-annotations/go/nexusannotations/v1"; + +extend google.protobuf.ServiceOptions { + optional ServiceOptions service = 8233; +} + +extend google.protobuf.MethodOptions { + optional OperationOptions operation = 8234; +} + +message OperationOptions { + // Nexus operation name (defaults to proto method name). + string name = 1; + // Tags to attach to the operation. Used by code generators to include and exclude operations. + repeated string tags = 2; +} + +message ServiceOptions { + // Nexus service name (defaults to proto service full name). + string name = 1; + // Tags to attach to the service. Used by code generators to include and exclude services. + repeated string tags = 2; +} diff --git a/temporal/api/workflowservice/v1/service.proto b/temporal/api/workflowservice/v1/service.proto index ff9829271..4d814899f 100644 --- a/temporal/api/workflowservice/v1/service.proto +++ b/temporal/api/workflowservice/v1/service.proto @@ -11,7 +11,7 @@ option csharp_namespace = "Temporalio.Api.WorkflowService.V1"; import "google/api/annotations.proto"; -import "temporalio/nexus-annotations/nexusannotations/v1/options.proto"; +import "nexusannotations/v1/options.proto"; import "temporal/api/protometa/v1/annotations.proto"; import "temporal/api/workflowservice/v1/request_response.proto"; From 65d1c56e818166f509d47cda72df7f82779f4b48 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Fri, 3 Apr 2026 15:58:59 -0600 Subject: [PATCH 07/14] first draft of a nexusrpc{.langs}.yaml files --- Makefile | 14 +- cmd/protoc-gen-nexus-rpc-yaml/generator.go | 196 +++++++++++++++++++++ cmd/protoc-gen-nexus-rpc-yaml/go.mod | 10 ++ cmd/protoc-gen-nexus-rpc-yaml/go.sum | 12 ++ cmd/protoc-gen-nexus-rpc-yaml/main.go | 14 ++ nexus/nexusrpc.langs.yaml | 15 ++ nexus/nexusrpc.yaml | 9 + 7 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/generator.go create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/go.mod create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/go.sum create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/main.go create mode 100644 nexus/nexusrpc.langs.yaml create mode 100644 nexus/nexusrpc.yaml diff --git a/Makefile b/Makefile index 3d9580d2a..4a1b4055f 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ ci-build: install proto http-api-docs install: grpc-install api-linter-install buf-install # Run all linters and compile proto files. -proto: sync-nexus-annotations grpc http-api-docs +proto: sync-nexus-annotations grpc http-api-docs nexusrpc-yaml ######################################################################## ##### Variables ###### @@ -121,6 +121,18 @@ buf-breaking: @printf $(COLOR) "Run buf breaking changes check against master branch..." @(cd $(PROTO_ROOT) && buf breaking --against 'https://github.com/temporalio/api.git#branch=master') +nexusrpc-yaml: nexusrpc-yaml-install + printf $(COLOR) "Generate nexus/nexusrpc.yaml and nexus/nexusrpc.langs.yaml..." + mkdir -p nexus + protoc -I $(PROTO_ROOT) \ + --nexus-rpc-yaml_out=. \ + temporal/api/workflowservice/v1/* \ + temporal/api/operatorservice/v1/* + +nexusrpc-yaml-install: + printf $(COLOR) "Install protoc-gen-nexus-rpc-yaml..." + cd cmd/protoc-gen-nexus-rpc-yaml && go install . + ##### Clean ##### clean: printf $(COLOR) "Delete generated go files..." diff --git a/cmd/protoc-gen-nexus-rpc-yaml/generator.go b/cmd/protoc-gen-nexus-rpc-yaml/generator.go new file mode 100644 index 000000000..39bbd117e --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/generator.go @@ -0,0 +1,196 @@ +package main + +import ( + "slices" + "sort" + "strings" + + nexusannotationsv1 "github.com/nexus-rpc/nexus-proto-annotations/go/nexusannotations/v1" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" + "gopkg.in/yaml.v3" +) + +func generate(gen *protogen.Plugin) error { + nexusDoc := newDoc() + langsDoc := newDoc() + + for _, f := range gen.Files { + if !f.Generate { + continue + } + for _, svc := range f.Services { + for _, m := range svc.Methods { + opts, ok := m.Desc.Options().(*descriptorpb.MethodOptions) + if !ok || opts == nil { + continue + } + if !proto.HasExtension(opts, nexusannotationsv1.E_Operation) { + continue + } + opOpts := proto.GetExtension(opts, nexusannotationsv1.E_Operation).(*nexusannotationsv1.OperationOptions) + if !slices.Contains(opOpts.GetTags(), "exposed") { + continue + } + + svcName := string(svc.Desc.Name()) + methodName := string(m.Desc.Name()) + + addOperation(nexusDoc, svcName, methodName, + map[string]string{"$ref": openAPIRef(m.Input.Desc)}, + map[string]string{"$ref": openAPIRef(m.Output.Desc)}, + ) + + addOperation(langsDoc, svcName, methodName, + langRefs(f.Desc, m.Input.Desc), + langRefs(f.Desc, m.Output.Desc), + ) + } + } + } + + if err := writeFile(gen, "nexus/nexusrpc.yaml", nexusDoc); err != nil { + return err + } + return writeFile(gen, "nexus/nexusrpc.langs.yaml", langsDoc) +} + +// openAPIRef returns the nexus-rpc-gen multi-file $ref string for a message type, +// referencing the openapiv3.yaml components/schemas entry. The path is relative to nexus/. +// +// Schema key convention used by protoc-gen-openapi (v3): +// +// {MessageName} (no package prefix) +// +// e.g. message "SignalWithStartWorkflowExecutionRequest" +// → "components.schemas.SignalWithStartWorkflowExecutionRequest" +func openAPIRef(msg protoreflect.MessageDescriptor) string { + return "../openapi/openapiv3.yaml#components.schemas." + string(msg.Name()) +} + +// langRefs builds the map of language-specific type refs for a message, +// derived from proto file-level options. Only keys with non-empty values are included. +// Order is canonical: go → java → ruby → csharp. +func langRefs(file protoreflect.FileDescriptor, msg protoreflect.MessageDescriptor) map[string]string { + opts, ok := file.Options().(*descriptorpb.FileOptions) + if !ok || opts == nil { + return nil + } + name := string(msg.Name()) + refs := make(map[string]string) + + if pkg := opts.GetGoPackage(); pkg != "" { + // strip the ";alias" suffix (e.g. "go.temporal.io/api/workflowservice/v1;workflowservice") + pkg = strings.SplitN(pkg, ";", 2)[0] + refs["$goRef"] = pkg + "." + name + } + if pkg := opts.GetJavaPackage(); pkg != "" { + refs["$javaRef"] = pkg + "." + name + } + if pkg := opts.GetRubyPackage(); pkg != "" { + refs["$rubyRef"] = pkg + "::" + name + } + if pkg := opts.GetCsharpNamespace(); pkg != "" { + refs["$csharpRef"] = pkg + "." + name + } + if len(refs) == 0 { + return nil + } + return refs +} + +// newDoc creates a yaml.Node document with the "nexusrpc: 1.0.0" header +// and an empty "services" mapping node, returned as a *yaml.Node (document node). +func newDoc() *yaml.Node { + doc := &yaml.Node{Kind: yaml.DocumentNode} + root := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + doc.Content = []*yaml.Node{root} + + root.Content = append(root.Content, + scalarNode("nexusrpc"), + scalarNode("1.0.0"), + scalarNode("services"), + &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}, + ) + return doc +} + +// servicesNode returns the "services" mapping node from a doc created by newDoc. +func servicesNode(doc *yaml.Node) *yaml.Node { + root := doc.Content[0] + for i := 0; i < len(root.Content)-1; i += 2 { + if root.Content[i].Value == "services" { + return root.Content[i+1] + } + } + panic("services node not found") +} + +// addOperation inserts a service → operation → {input, output} entry into doc. +// Services and operations are inserted in the order first encountered. +func addOperation(doc *yaml.Node, svcName, methodName string, input, output map[string]string) { + svcs := servicesNode(doc) + + // find or create service node + var svcOps *yaml.Node + for i := 0; i < len(svcs.Content)-1; i += 2 { + if svcs.Content[i].Value == svcName { + // find "operations" within service mapping + svcMap := svcs.Content[i+1] + for j := 0; j < len(svcMap.Content)-1; j += 2 { + if svcMap.Content[j].Value == "operations" { + svcOps = svcMap.Content[j+1] + } + } + } + } + if svcOps == nil { + svcMap := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + svcOps = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + svcMap.Content = append(svcMap.Content, + scalarNode("operations"), + svcOps, + ) + svcs.Content = append(svcs.Content, scalarNode(svcName), svcMap) + } + + // build operation node + opNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + if len(input) > 0 { + opNode.Content = append(opNode.Content, scalarNode("input"), mapNode(input)) + } + if len(output) > 0 { + opNode.Content = append(opNode.Content, scalarNode("output"), mapNode(output)) + } + svcOps.Content = append(svcOps.Content, scalarNode(methodName), opNode) +} + +// mapNode serializes a map[string]string as a yaml mapping node with keys in sorted order. +func mapNode(m map[string]string) *yaml.Node { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + for _, k := range keys { + node.Content = append(node.Content, scalarNode(k), scalarNode(m[k])) + } + return node +} + +func scalarNode(value string) *yaml.Node { + return &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: value} +} + +func writeFile(gen *protogen.Plugin, name string, doc *yaml.Node) error { + f := gen.NewGeneratedFile(name, "") + enc := yaml.NewEncoder(f) + enc.SetIndent(2) + if err := enc.Encode(doc); err != nil { + return err + } + return enc.Close() +} diff --git a/cmd/protoc-gen-nexus-rpc-yaml/go.mod b/cmd/protoc-gen-nexus-rpc-yaml/go.mod new file mode 100644 index 000000000..610f91712 --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/go.mod @@ -0,0 +1,10 @@ +module github.com/temporalio/api/cmd/protoc-gen-nexus-rpc-yaml + +go 1.25.4 + +require ( + google.golang.org/protobuf v1.36.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 diff --git a/cmd/protoc-gen-nexus-rpc-yaml/go.sum b/cmd/protoc-gen-nexus-rpc-yaml/go.sum new file mode 100644 index 000000000..f9fd894ce --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/go.sum @@ -0,0 +1,12 @@ +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 h1:SWHt3Coj0VvF0Km1A0wlY+IjnHKsjQLgO29io84r3wY= +github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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/cmd/protoc-gen-nexus-rpc-yaml/main.go b/cmd/protoc-gen-nexus-rpc-yaml/main.go new file mode 100644 index 000000000..9fadd0fb1 --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/main.go @@ -0,0 +1,14 @@ +// protoc-gen-nexus-rpc-yaml is a protoc plugin that generates nexus/nexusrpc.yaml +// and nexus/nexusrpc.langs.yaml from proto service methods annotated with +// option (nexusannotations.v1.operation).tags = "exposed". +package main + +import ( + "google.golang.org/protobuf/compiler/protogen" +) + +func main() { + protogen.Options{}.Run(func(gen *protogen.Plugin) error { + return generate(gen) + }) +} diff --git a/nexus/nexusrpc.langs.yaml b/nexus/nexusrpc.langs.yaml new file mode 100644 index 000000000..2262cf57d --- /dev/null +++ b/nexus/nexusrpc.langs.yaml @@ -0,0 +1,15 @@ +nexusrpc: 1.0.0 +services: + WorkflowService: + operations: + SignalWithStartWorkflowExecution: + input: + $csharpRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionRequest + $goRef: go.temporal.io/api/workflowservice/v1.SignalWithStartWorkflowExecutionRequest + $javaRef: io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest + $rubyRef: Temporalio::Api::WorkflowService::V1::SignalWithStartWorkflowExecutionRequest + output: + $csharpRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionResponse + $goRef: go.temporal.io/api/workflowservice/v1.SignalWithStartWorkflowExecutionResponse + $javaRef: io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse + $rubyRef: Temporalio::Api::WorkflowService::V1::SignalWithStartWorkflowExecutionResponse diff --git a/nexus/nexusrpc.yaml b/nexus/nexusrpc.yaml new file mode 100644 index 000000000..08adb4b0b --- /dev/null +++ b/nexus/nexusrpc.yaml @@ -0,0 +1,9 @@ +nexusrpc: 1.0.0 +services: + WorkflowService: + operations: + SignalWithStartWorkflowExecution: + input: + $ref: ../openapi/openapiv3.yaml#components.schemas.SignalWithStartWorkflowExecutionRequest + output: + $ref: ../openapi/openapiv3.yaml#components.schemas.SignalWithStartWorkflowExecutionResponse From 9558edfb1c2642ff6983ce5421eac7e32f11c332 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Fri, 3 Apr 2026 16:14:21 -0600 Subject: [PATCH 08/14] use json pointer format instead of dot notation --- cmd/protoc-gen-nexus-rpc-yaml/generator.go | 4 ++-- nexus/nexusrpc.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/protoc-gen-nexus-rpc-yaml/generator.go b/cmd/protoc-gen-nexus-rpc-yaml/generator.go index 39bbd117e..53fa78fe5 100644 --- a/cmd/protoc-gen-nexus-rpc-yaml/generator.go +++ b/cmd/protoc-gen-nexus-rpc-yaml/generator.go @@ -65,9 +65,9 @@ func generate(gen *protogen.Plugin) error { // {MessageName} (no package prefix) // // e.g. message "SignalWithStartWorkflowExecutionRequest" -// → "components.schemas.SignalWithStartWorkflowExecutionRequest" +// → "../openapi/openapiv3.yaml#/components/schemas/SignalWithStartWorkflowExecutionRequest" func openAPIRef(msg protoreflect.MessageDescriptor) string { - return "../openapi/openapiv3.yaml#components.schemas." + string(msg.Name()) + return "../openapi/openapiv3.yaml#/components/schemas/" + string(msg.Name()) } // langRefs builds the map of language-specific type refs for a message, diff --git a/nexus/nexusrpc.yaml b/nexus/nexusrpc.yaml index 08adb4b0b..bbfafb33a 100644 --- a/nexus/nexusrpc.yaml +++ b/nexus/nexusrpc.yaml @@ -4,6 +4,6 @@ services: operations: SignalWithStartWorkflowExecution: input: - $ref: ../openapi/openapiv3.yaml#components.schemas.SignalWithStartWorkflowExecutionRequest + $ref: ../openapi/openapiv3.yaml#/components/schemas/SignalWithStartWorkflowExecutionRequest output: - $ref: ../openapi/openapiv3.yaml#components.schemas.SignalWithStartWorkflowExecutionResponse + $ref: ../openapi/openapiv3.yaml#/components/schemas/SignalWithStartWorkflowExecutionResponse From d10e568cefc40a04c7866610682e63fde6378edc Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Mon, 6 Apr 2026 09:50:59 -0600 Subject: [PATCH 09/14] change file names --- cmd/protoc-gen-nexus-rpc-yaml/generator.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/protoc-gen-nexus-rpc-yaml/generator.go b/cmd/protoc-gen-nexus-rpc-yaml/generator.go index 53fa78fe5..24d959b2c 100644 --- a/cmd/protoc-gen-nexus-rpc-yaml/generator.go +++ b/cmd/protoc-gen-nexus-rpc-yaml/generator.go @@ -50,11 +50,10 @@ func generate(gen *protogen.Plugin) error { } } } - - if err := writeFile(gen, "nexus/nexusrpc.yaml", nexusDoc); err != nil { + if err := writeFile(gen, "nexus/temporal-json-schema-models-nexusrpc.yaml", nexusDoc); err != nil { return err } - return writeFile(gen, "nexus/nexusrpc.langs.yaml", langsDoc) + return writeFile(gen, "nexus/temporal-proto-models-nexusrpc.yaml", langsDoc) } // openAPIRef returns the nexus-rpc-gen multi-file $ref string for a message type, From d249bb64cd83c1f7abeaa98d27613be26a2593cf Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Mon, 6 Apr 2026 14:42:40 -0600 Subject: [PATCH 10/14] add typescript and python Refs --- cmd/protoc-gen-nexus-rpc-yaml/generator.go | 16 ++++++++++++---- ...=> temporal-json-schema-models-nexusrpc.yaml} | 0 ....yaml => temporal-proto-models-nexusrpc.yaml} | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) rename nexus/{nexusrpc.yaml => temporal-json-schema-models-nexusrpc.yaml} (100%) rename nexus/{nexusrpc.langs.yaml => temporal-proto-models-nexusrpc.yaml} (59%) diff --git a/cmd/protoc-gen-nexus-rpc-yaml/generator.go b/cmd/protoc-gen-nexus-rpc-yaml/generator.go index 24d959b2c..0cf0e0c7d 100644 --- a/cmd/protoc-gen-nexus-rpc-yaml/generator.go +++ b/cmd/protoc-gen-nexus-rpc-yaml/generator.go @@ -69,9 +69,10 @@ func openAPIRef(msg protoreflect.MessageDescriptor) string { return "../openapi/openapiv3.yaml#/components/schemas/" + string(msg.Name()) } -// langRefs builds the map of language-specific type refs for a message, -// derived from proto file-level options. Only keys with non-empty values are included. -// Order is canonical: go → java → ruby → csharp. +// langRefs builds the map of language-specific type refs for a message. +// Go, Java, dotnet, and Ruby refs are derived from proto file-level package options. +// Python and TypeScript refs are derived from the go_package path, taking only the +// last two path segments ({service}/v{n}). func langRefs(file protoreflect.FileDescriptor, msg protoreflect.MessageDescriptor) map[string]string { opts, ok := file.Options().(*descriptorpb.FileOptions) if !ok || opts == nil { @@ -84,6 +85,13 @@ func langRefs(file protoreflect.FileDescriptor, msg protoreflect.MessageDescript // strip the ";alias" suffix (e.g. "go.temporal.io/api/workflowservice/v1;workflowservice") pkg = strings.SplitN(pkg, ";", 2)[0] refs["$goRef"] = pkg + "." + name + + segments := strings.Split(pkg, "/") + if len(segments) >= 2 { + tail := strings.Join(segments[len(segments)-2:], "/") + refs["$pythonRef"] = "temporalio.api." + strings.ReplaceAll(tail, "/", ".") + "." + name + refs["$typescriptRef"] = "@temporalio/api/" + tail + "." + name + } } if pkg := opts.GetJavaPackage(); pkg != "" { refs["$javaRef"] = pkg + "." + name @@ -92,7 +100,7 @@ func langRefs(file protoreflect.FileDescriptor, msg protoreflect.MessageDescript refs["$rubyRef"] = pkg + "::" + name } if pkg := opts.GetCsharpNamespace(); pkg != "" { - refs["$csharpRef"] = pkg + "." + name + refs["$dotnetRef"] = pkg + "." + name } if len(refs) == 0 { return nil diff --git a/nexus/nexusrpc.yaml b/nexus/temporal-json-schema-models-nexusrpc.yaml similarity index 100% rename from nexus/nexusrpc.yaml rename to nexus/temporal-json-schema-models-nexusrpc.yaml diff --git a/nexus/nexusrpc.langs.yaml b/nexus/temporal-proto-models-nexusrpc.yaml similarity index 59% rename from nexus/nexusrpc.langs.yaml rename to nexus/temporal-proto-models-nexusrpc.yaml index 2262cf57d..e0761fd15 100644 --- a/nexus/nexusrpc.langs.yaml +++ b/nexus/temporal-proto-models-nexusrpc.yaml @@ -4,12 +4,16 @@ services: operations: SignalWithStartWorkflowExecution: input: - $csharpRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionRequest + $dotnetRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionRequest $goRef: go.temporal.io/api/workflowservice/v1.SignalWithStartWorkflowExecutionRequest $javaRef: io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest + $pythonRef: temporalio.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest $rubyRef: Temporalio::Api::WorkflowService::V1::SignalWithStartWorkflowExecutionRequest + $typescriptRef: '@temporalio/api/workflowservice/v1.SignalWithStartWorkflowExecutionRequest' output: - $csharpRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionResponse + $dotnetRef: Temporalio.Api.WorkflowService.V1.SignalWithStartWorkflowExecutionResponse $goRef: go.temporal.io/api/workflowservice/v1.SignalWithStartWorkflowExecutionResponse $javaRef: io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse + $pythonRef: temporalio.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse $rubyRef: Temporalio::Api::WorkflowService::V1::SignalWithStartWorkflowExecutionResponse + $typescriptRef: '@temporalio/api/workflowservice/v1.SignalWithStartWorkflowExecutionResponse' From 1152ac57012cadc51657f87c38110d862d193883 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Mon, 13 Apr 2026 12:52:14 -0600 Subject: [PATCH 11/14] addressing review comments --- Makefile | 6 + cmd/protoc-gen-nexus-rpc-yaml/generator.go | 189 ++++++++++++++++----- 2 files changed, 150 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index 4a1b4055f..aac952788 100644 --- a/Makefile +++ b/Makefile @@ -125,6 +125,12 @@ nexusrpc-yaml: nexusrpc-yaml-install printf $(COLOR) "Generate nexus/nexusrpc.yaml and nexus/nexusrpc.langs.yaml..." mkdir -p nexus protoc -I $(PROTO_ROOT) \ + --nexus-rpc-yaml_opt=openapi_ref_prefix=../openapi/openapiv3.yaml#/components/schemas/ \ + --nexus-rpc-yaml_opt=nexusrpc_out=nexus/temporal-json-schema-models-nexusrpc.yaml \ + --nexus-rpc-yaml_opt=nexusrpc_langs_out=nexus/temporal-proto-models-nexusrpc.yaml \ + --nexus-rpc-yaml_opt=python_package_prefix=temporalio.api \ + --nexus-rpc-yaml_opt=typescript_package_prefix=@temporalio/api \ + --nexus-rpc-yaml_opt=include_operation_tags=exposed \ --nexus-rpc-yaml_out=. \ temporal/api/workflowservice/v1/* \ temporal/api/operatorservice/v1/* diff --git a/cmd/protoc-gen-nexus-rpc-yaml/generator.go b/cmd/protoc-gen-nexus-rpc-yaml/generator.go index 0cf0e0c7d..9d2ecb5ca 100644 --- a/cmd/protoc-gen-nexus-rpc-yaml/generator.go +++ b/cmd/protoc-gen-nexus-rpc-yaml/generator.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "slices" "sort" "strings" @@ -13,9 +14,115 @@ import ( "gopkg.in/yaml.v3" ) +// params holds the parsed protoc plugin options. +// Passed via --nexus-rpc-yaml_opt=key=value (multiple opts are comma-joined by protoc). +// +// - openapi_ref_prefix: optional. Prefix prepended to the message name to form the +// JSON Schema $ref in nexusrpc.yaml. If empty, nexusrpc.yaml is not written. +// Example: "../openapi/openapiv3.yaml#/components/schemas/" +// +// - nexusrpc_out: optional. Output path for nexusrpc.yaml (relative to --nexus-rpc-yaml_out dir). +// If empty, nexusrpc.yaml is not written. Requires openapi_ref_prefix to also be set. +// Example: "nexus/nexusrpc.yaml" +// +// - nexusrpc_langs_out: optional. Output path for nexusrpc.langs.yaml. +// If empty, nexusrpc.langs.yaml is not written. +// Example: "nexus/nexusrpc.langs.yaml" +// +// - python_package_prefix: optional. Dot-separated package prefix for $pythonRef. +// The last two path segments of the go_package ({service}/v{n}) are appended. +// Example: "temporalio.api" → "temporalio.api.workflowservice.v1.TypeName" +// If empty, $pythonRef is omitted. +// +// - typescript_package_prefix: optional. Scoped package prefix for $typescriptRef. +// The last two path segments of the go_package ({service}/v{n}) are appended. +// Example: "@temporalio/api" → "@temporalio/api/workflowservice/v1.TypeName" +// If empty, $typescriptRef is omitted. +// +// - include_operation_tags: optional, repeatable. Only include operations whose tags +// contain at least one of these values. If empty, all annotated operations are included +// (subject to exclude_operation_tags). Specify multiple times for multiple tags. +// Example: include_operation_tags=exposed +// +// - exclude_operation_tags: optional, repeatable. Exclude operations whose tags contain +// any of these values. Applied after include_operation_tags. +// Example: exclude_operation_tags=internal +type params struct { + openAPIRefPrefix string + nexusrpcOut string + nexusrpcLangsOut string + pythonPackagePrefix string + typescriptPackagePrefix string + includeOperationTags []string + excludeOperationTags []string +} + +// parseParams parses the comma-separated key=value parameter string provided by protoc. +func parseParams(raw string) (params, error) { + var p params + if raw == "" { + return p, nil + } + for kv := range strings.SplitSeq(raw, ",") { + key, value, ok := strings.Cut(kv, "=") + if !ok { + return p, fmt.Errorf("invalid parameter %q: expected key=value", kv) + } + switch key { + case "openapi_ref_prefix": + p.openAPIRefPrefix = value + case "nexusrpc_out": + p.nexusrpcOut = value + case "nexusrpc_langs_out": + p.nexusrpcLangsOut = value + case "python_package_prefix": + p.pythonPackagePrefix = value + case "typescript_package_prefix": + p.typescriptPackagePrefix = value + case "include_operation_tags": + p.includeOperationTags = append(p.includeOperationTags, value) + case "exclude_operation_tags": + p.excludeOperationTags = append(p.excludeOperationTags, value) + default: + return p, fmt.Errorf("unknown parameter %q", key) + } + } + return p, nil +} + +// shouldIncludeOperation returns true if the method's nexus operation tags pass +// the include/exclude filters. Mirrors the logic from protoc-gen-go-nexus: +// 1. Method must have the nexus operation extension set. +// 2. If includeOperationTags is non-empty, at least one of the method's tags must match. +// 3. If excludeOperationTags is non-empty, none of the method's tags may match. +func shouldIncludeOperation(p params, m *protogen.Method) bool { + opts, ok := m.Desc.Options().(*descriptorpb.MethodOptions) + if !ok || opts == nil { + return false + } + if !proto.HasExtension(opts, nexusannotationsv1.E_Operation) { + return false + } + tags := proto.GetExtension(opts, nexusannotationsv1.E_Operation).(*nexusannotationsv1.OperationOptions).GetTags() + if len(p.includeOperationTags) > 0 && !slices.ContainsFunc(p.includeOperationTags, func(t string) bool { + return slices.Contains(tags, t) + }) { + return false + } + return !slices.ContainsFunc(p.excludeOperationTags, func(t string) bool { + return slices.Contains(tags, t) + }) +} + func generate(gen *protogen.Plugin) error { + p, err := parseParams(gen.Request.GetParameter()) + if err != nil { + return err + } + nexusDoc := newDoc() langsDoc := newDoc() + hasOps := false for _, f := range gen.Files { if !f.Generate { @@ -23,57 +130,51 @@ func generate(gen *protogen.Plugin) error { } for _, svc := range f.Services { for _, m := range svc.Methods { - opts, ok := m.Desc.Options().(*descriptorpb.MethodOptions) - if !ok || opts == nil { - continue - } - if !proto.HasExtension(opts, nexusannotationsv1.E_Operation) { - continue - } - opOpts := proto.GetExtension(opts, nexusannotationsv1.E_Operation).(*nexusannotationsv1.OperationOptions) - if !slices.Contains(opOpts.GetTags(), "exposed") { + if !shouldIncludeOperation(p, m) { continue } svcName := string(svc.Desc.Name()) methodName := string(m.Desc.Name()) + hasOps = true - addOperation(nexusDoc, svcName, methodName, - map[string]string{"$ref": openAPIRef(m.Input.Desc)}, - map[string]string{"$ref": openAPIRef(m.Output.Desc)}, - ) + if p.openAPIRefPrefix != "" { + addOperation(nexusDoc, svcName, methodName, + map[string]string{"$ref": p.openAPIRefPrefix + string(m.Input.Desc.Name())}, + map[string]string{"$ref": p.openAPIRefPrefix + string(m.Output.Desc.Name())}, + ) + } addOperation(langsDoc, svcName, methodName, - langRefs(f.Desc, m.Input.Desc), - langRefs(f.Desc, m.Output.Desc), + langRefs(p, f.Desc, m.Input.Desc), + langRefs(p, f.Desc, m.Output.Desc), ) } } } - if err := writeFile(gen, "nexus/temporal-json-schema-models-nexusrpc.yaml", nexusDoc); err != nil { - return err + + if !hasOps { + return nil } - return writeFile(gen, "nexus/temporal-proto-models-nexusrpc.yaml", langsDoc) -} -// openAPIRef returns the nexus-rpc-gen multi-file $ref string for a message type, -// referencing the openapiv3.yaml components/schemas entry. The path is relative to nexus/. -// -// Schema key convention used by protoc-gen-openapi (v3): -// -// {MessageName} (no package prefix) -// -// e.g. message "SignalWithStartWorkflowExecutionRequest" -// → "../openapi/openapiv3.yaml#/components/schemas/SignalWithStartWorkflowExecutionRequest" -func openAPIRef(msg protoreflect.MessageDescriptor) string { - return "../openapi/openapiv3.yaml#/components/schemas/" + string(msg.Name()) + if p.openAPIRefPrefix != "" && p.nexusrpcOut != "" { + if err := writeFile(gen, p.nexusrpcOut, nexusDoc); err != nil { + return err + } + } + if p.nexusrpcLangsOut != "" { + return writeFile(gen, p.nexusrpcLangsOut, langsDoc) + } + return nil } // langRefs builds the map of language-specific type refs for a message. +// // Go, Java, dotnet, and Ruby refs are derived from proto file-level package options. -// Python and TypeScript refs are derived from the go_package path, taking only the -// last two path segments ({service}/v{n}). -func langRefs(file protoreflect.FileDescriptor, msg protoreflect.MessageDescriptor) map[string]string { +// Python and TypeScript refs require the corresponding prefix params to be set; if +// empty they are omitted. Both use the last two path segments of go_package +// ({service}/v{n}), dropping any intermediate grouping directory. +func langRefs(p params, file protoreflect.FileDescriptor, msg protoreflect.MessageDescriptor) map[string]string { opts, ok := file.Options().(*descriptorpb.FileOptions) if !ok || opts == nil { return nil @@ -88,9 +189,14 @@ func langRefs(file protoreflect.FileDescriptor, msg protoreflect.MessageDescript segments := strings.Split(pkg, "/") if len(segments) >= 2 { - tail := strings.Join(segments[len(segments)-2:], "/") - refs["$pythonRef"] = "temporalio.api." + strings.ReplaceAll(tail, "/", ".") + "." + name - refs["$typescriptRef"] = "@temporalio/api/" + tail + "." + name + tail := segments[len(segments)-2] + "/" + segments[len(segments)-1] + if p.pythonPackagePrefix != "" { + dotTail := strings.ReplaceAll(tail, "/", ".") + refs["$pythonRef"] = p.pythonPackagePrefix + "." + dotTail + "." + name + } + if p.typescriptPackagePrefix != "" { + refs["$typescriptRef"] = p.typescriptPackagePrefix + "/" + tail + "." + name + } } } if pkg := opts.GetJavaPackage(); pkg != "" { @@ -109,12 +215,11 @@ func langRefs(file protoreflect.FileDescriptor, msg protoreflect.MessageDescript } // newDoc creates a yaml.Node document with the "nexusrpc: 1.0.0" header -// and an empty "services" mapping node, returned as a *yaml.Node (document node). +// and an empty "services" mapping node. func newDoc() *yaml.Node { doc := &yaml.Node{Kind: yaml.DocumentNode} root := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} doc.Content = []*yaml.Node{root} - root.Content = append(root.Content, scalarNode("nexusrpc"), scalarNode("1.0.0"), @@ -140,11 +245,9 @@ func servicesNode(doc *yaml.Node) *yaml.Node { func addOperation(doc *yaml.Node, svcName, methodName string, input, output map[string]string) { svcs := servicesNode(doc) - // find or create service node var svcOps *yaml.Node for i := 0; i < len(svcs.Content)-1; i += 2 { if svcs.Content[i].Value == svcName { - // find "operations" within service mapping svcMap := svcs.Content[i+1] for j := 0; j < len(svcMap.Content)-1; j += 2 { if svcMap.Content[j].Value == "operations" { @@ -156,14 +259,10 @@ func addOperation(doc *yaml.Node, svcName, methodName string, input, output map[ if svcOps == nil { svcMap := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} svcOps = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - svcMap.Content = append(svcMap.Content, - scalarNode("operations"), - svcOps, - ) + svcMap.Content = append(svcMap.Content, scalarNode("operations"), svcOps) svcs.Content = append(svcs.Content, scalarNode(svcName), svcMap) } - // build operation node opNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} if len(input) > 0 { opNode.Content = append(opNode.Content, scalarNode("input"), mapNode(input)) From c2673983c3203174c1c0a8e660a9bc5d52268d26 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Mon, 13 Apr 2026 13:54:21 -0600 Subject: [PATCH 12/14] install from nexus-rpc/nexus-rpc-gen repo --- Makefile | 24 ++++++++++++++---------- cmd/protoc-gen-nexus-rpc-yaml/go.mod | 7 ++++++- cmd/protoc-gen-nexus-rpc-yaml/go.sum | 16 ++++++++++++---- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index aac952788..e069f75fd 100644 --- a/Makefile +++ b/Makefile @@ -122,22 +122,26 @@ buf-breaking: @(cd $(PROTO_ROOT) && buf breaking --against 'https://github.com/temporalio/api.git#branch=master') nexusrpc-yaml: nexusrpc-yaml-install - printf $(COLOR) "Generate nexus/nexusrpc.yaml and nexus/nexusrpc.langs.yaml..." + printf $(COLOR) "Generate nexus/temporal-json-schema-models-nexusrpc.yaml and nexus/temporal-proto-models-nexusrpc.yaml..." mkdir -p nexus protoc -I $(PROTO_ROOT) \ - --nexus-rpc-yaml_opt=openapi_ref_prefix=../openapi/openapiv3.yaml#/components/schemas/ \ - --nexus-rpc-yaml_opt=nexusrpc_out=nexus/temporal-json-schema-models-nexusrpc.yaml \ - --nexus-rpc-yaml_opt=nexusrpc_langs_out=nexus/temporal-proto-models-nexusrpc.yaml \ - --nexus-rpc-yaml_opt=python_package_prefix=temporalio.api \ - --nexus-rpc-yaml_opt=typescript_package_prefix=@temporalio/api \ - --nexus-rpc-yaml_opt=include_operation_tags=exposed \ - --nexus-rpc-yaml_out=. \ + --nexusrpc_opt=openapi_ref_prefix=../openapi/openapiv3.yaml#/components/schemas/ \ + --nexusrpc_opt=nexusrpc_out=nexus/temporal-json-schema-models-nexusrpc.yaml \ + --nexusrpc_opt=nexusrpc_langs_out=nexus/temporal-proto-models-nexusrpc.yaml \ + --nexusrpc_opt=python_package_prefix=temporalio.api \ + --nexusrpc_opt=typescript_package_prefix=@temporalio/api \ + --nexusrpc_opt=include_operation_tags=exposed \ + --nexusrpc_out=. \ temporal/api/workflowservice/v1/* \ temporal/api/operatorservice/v1/* nexusrpc-yaml-install: - printf $(COLOR) "Install protoc-gen-nexus-rpc-yaml..." - cd cmd/protoc-gen-nexus-rpc-yaml && go install . + # TODO seankane: after merging nexus-rpc/nexus-rpc-gen#36 update this to install from `main` instead of branch + printf $(COLOR) "Install protoc-gen-nexusrpc from nexus-rpc/nexus-rpc-gen@spk/protoc-plugin..." + @NEXUSRPC_TMP=$$(mktemp -d) && \ + git clone --quiet --depth=1 --branch=spk/protoc-plugin https://github.com/nexus-rpc/nexus-rpc-gen.git $$NEXUSRPC_TMP && \ + cd $$NEXUSRPC_TMP/tools/protoc-gen-nexusrpc && go install ./cmd/protoc-gen-nexusrpc && \ + rm -rf $$NEXUSRPC_TMP ##### Clean ##### clean: diff --git a/cmd/protoc-gen-nexus-rpc-yaml/go.mod b/cmd/protoc-gen-nexus-rpc-yaml/go.mod index 610f91712..c507d8486 100644 --- a/cmd/protoc-gen-nexus-rpc-yaml/go.mod +++ b/cmd/protoc-gen-nexus-rpc-yaml/go.mod @@ -7,4 +7,9 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 +require ( + github.com/bufbuild/protocompile v0.14.1 + github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 +) + +require golang.org/x/sync v0.8.0 // indirect diff --git a/cmd/protoc-gen-nexus-rpc-yaml/go.sum b/cmd/protoc-gen-nexus-rpc-yaml/go.sum index f9fd894ce..c248e2ac6 100644 --- a/cmd/protoc-gen-nexus-rpc-yaml/go.sum +++ b/cmd/protoc-gen-nexus-rpc-yaml/go.sum @@ -1,9 +1,17 @@ -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84 h1:SWHt3Coj0VvF0Km1A0wlY+IjnHKsjQLgO29io84r3wY= github.com/nexus-rpc/nexus-proto-annotations v0.0.0-20260330194009-e558d6edaf84/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From c3e6d2663868b65987aeda209f789231c1b22e31 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Tue, 14 Apr 2026 11:59:14 -0600 Subject: [PATCH 13/14] tests --- .../generator_test.go | 162 ++++++++++++++++++ .../testdata/nexusrpc.yaml | 9 + .../testdata/nexusrpc_langs.yaml | 19 ++ .../testdata/test/v1/service.proto | 21 +++ 4 files changed, 211 insertions(+) create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/generator_test.go create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc.yaml create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc_langs.yaml create mode 100644 cmd/protoc-gen-nexus-rpc-yaml/testdata/test/v1/service.proto diff --git a/cmd/protoc-gen-nexus-rpc-yaml/generator_test.go b/cmd/protoc-gen-nexus-rpc-yaml/generator_test.go new file mode 100644 index 000000000..78d7374f1 --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/generator_test.go @@ -0,0 +1,162 @@ +package main + +import ( + "context" + "os" + "testing" + + "github.com/bufbuild/protocompile" + nexusannotationsv1 "github.com/nexus-rpc/nexus-proto-annotations/go/nexusannotations/v1" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/descriptorpb" + "google.golang.org/protobuf/types/pluginpb" +) + +// runGenerate compiles testdata/test/v1/service.proto using the given plugin +// parameter string and returns the generated file contents keyed by filename. +// +// Known dependencies (google/protobuf/descriptor.proto and +// nexusannotations/v1/options.proto) are supplied as pre-compiled Go +// descriptors so that extension values are deserialized into Go-generated +// types rather than *dynamicpb.Message. +func runGenerate(t *testing.T, parameter string) map[string]string { + t.Helper() + + resolver := protocompile.CompositeResolver{ + // Return pre-compiled Go file descriptors for known dependencies. + // This ensures that the nexus extension is deserialised into the + // Go-generated *nexusannotationsv1.OperationOptions type, not a + // dynamic protobuf message, which would cause a type-assertion panic + // in shouldIncludeOperation. + protocompile.ResolverFunc(func(path string) (protocompile.SearchResult, error) { + switch path { + case "google/protobuf/descriptor.proto": + return protocompile.SearchResult{Desc: descriptorpb.File_google_protobuf_descriptor_proto}, nil + case "nexusannotations/v1/options.proto": + return protocompile.SearchResult{Desc: nexusannotationsv1.File_nexusannotations_v1_options_proto}, nil + } + return protocompile.SearchResult{}, protoregistry.NotFound + }), + // Fall back to reading source files from the testdata directory. + &protocompile.SourceResolver{ImportPaths: []string{"testdata"}}, + } + + compiledFiles, err := (&protocompile.Compiler{Resolver: resolver}).Compile( + context.Background(), "test/v1/service.proto", + ) + if err != nil { + t.Fatalf("compiling test proto: %v", err) + } + + // Collect all file descriptor protos in topological order (imports before importers). + // Each FileDescriptorProto is round-tripped through proto.Marshal + proto.Unmarshal + // to normalize extension fields. protocompile stores extension values as + // *dynamicpb.Message objects; the round-trip serializes them to raw bytes and lets + // proto.Unmarshal deserialize them as the registered Go-generated types (e.g. + // *nexusannotationsv1.OperationOptions). Without this, proto.GetExtension in the + // generator would panic with "invalid type: got *dynamicpb.Message". + seen := make(map[string]bool) + var protoFiles []*descriptorpb.FileDescriptorProto + var collect func(f protoreflect.FileDescriptor) + collect = func(f protoreflect.FileDescriptor) { + if seen[f.Path()] { + return + } + seen[f.Path()] = true + for i := 0; i < f.Imports().Len(); i++ { + collect(f.Imports().Get(i)) + } + raw, err := proto.Marshal(protodesc.ToFileDescriptorProto(f)) + if err != nil { + t.Fatalf("marshaling file descriptor %s: %v", f.Path(), err) + } + var fdp descriptorpb.FileDescriptorProto + if err := proto.Unmarshal(raw, &fdp); err != nil { + t.Fatalf("unmarshaling file descriptor %s: %v", f.Path(), err) + } + protoFiles = append(protoFiles, &fdp) + } + for _, f := range compiledFiles { + collect(f) + } + + req := &pluginpb.CodeGeneratorRequest{ + FileToGenerate: []string{"test/v1/service.proto"}, + ProtoFile: protoFiles, + Parameter: proto.String(parameter), + } + gen, err := protogen.Options{}.New(req) + if err != nil { + t.Fatalf("creating plugin: %v", err) + } + if err := generate(gen); err != nil { + t.Fatalf("generate: %v", err) + } + + result := make(map[string]string) + for _, f := range gen.Response().File { + result[f.GetName()] = f.GetContent() + } + return result +} + +// compareGolden compares got against the contents of goldenPath. +// Set UPDATE_GOLDEN=1 to overwrite the golden file with the current output +// instead of comparing, making it easy to refresh after intentional generator changes. +func compareGolden(t *testing.T, got, goldenPath string) { + t.Helper() + if os.Getenv("UPDATE_GOLDEN") == "1" { + if err := os.WriteFile(goldenPath, []byte(got), 0o644); err != nil { + t.Fatalf("updating golden file %s: %v", goldenPath, err) + } + return + } + want, err := os.ReadFile(goldenPath) + if err != nil { + t.Fatalf("reading golden file %s: %v", goldenPath, err) + } + if got != string(want) { + t.Errorf("output mismatch for %s:\ngot:\n%s\nwant:\n%s", goldenPath, got, string(want)) + } +} + +func TestGenerate_ExposedOperation(t *testing.T) { + const params = "openapi_ref_prefix=../openapi/openapiv3.yaml#/components/schemas/," + + "nexusrpc_out=nexus/nexusrpc.yaml," + + "nexusrpc_langs_out=nexus/nexusrpc.langs.yaml," + + "python_package_prefix=example.api," + + "typescript_package_prefix=@example/api," + + "include_operation_tags=exposed" + + files := runGenerate(t, params) + + compareGolden(t, files["nexus/nexusrpc.yaml"], "testdata/nexusrpc.yaml") + compareGolden(t, files["nexus/nexusrpc.langs.yaml"], "testdata/nexusrpc_langs.yaml") +} + +func TestGenerate_ExcludeTag_ProducesNoFiles(t *testing.T) { + const params = "nexusrpc_out=nexus/nexusrpc.yaml," + + "nexusrpc_langs_out=nexus/nexusrpc.langs.yaml," + + "exclude_operation_tags=exposed" + + files := runGenerate(t, params) + + if len(files) != 0 { + t.Errorf("expected no files when operation tag is excluded, got: %v", files) + } +} + +func TestGenerate_NoIncludeFilter_IncludesAnnotatedOperations(t *testing.T) { + // When no include_operation_tags is set, any annotated operation is included. + const params = "nexusrpc_langs_out=nexus/nexusrpc.langs.yaml" + + files := runGenerate(t, params) + + if _, ok := files["nexus/nexusrpc.langs.yaml"]; !ok { + t.Error("expected nexusrpc.langs.yaml to be generated when no include filter is set") + } +} diff --git a/cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc.yaml b/cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc.yaml new file mode 100644 index 000000000..7c56a479e --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc.yaml @@ -0,0 +1,9 @@ +nexusrpc: 1.0.0 +services: + TestService: + operations: + DoThing: + input: + $ref: ../openapi/openapiv3.yaml#/components/schemas/DoThingRequest + output: + $ref: ../openapi/openapiv3.yaml#/components/schemas/DoThingResponse diff --git a/cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc_langs.yaml b/cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc_langs.yaml new file mode 100644 index 000000000..f010cfb00 --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/testdata/nexusrpc_langs.yaml @@ -0,0 +1,19 @@ +nexusrpc: 1.0.0 +services: + TestService: + operations: + DoThing: + input: + $dotnetRef: Example.Api.Test.V1.DoThingRequest + $goRef: example.com/api/test/v1.DoThingRequest + $javaRef: com.example.api.test.v1.DoThingRequest + $pythonRef: example.api.test.v1.DoThingRequest + $rubyRef: Example::Api::Test::V1::DoThingRequest + $typescriptRef: '@example/api/test/v1.DoThingRequest' + output: + $dotnetRef: Example.Api.Test.V1.DoThingResponse + $goRef: example.com/api/test/v1.DoThingResponse + $javaRef: com.example.api.test.v1.DoThingResponse + $pythonRef: example.api.test.v1.DoThingResponse + $rubyRef: Example::Api::Test::V1::DoThingResponse + $typescriptRef: '@example/api/test/v1.DoThingResponse' diff --git a/cmd/protoc-gen-nexus-rpc-yaml/testdata/test/v1/service.proto b/cmd/protoc-gen-nexus-rpc-yaml/testdata/test/v1/service.proto new file mode 100644 index 000000000..0a37a0eb3 --- /dev/null +++ b/cmd/protoc-gen-nexus-rpc-yaml/testdata/test/v1/service.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package test.v1; + +import "nexusannotations/v1/options.proto"; + +option go_package = "example.com/api/test/v1;testv1"; +option java_package = "com.example.api.test.v1"; +option ruby_package = "Example::Api::Test::V1"; +option csharp_namespace = "Example.Api.Test.V1"; + +service TestService { + rpc DoThing(DoThingRequest) returns (DoThingResponse) { + option (nexusannotations.v1.operation) = { + tags: "exposed" + }; + } +} + +message DoThingRequest {} +message DoThingResponse {} From 1b7b2b5b751ed5fec551fb357cd6126d29e0114b Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Tue, 14 Apr 2026 12:01:58 -0600 Subject: [PATCH 14/14] proto --- buf.yaml | 1 + openapi/openapiv2.json | 224 ++++++++++++++++++++--------------------- 2 files changed, 113 insertions(+), 112 deletions(-) diff --git a/buf.yaml b/buf.yaml index 6b9d9eb57..2f2fa5389 100644 --- a/buf.yaml +++ b/buf.yaml @@ -21,3 +21,4 @@ lint: - DEFAULT ignore: - google + - cmd diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 8d4538d07..65a0ab54a 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -9332,7 +9332,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links to be associated with the WorkflowExecutionSignaled event." } @@ -11162,7 +11162,7 @@ "description": "The time when the last attempt completed." }, "lastAttemptFailure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "The last attempt's failure, if any." }, "nextAttemptScheduleTime": { @@ -11177,6 +11177,85 @@ }, "description": "Common callback information. Specific CallbackInfo messages should embed this and may include additional fields." }, + "apiCommonV1Link": { + "type": "object", + "properties": { + "workflowEvent": { + "$ref": "#/definitions/LinkWorkflowEvent" + }, + "batchJob": { + "$ref": "#/definitions/LinkBatchJob" + }, + "activity": { + "$ref": "#/definitions/LinkActivity" + } + }, + "description": "Link can be associated with history events. It might contain information about an external entity\nrelated to the history event. For example, workflow A makes a Nexus call that starts workflow B:\nin this case, a history event in workflow A could contain a Link to the workflow started event in\nworkflow B, and vice-versa." + }, + "apiFailureV1Failure": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "source": { + "type": "string", + "description": "The source this Failure originated in, e.g. TypeScriptSDK / JavaSDK\nIn some SDKs this is used to rehydrate the stack trace into an exception object." + }, + "stackTrace": { + "type": "string" + }, + "encodedAttributes": { + "$ref": "#/definitions/v1Payload", + "description": "Alternative way to supply `message` and `stack_trace` and possibly other attributes, used for encryption of\nerrors originating in user code which might contain sensitive information.\nThe `encoded_attributes` Payload could represent any serializable object, e.g. JSON object or a `Failure` proto\nmessage.\n\nSDK authors:\n- The SDK should provide a default `encodeFailureAttributes` and `decodeFailureAttributes` implementation that:\n - Uses a JSON object to represent `{ message, stack_trace }`.\n - Overwrites the original message with \"Encoded failure\" to indicate that more information could be extracted.\n - Overwrites the original stack_trace with an empty string.\n - The resulting JSON object is converted to Payload using the default PayloadConverter and should be processed\n by the user-provided PayloadCodec\n\n- If there's demand, we could allow overriding the default SDK implementation to encode other opaque Failure attributes." + }, + "cause": { + "$ref": "#/definitions/apiFailureV1Failure" + }, + "applicationFailureInfo": { + "$ref": "#/definitions/v1ApplicationFailureInfo" + }, + "timeoutFailureInfo": { + "$ref": "#/definitions/v1TimeoutFailureInfo" + }, + "canceledFailureInfo": { + "$ref": "#/definitions/v1CanceledFailureInfo" + }, + "terminatedFailureInfo": { + "$ref": "#/definitions/v1TerminatedFailureInfo" + }, + "serverFailureInfo": { + "$ref": "#/definitions/v1ServerFailureInfo" + }, + "resetWorkflowFailureInfo": { + "$ref": "#/definitions/v1ResetWorkflowFailureInfo" + }, + "activityFailureInfo": { + "$ref": "#/definitions/v1ActivityFailureInfo" + }, + "childWorkflowExecutionFailureInfo": { + "$ref": "#/definitions/v1ChildWorkflowExecutionFailureInfo" + }, + "nexusOperationExecutionFailureInfo": { + "$ref": "#/definitions/v1NexusOperationFailureInfo" + }, + "nexusHandlerFailureInfo": { + "$ref": "#/definitions/v1NexusHandlerFailureInfo" + } + } + }, + "apiUpdateV1Request": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/definitions/v1Meta" + }, + "input": { + "$ref": "#/definitions/v1Input" + } + }, + "description": "The client request that triggers a Workflow Update." + }, "apiWorkflowV1CallbackInfo": { "type": "object", "properties": { @@ -11207,7 +11286,7 @@ "description": "The time when the last attempt completed." }, "lastAttemptFailure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "The last attempt's failure, if any." }, "nextAttemptScheduleTime": { @@ -11344,7 +11423,7 @@ "description": "Time when the activity transitioned to a closed state." }, "lastFailure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Failure details from the last failed attempt." }, "lastWorkerIdentity": { @@ -11400,7 +11479,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links to related entities, such as the entity that started this activity." } @@ -11469,7 +11548,7 @@ "description": "The result if the activity completed successfully." }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "The failure if the activity completed unsuccessfully." } }, @@ -11659,7 +11738,7 @@ "type": "object", "properties": { "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "title": "Failure details" }, "scheduledEventId": { @@ -11761,7 +11840,7 @@ "title": "Starting at 1, the number of times this task has been attempted" }, "lastFailure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Will be set to the most recent failure details, if this task has previously failed and then\nbeen retried." }, "workerVersion": { @@ -11779,7 +11858,7 @@ "type": "object", "properties": { "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "If this activity had failed, was retried, and then timed out, that failure is stored as the\n`cause` in here." }, "scheduledEventId": { @@ -12239,7 +12318,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links associated with the callback. It can be used to link to underlying resources of the\ncallback." } @@ -12347,7 +12426,7 @@ "type": "object", "properties": { "failure": { - "$ref": "#/definitions/v1Failure" + "$ref": "#/definitions/apiFailureV1Failure" }, "namespace": { "type": "string", @@ -13443,58 +13522,6 @@ }, "title": "Represents a historical replication status of a Namespace" }, - "v1Failure": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "source": { - "type": "string", - "description": "The source this Failure originated in, e.g. TypeScriptSDK / JavaSDK\nIn some SDKs this is used to rehydrate the stack trace into an exception object." - }, - "stackTrace": { - "type": "string" - }, - "encodedAttributes": { - "$ref": "#/definitions/v1Payload", - "description": "Alternative way to supply `message` and `stack_trace` and possibly other attributes, used for encryption of\nerrors originating in user code which might contain sensitive information.\nThe `encoded_attributes` Payload could represent any serializable object, e.g. JSON object or a `Failure` proto\nmessage.\n\nSDK authors:\n- The SDK should provide a default `encodeFailureAttributes` and `decodeFailureAttributes` implementation that:\n - Uses a JSON object to represent `{ message, stack_trace }`.\n - Overwrites the original message with \"Encoded failure\" to indicate that more information could be extracted.\n - Overwrites the original stack_trace with an empty string.\n - The resulting JSON object is converted to Payload using the default PayloadConverter and should be processed\n by the user-provided PayloadCodec\n\n- If there's demand, we could allow overriding the default SDK implementation to encode other opaque Failure attributes." - }, - "cause": { - "$ref": "#/definitions/v1Failure" - }, - "applicationFailureInfo": { - "$ref": "#/definitions/v1ApplicationFailureInfo" - }, - "timeoutFailureInfo": { - "$ref": "#/definitions/v1TimeoutFailureInfo" - }, - "canceledFailureInfo": { - "$ref": "#/definitions/v1CanceledFailureInfo" - }, - "terminatedFailureInfo": { - "$ref": "#/definitions/v1TerminatedFailureInfo" - }, - "serverFailureInfo": { - "$ref": "#/definitions/v1ServerFailureInfo" - }, - "resetWorkflowFailureInfo": { - "$ref": "#/definitions/v1ResetWorkflowFailureInfo" - }, - "activityFailureInfo": { - "$ref": "#/definitions/v1ActivityFailureInfo" - }, - "childWorkflowExecutionFailureInfo": { - "$ref": "#/definitions/v1ChildWorkflowExecutionFailureInfo" - }, - "nexusOperationExecutionFailureInfo": { - "$ref": "#/definitions/v1NexusOperationFailureInfo" - }, - "nexusHandlerFailureInfo": { - "$ref": "#/definitions/v1NexusHandlerFailureInfo" - } - } - }, "v1FetchWorkerConfigResponse": { "type": "object", "properties": { @@ -13799,7 +13826,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links to related entities, such as the entity that started this event's workflow." }, @@ -14061,21 +14088,6 @@ }, "description": "IntervalSpec matches times that can be expressed as:\nepoch + n * interval + phase\nwhere n is an integer.\nphase defaults to zero if missing. interval is required.\nBoth interval and phase must be non-negative and are truncated to the nearest\nsecond before any calculations.\nFor example, an interval of 1 hour with phase of zero would match every hour,\non the hour. The same interval but a phase of 19 minutes would match every\nxx:19:00. An interval of 28 days with phase zero would match\n2022-02-17T00:00:00Z (among other times). The same interval with a phase of 3\ndays, 5 hours, and 23 minutes would match 2022-02-20T05:23:00Z instead." }, - "v1Link": { - "type": "object", - "properties": { - "workflowEvent": { - "$ref": "#/definitions/LinkWorkflowEvent" - }, - "batchJob": { - "$ref": "#/definitions/LinkBatchJob" - }, - "activity": { - "$ref": "#/definitions/LinkActivity" - } - }, - "description": "Link can be associated with history events. It might contain information about an external entity\nrelated to the history event. For example, workflow A makes a Nexus call that starts workflow B:\nin this case, a history event in workflow A could contain a Link to the workflow started event in\nworkflow B, and vice-versa." - }, "v1ListActivityExecutionsResponse": { "type": "object", "properties": { @@ -14328,7 +14340,7 @@ "$ref": "#/definitions/v1Header" }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Some uses of markers, like a local activity, could \"fail\". If they did that is recorded here." } } @@ -14666,7 +14678,7 @@ "description": "The `WORKFLOW_TASK_COMPLETED` event that the corresponding RequestCancelNexusOperation command was reported\nwith." }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Failure details. A NexusOperationFailureInfo wrapping a CanceledFailureInfo." }, "scheduledEventId": { @@ -14700,7 +14712,7 @@ "description": "The ID of the `NEXUS_OPERATION_SCHEDULED` event. Uniquely identifies this operation." }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Cancellation details." }, "requestId": { @@ -14732,7 +14744,7 @@ "description": "The time when the last attempt completed." }, "lastAttemptFailure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "The last attempt's failure, if any." }, "nextAttemptScheduleTime": { @@ -14789,7 +14801,7 @@ "description": "The ID of the `NEXUS_OPERATION_SCHEDULED` event. Uniquely identifies this operation." }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Failure details. A NexusOperationFailureInfo wrapping an ApplicationFailureInfo." }, "requestId": { @@ -14916,7 +14928,7 @@ "description": "The ID of the `NEXUS_OPERATION_SCHEDULED` event. Uniquely identifies this operation." }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Failure details. A NexusOperationFailureInfo wrapping a CanceledFailureInfo." }, "requestId": { @@ -14951,7 +14963,7 @@ "$ref": "#/definitions/v1Payloads" }, "failure": { - "$ref": "#/definitions/v1Failure" + "$ref": "#/definitions/apiFailureV1Failure" } }, "description": "The outcome of a Workflow Update: success or failure." @@ -15034,7 +15046,7 @@ "format": "date-time" }, "lastFailure": { - "$ref": "#/definitions/v1Failure" + "$ref": "#/definitions/apiFailureV1Failure" }, "lastWorkerIdentity": { "type": "string" @@ -15172,7 +15184,7 @@ "description": "The time when the last attempt completed." }, "lastAttemptFailure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "The last attempt's failure, if any." }, "nextAttemptScheduleTime": { @@ -15653,18 +15665,6 @@ ], "default": "REPLICATION_STATE_UNSPECIFIED" }, - "v1Request": { - "type": "object", - "properties": { - "meta": { - "$ref": "#/definitions/v1Meta" - }, - "input": { - "$ref": "#/definitions/v1Input" - } - }, - "description": "The client request that triggers a Workflow Update." - }, "v1RequestCancelActivityExecutionResponse": { "type": "object" }, @@ -15913,7 +15913,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Failure" + "$ref": "#/definitions/apiFailureV1Failure" }, "title": "Server validation failures could include\nlast_heartbeat_details payload is too large, request failure is too large" } @@ -15926,7 +15926,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Failure" + "$ref": "#/definitions/apiFailureV1Failure" }, "title": "Server validation failures could include\nlast_heartbeat_details payload is too large, request failure is too large" } @@ -16564,7 +16564,7 @@ "description": "If true, a new activity was started." }, "link": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Link to the started activity." } } @@ -16713,7 +16713,7 @@ "description": "When `request_eager_execution` is set on the `StartWorkflowExecutionRequest`, the server - if supported - will\nreturn the first workflow task to be eagerly executed.\nThe caller is expected to have a worker available to process the task." }, "link": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Link to the workflow event." } } @@ -18158,7 +18158,7 @@ "$ref": "#/definitions/v1ContinueAsNewInitiator" }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "Deprecated. If a workflow's retry policy would cause a new run to start when the current one\nhas failed, this field would be populated with that failure. Now (when supported by server\nand sdk) the final event will be `WORKFLOW_EXECUTION_FAILED` with `new_execution_run_id` set." }, "lastCompletionResult": { @@ -18233,7 +18233,7 @@ "type": "object", "properties": { "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "title": "Serialized result of workflow failure (ex: An exception thrown, or error returned)" }, "retryState": { @@ -18522,7 +18522,7 @@ "$ref": "#/definitions/v1ContinueAsNewInitiator" }, "continuedFailure": { - "$ref": "#/definitions/v1Failure" + "$ref": "#/definitions/apiFailureV1Failure" }, "lastCompletionResult": { "$ref": "#/definitions/v1Payloads" @@ -18735,7 +18735,7 @@ "description": "The event ID used to sequence the original request message." }, "acceptedRequest": { - "$ref": "#/definitions/v1Request", + "$ref": "#/definitions/apiUpdateV1Request", "description": "The message payload of the original request message that initiated this\nupdate." } } @@ -18744,7 +18744,7 @@ "type": "object", "properties": { "request": { - "$ref": "#/definitions/v1Request", + "$ref": "#/definitions/apiUpdateV1Request", "description": "The update request associated with this event." }, "origin": { @@ -18788,11 +18788,11 @@ "description": "The event ID used to sequence the original request message." }, "rejectedRequest": { - "$ref": "#/definitions/v1Request", + "$ref": "#/definitions/apiUpdateV1Request", "description": "The message payload of the original request message that initiated this\nupdate." }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "description": "The cause of rejection." } } @@ -19128,7 +19128,7 @@ "$ref": "#/definitions/v1WorkflowTaskFailedCause" }, "failure": { - "$ref": "#/definitions/v1Failure", + "$ref": "#/definitions/apiFailureV1Failure", "title": "The failure details" }, "identity": {