Skip to content

Commit 515546f

Browse files
committed
More client features
1 parent 5d17d1d commit 515546f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+4241
-250
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,19 @@ jobs:
7171
working-directory: ./temporalio
7272
run: cargo clippy && cargo fmt --check
7373

74-
# TODO(cretz): For checkTarget, regen protos and ensure no diff
74+
- name: Install bundle
75+
working-directory: ./temporalio
76+
run: bundle install
77+
78+
- name: Check generated protos
79+
if: ${{ matrix.checkTarget }}
80+
working-directory: ./temporalio
81+
run: |
82+
bundle exec rake proto:generate
83+
[[ -z $(git status --porcelain lib/temporalio/api) ]] || (git diff lib/temporalio/api; echo "Protos changed"; exit 1)
7584
7685
- name: Lint, compile, test Ruby
7786
working-directory: ./temporalio
78-
run: bundle install && bundle exec rake TESTOPTS="--verbose"
87+
run: bundle exec rake TESTOPTS="--verbose"
7988

8089
# TODO(cretz): Build gem and smoke test against separate dir

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ Prerequisites:
2121

2222
To build shared library for development use:
2323

24-
bundle exec rake compile:dev
24+
bundle exec rake compile
25+
26+
Note, this is not `compile:dev` because debug-mode in Rust has
27+
[an issue](https://github.com/rust-lang/rust/issues/34283) that causes runtime stack size problems.
2528

2629
To build and test release:
2730

temporalio/.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Metrics/BlockLength:
3434

3535
# The default is too small
3636
Metrics/ClassLength:
37-
Max: 400
37+
Max: 1000
3838

3939
# The default is too small
4040
Metrics/CyclomaticComplexity:

temporalio/Rakefile

Lines changed: 219 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
# rubocop:disable Metrics/BlockLength, Lint/MissingCopEnableDirective, Style/DocumentationMethod
4+
35
require 'bundler/gem_tasks'
46
require 'rb_sys/cargo/metadata'
57
require 'rb_sys/extensiontask'
@@ -34,57 +36,244 @@ require 'yard'
3436
YARD::Rake::YardocTask.new
3537

3638
require 'fileutils'
39+
require 'google/protobuf'
3740

3841
namespace :proto do
3942
desc 'Generate API and Core protobufs'
4043
task :generate do
4144
# Remove all existing
4245
FileUtils.rm_rf('lib/temporalio/api')
4346

44-
# Collect set of API protos with Google ones removed
45-
api_protos = Dir.glob('ext/sdk-core/sdk-core-protos/protos/api_upstream/**/*.proto').reject do |proto|
46-
proto.include?('google')
47-
end
47+
def generate_protos(api_protos)
48+
# Generate API to temp dir and move
49+
FileUtils.rm_rf('tmp-proto')
50+
FileUtils.mkdir_p('tmp-proto')
51+
sh 'bundle exec grpc_tools_ruby_protoc ' \
52+
'--proto_path=ext/sdk-core/sdk-core-protos/protos/api_upstream ' \
53+
'--proto_path=ext/sdk-core/sdk-core-protos/protos/api_cloud_upstream ' \
54+
'--proto_path=ext/additional_protos ' \
55+
'--ruby_out=tmp-proto ' \
56+
"#{api_protos.join(' ')}"
57+
58+
# Walk all generated Ruby files and cleanup content and filename
59+
Dir.glob('tmp-proto/temporal/api/**/*.rb') do |path|
60+
# Fix up the import
61+
content = File.read(path)
62+
content.gsub!(%r{^require 'temporal/(.*)_pb'$}, "require 'temporalio/\\1'")
63+
File.write(path, content)
64+
65+
# Remove _pb from the filename
66+
FileUtils.mv(path, path.sub('_pb', ''))
67+
end
4868

49-
# Generate API to temp dir and move
50-
FileUtils.rm_rf('tmp-proto')
51-
FileUtils.mkdir_p('tmp-proto')
52-
sh 'bundle exec grpc_tools_ruby_protoc ' \
53-
'--proto_path=ext/sdk-core/sdk-core-protos/protos/api_upstream ' \
54-
'--ruby_out=tmp-proto ' \
55-
"#{api_protos.join(' ')}"
56-
57-
# Walk all generated Ruby files and cleanup content and filename
58-
Dir.glob('tmp-proto/temporal/api/**/*.rb') do |path|
59-
# Fix up the import
60-
content = File.read(path)
61-
content.gsub!(%r{^require 'temporal/(.*)_pb'$}, "require 'temporalio/\\1'")
62-
File.write(path, content)
63-
64-
# Remove _pb from the filename
65-
FileUtils.mv(path, path.sub('_pb', ''))
69+
# Move from temp dir and remove temp dir
70+
FileUtils.cp_r('tmp-proto/temporal/api', 'lib/temporalio')
71+
FileUtils.rm_rf('tmp-proto')
6672
end
6773

68-
# Move from temp dir and remove temp dir
69-
FileUtils.mv('tmp-proto/temporal/api', 'lib/temporalio')
70-
FileUtils.rm_rf('tmp-proto')
74+
# Generate from API with Google ones removed
75+
generate_protos(Dir.glob('ext/sdk-core/sdk-core-protos/protos/api_upstream/**/*.proto').reject do |proto|
76+
proto.include?('google')
77+
end)
78+
79+
# Generate from Cloud API
80+
generate_protos(Dir.glob('ext/sdk-core/sdk-core-protos/protos/api_cloud_upstream/**/*.proto'))
81+
82+
# Generate additional protos
83+
generate_protos(Dir.glob('ext/additional_protos/**/*.proto'))
7184

7285
# Write files that will help with imports. We are requiring the
7386
# request_response and not the service because the service depends on Google
7487
# API annotations we don't want to have to depend on.
75-
string_lit = "# frozen_string_literal: true\n\n"
88+
File.write(
89+
'lib/temporalio/api/cloud/cloudservice.rb',
90+
<<~TEXT
91+
# frozen_string_literal: true
92+
93+
require 'temporalio/api/cloud/cloudservice/v1/request_response'
94+
TEXT
95+
)
7696
File.write(
7797
'lib/temporalio/api/workflowservice.rb',
78-
"#{string_lit}require 'temporalio/api/workflowservice/v1/request_response'\n"
98+
<<~TEXT
99+
# frozen_string_literal: true
100+
101+
require 'temporalio/api/workflowservice/v1/request_response'
102+
TEXT
79103
)
80104
File.write(
81105
'lib/temporalio/api/operatorservice.rb',
82-
"#{string_lit}require 'temporalio/api/operatorservice/v1/request_response'\n"
106+
<<~TEXT
107+
# frozen_string_literal: true
108+
109+
require 'temporalio/api/operatorservice/v1/request_response'
110+
TEXT
83111
)
84112
File.write(
85113
'lib/temporalio/api.rb',
86-
"#{string_lit}require 'temporalio/api/operatorservice'\nrequire 'temporalio/api/workflowservice'\n"
114+
<<~TEXT
115+
# frozen_string_literal: true
116+
117+
require 'temporalio/api/cloud/cloudservice'
118+
require 'temporalio/api/common/v1/grpc_status'
119+
require 'temporalio/api/errordetails/v1/message'
120+
require 'temporalio/api/operatorservice'
121+
require 'temporalio/api/workflowservice'
122+
123+
module Temporalio
124+
# Raw protocol buffer models.
125+
module Api
126+
end
127+
end
128+
TEXT
87129
)
130+
131+
# Write the service classes that have the RPC calls
132+
def write_service_file(qualified_service_name:, file_name:, class_name:, service_enum:)
133+
# Do service lookup
134+
desc = Google::Protobuf::DescriptorPool.generated_pool.lookup(qualified_service_name)
135+
raise 'Failed finding service descriptor' unless desc
136+
137+
# Open file to generate
138+
File.open("lib/temporalio/client/connection/#{file_name}.rb", 'w') do |file|
139+
file.puts <<~TEXT
140+
# frozen_string_literal: true
141+
142+
# Generated code. DO NOT EDIT!
143+
144+
require 'temporalio/api'
145+
require 'temporalio/client/connection/service'
146+
require 'temporalio/internal/bridge/client'
147+
148+
module Temporalio
149+
class Client
150+
class Connection
151+
# #{class_name} API.
152+
class #{class_name} < Service
153+
# @!visibility private
154+
def initialize(connection)
155+
super(connection, Internal::Bridge::Client::#{service_enum})
156+
end
157+
TEXT
158+
159+
desc.each do |method|
160+
# Camel case to snake case
161+
rpc = method.name.gsub(/([A-Z])/, '_\1').downcase.delete_prefix('_')
162+
file.puts <<-TEXT
163+
164+
# Calls #{class_name}.#{method.name} API call.
165+
#
166+
# @param request [#{method.input_type.msgclass}] API request.
167+
# @param rpc_retry [Boolean] Whether to implicitly retry known retryable errors.
168+
# @param rpc_metadata [Hash<String, String>, nil] Headers to include on the RPC call.
169+
# @param rpc_timeout [Float, nil] Number of seconds before timeout.
170+
# @return [#{method.output_type.msgclass}] API response.
171+
def #{rpc}(request, rpc_retry: false, rpc_metadata: nil, rpc_timeout: nil)
172+
invoke_rpc(
173+
rpc: '#{rpc}',
174+
request_class: #{method.input_type.msgclass},
175+
response_class: #{method.output_type.msgclass},
176+
request:,
177+
rpc_retry:,
178+
rpc_metadata:,
179+
rpc_timeout:
180+
)
181+
end
182+
TEXT
183+
end
184+
185+
file.puts <<~TEXT
186+
end
187+
end
188+
end
189+
end
190+
TEXT
191+
end
192+
end
193+
194+
require './lib/temporalio/api/workflowservice/v1/service'
195+
write_service_file(
196+
qualified_service_name: 'temporal.api.workflowservice.v1.WorkflowService',
197+
file_name: 'workflow_service',
198+
class_name: 'WorkflowService',
199+
service_enum: 'SERVICE_WORKFLOW'
200+
)
201+
require './lib/temporalio/api/operatorservice/v1/service'
202+
write_service_file(
203+
qualified_service_name: 'temporal.api.operatorservice.v1.OperatorService',
204+
file_name: 'operator_service',
205+
class_name: 'OperatorService',
206+
service_enum: 'SERVICE_OPERATOR'
207+
)
208+
require './lib/temporalio/api/cloud/cloudservice/v1/service'
209+
write_service_file(
210+
qualified_service_name: 'temporal.api.cloud.cloudservice.v1.CloudService',
211+
file_name: 'cloud_service',
212+
class_name: 'CloudService',
213+
service_enum: 'SERVICE_CLOUD'
214+
)
215+
216+
# Generate Rust code
217+
def generate_rust_match_arm(file:, qualified_service_name:, service_enum:, trait:)
218+
# Do service lookup
219+
desc = Google::Protobuf::DescriptorPool.generated_pool.lookup(qualified_service_name)
220+
file.puts <<~TEXT
221+
#{service_enum} => match call.rpc.as_str() {
222+
TEXT
223+
224+
# desc.sort_by { |a, b| a.name <=> b.name }.each do |method|
225+
desc.to_a.sort_by(&:name).each do |method|
226+
# Camel case to snake case
227+
rpc = method.name.gsub(/([A-Z])/, '_\1').downcase.delete_prefix('_')
228+
file.puts <<~TEXT
229+
"#{rpc}" => rpc_call!(self, block, call, #{trait}, #{rpc}),
230+
TEXT
231+
end
232+
file.puts <<~TEXT
233+
_ => Err(error!("Unknown RPC call {}", call.rpc)),
234+
},
235+
TEXT
236+
end
237+
File.open('ext/src/client_rpc_generated.rs', 'w') do |file|
238+
file.puts <<~TEXT
239+
// Generated code. DO NOT EDIT!
240+
241+
use magnus::{block::Proc, value::Opaque, Error, Ruby};
242+
use temporal_client::{CloudService, OperatorService, WorkflowService};
243+
244+
use super::{error, rpc_call};
245+
use crate::client::{Client, RpcCall, SERVICE_CLOUD, SERVICE_OPERATOR, SERVICE_WORKFLOW};
246+
247+
impl Client {
248+
pub fn invoke_rpc(&self, service: u8, block: Opaque<Proc>, call: RpcCall) -> Result<(), Error> {
249+
match service {
250+
TEXT
251+
generate_rust_match_arm(
252+
file:,
253+
qualified_service_name: 'temporal.api.workflowservice.v1.WorkflowService',
254+
service_enum: 'SERVICE_WORKFLOW',
255+
trait: 'WorkflowService'
256+
)
257+
generate_rust_match_arm(
258+
file:,
259+
qualified_service_name: 'temporal.api.operatorservice.v1.OperatorService',
260+
service_enum: 'SERVICE_OPERATOR',
261+
trait: 'OperatorService'
262+
)
263+
generate_rust_match_arm(
264+
file:,
265+
qualified_service_name: 'temporal.api.cloud.cloudservice.v1.CloudService',
266+
service_enum: 'SERVICE_CLOUD',
267+
trait: 'CloudService'
268+
)
269+
file.puts <<~TEXT
270+
_ => Err(error!("Unknown service")),
271+
}
272+
}
273+
}
274+
TEXT
275+
end
276+
sh 'cargo', 'fmt', '--', 'ext/src/client_rpc_generated.rs'
88277
end
89278
end
90279

@@ -104,4 +293,5 @@ Rake::Task[:build].enhance([:copy_parent_files]) do
104293
rm ['LICENSE', 'README.md']
105294
end
106295

107-
task default: [:rubocop, 'rbs:install_collection', :steep, :compile, :test]
296+
# TODO(cretz): Add rbs:install_collection and :steep back when RBS is ready
297+
task default: %i[rubocop compile test]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
package temporal.api.common.v1;
4+
5+
option ruby_package = "Temporalio::Api::Common::V1";
6+
7+
import "google/protobuf/any.proto";
8+
9+
// From https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto
10+
// since we don't import grpc but still need the status info
11+
message GrpcStatus {
12+
int32 code = 1;
13+
string message = 2;
14+
repeated google.protobuf.Any details = 3;
15+
}

0 commit comments

Comments
 (0)