spikard 0.12.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Steepfile +6 -0
- data/ext/spikard_rb/extconf.rb +1 -2
- data/ext/spikard_rb/{Cargo.lock → native/Cargo.lock} +897 -451
- data/ext/spikard_rb/native/Cargo.toml +24 -0
- data/ext/spikard_rb/src/lib.rs +5366 -3
- data/lib/spikard/native.rb +86 -0
- data/lib/spikard/version.rb +6 -1
- data/lib/spikard.rb +8 -45
- data/lib/spikard_rb.so +0 -0
- data/sig/types.rbs +427 -0
- metadata +14 -242
- data/LICENSE +0 -1
- data/README.md +0 -267
- data/ext/spikard_rb/Cargo.toml +0 -17
- data/lib/spikard/app.rb +0 -428
- data/lib/spikard/background.rb +0 -58
- data/lib/spikard/config.rb +0 -506
- data/lib/spikard/converters.rb +0 -13
- data/lib/spikard/grpc.rb +0 -182
- data/lib/spikard/handler_wrapper.rb +0 -113
- data/lib/spikard/provide.rb +0 -214
- data/lib/spikard/response.rb +0 -173
- data/lib/spikard/schema.rb +0 -243
- data/lib/spikard/sse.rb +0 -111
- data/lib/spikard/streaming_response.rb +0 -44
- data/lib/spikard/testing.rb +0 -432
- data/lib/spikard/upload_file.rb +0 -131
- data/lib/spikard/websocket.rb +0 -59
- data/sig/spikard.rbs +0 -719
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -80
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +0 -132
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +0 -905
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +0 -210
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +0 -404
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +0 -199
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +0 -829
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +0 -587
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +0 -33
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +0 -298
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +0 -594
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +0 -743
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +0 -944
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +0 -260
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +0 -369
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +0 -192
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +0 -383
- data/vendor/crates/spikard-bindings-shared/tests/full_coverage.rs +0 -459
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +0 -280
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +0 -669
- data/vendor/crates/spikard-core/Cargo.toml +0 -60
- data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +0 -130
- data/vendor/crates/spikard-core/src/debug.rs +0 -127
- data/vendor/crates/spikard-core/src/di/container.rs +0 -702
- data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
- data/vendor/crates/spikard-core/src/di/error.rs +0 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +0 -538
- data/vendor/crates/spikard-core/src/di/graph.rs +0 -507
- data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +0 -428
- data/vendor/crates/spikard-core/src/di/value.rs +0 -282
- data/vendor/crates/spikard-core/src/errors.rs +0 -72
- data/vendor/crates/spikard-core/src/http.rs +0 -492
- data/vendor/crates/spikard-core/src/lib.rs +0 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +0 -1273
- data/vendor/crates/spikard-core/src/metadata.rs +0 -378
- data/vendor/crates/spikard-core/src/parameters.rs +0 -2546
- data/vendor/crates/spikard-core/src/problem.rs +0 -358
- data/vendor/crates/spikard-core/src/request_data.rs +0 -1146
- data/vendor/crates/spikard-core/src/router.rs +0 -530
- data/vendor/crates/spikard-core/src/schema_registry.rs +0 -197
- data/vendor/crates/spikard-core/src/type_hints.rs +0 -311
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +0 -710
- data/vendor/crates/spikard-core/src/validation/mod.rs +0 -470
- data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +0 -136
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +0 -37
- data/vendor/crates/spikard-core/tests/error_mapper.rs +0 -761
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +0 -106
- data/vendor/crates/spikard-core/tests/parameters_full.rs +0 -701
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +0 -301
- data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +0 -67
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +0 -250
- data/vendor/crates/spikard-core/tests/validation_error_paths.rs +0 -45
- data/vendor/crates/spikard-http/Cargo.toml +0 -87
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +0 -148
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +0 -92
- data/vendor/crates/spikard-http/src/auth.rs +0 -301
- data/vendor/crates/spikard-http/src/background.rs +0 -1860
- data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
- data/vendor/crates/spikard-http/src/cors.rs +0 -1026
- data/vendor/crates/spikard-http/src/debug.rs +0 -128
- data/vendor/crates/spikard-http/src/di_handler.rs +0 -1672
- data/vendor/crates/spikard-http/src/grpc/framing.rs +0 -469
- data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1122
- data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -434
- data/vendor/crates/spikard-http/src/grpc/service.rs +0 -622
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +0 -319
- data/vendor/crates/spikard-http/src/handler_response.rs +0 -901
- data/vendor/crates/spikard-http/src/handler_trait.rs +0 -1015
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -290
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +0 -502
- data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +0 -648
- data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +0 -58
- data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +0 -1207
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +0 -2262
- data/vendor/crates/spikard-http/src/lib.rs +0 -548
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -230
- data/vendor/crates/spikard-http/src/lifecycle.rs +0 -1193
- data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -560
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -912
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -513
- data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -768
- data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -535
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -1363
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -667
- data/vendor/crates/spikard-http/src/query_parser.rs +0 -793
- data/vendor/crates/spikard-http/src/response.rs +0 -720
- data/vendor/crates/spikard-http/src/server/fast_router.rs +0 -186
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +0 -858
- data/vendor/crates/spikard-http/src/server/handler.rs +0 -1661
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -253
- data/vendor/crates/spikard-http/src/server/mod.rs +0 -1649
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -871
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +0 -618
- data/vendor/crates/spikard-http/src/sse.rs +0 -1409
- data/vendor/crates/spikard-http/src/testing/form.rs +0 -52
- data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -64
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -787
- data/vendor/crates/spikard-http/src/testing.rs +0 -617
- data/vendor/crates/spikard-http/src/websocket.rs +0 -1477
- data/vendor/crates/spikard-http/tests/auth_integration.rs +0 -645
- data/vendor/crates/spikard-http/tests/background_behavior.rs +0 -832
- data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +0 -1012
- data/vendor/crates/spikard-http/tests/common/handlers.rs +0 -309
- data/vendor/crates/spikard-http/tests/common/mod.rs +0 -33
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +0 -628
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +0 -162
- data/vendor/crates/spikard-http/tests/di_integration.rs +0 -192
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +0 -5
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +0 -430
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +0 -738
- data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +0 -652
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +0 -334
- data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +0 -532
- data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +0 -495
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +0 -974
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +0 -1093
- data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +0 -389
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +0 -656
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +0 -513
- data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +0 -328
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +0 -314
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -200
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +0 -83
- data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +0 -464
- data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +0 -286
- data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +0 -118
- data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +0 -99
- data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +0 -204
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +0 -421
- data/vendor/crates/spikard-http/tests/server_router_behavior.rs +0 -121
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +0 -620
- data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +0 -584
- data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +0 -130
- data/vendor/crates/spikard-http/tests/test_client_requests.rs +0 -167
- data/vendor/crates/spikard-http/tests/testing_helpers.rs +0 -87
- data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +0 -155
- data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +0 -82
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +0 -663
- data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +0 -440
- data/vendor/crates/spikard-http/tests/websocket_integration.rs +0 -150
- data/vendor/crates/spikard-rb/Cargo.toml +0 -68
- data/vendor/crates/spikard-rb/build.rs +0 -200
- data/vendor/crates/spikard-rb/src/background.rs +0 -63
- data/vendor/crates/spikard-rb/src/config/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/config/server_config.rs +0 -401
- data/vendor/crates/spikard-rb/src/conversion.rs +0 -688
- data/vendor/crates/spikard-rb/src/di/builder.rs +0 -100
- data/vendor/crates/spikard-rb/src/di/mod.rs +0 -375
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -834
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +0 -13
- data/vendor/crates/spikard-rb/src/gvl.rs +0 -80
- data/vendor/crates/spikard-rb/src/handler.rs +0 -699
- data/vendor/crates/spikard-rb/src/integration/mod.rs +0 -3
- data/vendor/crates/spikard-rb/src/lib.rs +0 -2264
- data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -303
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +0 -507
- data/vendor/crates/spikard-rb/src/request.rs +0 -439
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +0 -344
- data/vendor/crates/spikard-rb/src/server.rs +0 -307
- data/vendor/crates/spikard-rb/src/sse.rs +0 -231
- data/vendor/crates/spikard-rb/src/testing/client.rs +0 -698
- data/vendor/crates/spikard-rb/src/testing/mod.rs +0 -7
- data/vendor/crates/spikard-rb/src/testing/sse.rs +0 -108
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +0 -573
- data/vendor/crates/spikard-rb/src/websocket.rs +0 -475
- data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -25
- data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
data/lib/spikard/config.rb
DELETED
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Spikard
|
|
4
|
-
# Compression configuration for response compression middleware.
|
|
5
|
-
#
|
|
6
|
-
# Spikard supports gzip and brotli compression for responses.
|
|
7
|
-
# Compression is applied based on Accept-Encoding headers.
|
|
8
|
-
#
|
|
9
|
-
# @example
|
|
10
|
-
# compression = CompressionConfig.new(
|
|
11
|
-
# gzip: true,
|
|
12
|
-
# brotli: true,
|
|
13
|
-
# min_size: 1024,
|
|
14
|
-
# quality: 6
|
|
15
|
-
# )
|
|
16
|
-
class CompressionConfig
|
|
17
|
-
attr_accessor :gzip, :brotli, :min_size, :quality
|
|
18
|
-
|
|
19
|
-
# @param gzip [Boolean] Enable gzip compression (default: true)
|
|
20
|
-
# @param brotli [Boolean] Enable brotli compression (default: true)
|
|
21
|
-
# @param min_size [Integer] Minimum response size in bytes to compress (default: 1024)
|
|
22
|
-
# @param quality [Integer] Compression quality level (0-11 for brotli, 0-9 for gzip, default: 6)
|
|
23
|
-
def initialize(gzip: true, brotli: true, min_size: 1024, quality: 6)
|
|
24
|
-
@gzip = normalize_boolean('gzip', gzip)
|
|
25
|
-
@brotli = normalize_boolean('brotli', brotli)
|
|
26
|
-
@min_size = normalize_nonnegative_integer('min_size', min_size)
|
|
27
|
-
@quality = normalize_quality(quality)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def normalize_boolean(name, value)
|
|
33
|
-
return value if [true, false].include?(value)
|
|
34
|
-
|
|
35
|
-
raise ArgumentError, "#{name} must be a boolean"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def normalize_nonnegative_integer(name, value)
|
|
39
|
-
raise ArgumentError, "#{name} must be an Integer" unless value.is_a?(Integer)
|
|
40
|
-
return value if value >= 0
|
|
41
|
-
|
|
42
|
-
raise ArgumentError, "#{name} must be >= 0"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def normalize_quality(value)
|
|
46
|
-
raise ArgumentError, 'quality must be a number' unless value.is_a?(Integer) || value.is_a?(Float)
|
|
47
|
-
|
|
48
|
-
normalized = value.to_i
|
|
49
|
-
return normalized if normalized.between?(0, 11)
|
|
50
|
-
|
|
51
|
-
raise ArgumentError, 'quality must be between 0 and 11'
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Rate limiting configuration using Generic Cell Rate Algorithm (GCRA).
|
|
56
|
-
#
|
|
57
|
-
# By default, rate limits are applied per IP address.
|
|
58
|
-
#
|
|
59
|
-
# @example
|
|
60
|
-
# rate_limit = RateLimitConfig.new(
|
|
61
|
-
# per_second: 100,
|
|
62
|
-
# burst: 200,
|
|
63
|
-
# ip_based: true
|
|
64
|
-
# )
|
|
65
|
-
class RateLimitConfig
|
|
66
|
-
attr_accessor :per_second, :burst, :ip_based
|
|
67
|
-
|
|
68
|
-
# @param per_second [Integer] Maximum requests per second
|
|
69
|
-
# @param burst [Integer] Burst allowance - allows temporary spikes
|
|
70
|
-
# @param ip_based [Boolean] Apply rate limits per IP address (default: true)
|
|
71
|
-
def initialize(per_second:, burst:, ip_based: true)
|
|
72
|
-
@per_second = per_second
|
|
73
|
-
@burst = burst
|
|
74
|
-
@ip_based = ip_based
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# JWT authentication configuration.
|
|
79
|
-
#
|
|
80
|
-
# Validates JWT tokens using the specified secret and algorithm.
|
|
81
|
-
# Tokens are expected in the Authorization header as "Bearer <token>".
|
|
82
|
-
#
|
|
83
|
-
# Supported algorithms:
|
|
84
|
-
# - HS256, HS384, HS512 (HMAC with SHA)
|
|
85
|
-
# - RS256, RS384, RS512 (RSA signatures)
|
|
86
|
-
# - ES256, ES384, ES512 (ECDSA signatures)
|
|
87
|
-
# - PS256, PS384, PS512 (RSA-PSS signatures)
|
|
88
|
-
#
|
|
89
|
-
# @example
|
|
90
|
-
# jwt = JwtConfig.new(
|
|
91
|
-
# secret: 'your-secret-key',
|
|
92
|
-
# algorithm: 'HS256',
|
|
93
|
-
# audience: ['api.example.com'],
|
|
94
|
-
# issuer: 'auth.example.com',
|
|
95
|
-
# leeway: 30
|
|
96
|
-
# )
|
|
97
|
-
class JwtConfig
|
|
98
|
-
attr_accessor :secret, :algorithm, :audience, :issuer, :leeway
|
|
99
|
-
|
|
100
|
-
# @param secret [String] Secret key for JWT validation
|
|
101
|
-
# @param algorithm [String] JWT algorithm (default: "HS256")
|
|
102
|
-
# @param audience [Array<String>, nil] Expected audience claim(s)
|
|
103
|
-
# @param issuer [String, nil] Expected issuer claim
|
|
104
|
-
# @param leeway [Integer] Time leeway in seconds for exp/nbf/iat claims (default: 0)
|
|
105
|
-
def initialize(secret:, algorithm: 'HS256', audience: nil, issuer: nil, leeway: 0)
|
|
106
|
-
@secret = secret
|
|
107
|
-
@algorithm = algorithm
|
|
108
|
-
@audience = audience
|
|
109
|
-
@issuer = issuer
|
|
110
|
-
@leeway = leeway
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# API key authentication configuration.
|
|
115
|
-
#
|
|
116
|
-
# Validates API keys from request headers. Keys are matched exactly.
|
|
117
|
-
#
|
|
118
|
-
# @example
|
|
119
|
-
# api_key = ApiKeyConfig.new(
|
|
120
|
-
# keys: ['key-1', 'key-2', 'key-3'],
|
|
121
|
-
# header_name: 'X-API-Key'
|
|
122
|
-
# )
|
|
123
|
-
class ApiKeyConfig
|
|
124
|
-
attr_accessor :keys, :header_name
|
|
125
|
-
|
|
126
|
-
# @param keys [Array<String>] List of valid API keys
|
|
127
|
-
# @param header_name [String] HTTP header name to check for API key (default: "X-API-Key")
|
|
128
|
-
def initialize(keys:, header_name: 'X-API-Key')
|
|
129
|
-
@keys = keys
|
|
130
|
-
@header_name = header_name
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Static file serving configuration.
|
|
135
|
-
#
|
|
136
|
-
# Serves files from a directory at a given route prefix.
|
|
137
|
-
# Multiple static file configurations can be registered.
|
|
138
|
-
#
|
|
139
|
-
# @example
|
|
140
|
-
# static = StaticFilesConfig.new(
|
|
141
|
-
# directory: './public',
|
|
142
|
-
# route_prefix: '/static',
|
|
143
|
-
# index_file: true,
|
|
144
|
-
# cache_control: 'public, max-age=3600'
|
|
145
|
-
# )
|
|
146
|
-
class StaticFilesConfig
|
|
147
|
-
attr_accessor :directory, :route_prefix, :index_file, :cache_control
|
|
148
|
-
|
|
149
|
-
# @param directory [String] Directory path containing static files
|
|
150
|
-
# @param route_prefix [String] URL prefix for serving static files (e.g., "/static")
|
|
151
|
-
# @param index_file [Boolean] Serve index.html for directory requests (default: true)
|
|
152
|
-
# @param cache_control [String, nil] Optional Cache-Control header value (e.g., "public, max-age=3600")
|
|
153
|
-
def initialize(directory:, route_prefix:, index_file: true, cache_control: nil)
|
|
154
|
-
@directory = directory
|
|
155
|
-
@route_prefix = route_prefix
|
|
156
|
-
@index_file = index_file
|
|
157
|
-
@cache_control = cache_control
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# Contact information for OpenAPI documentation.
|
|
162
|
-
#
|
|
163
|
-
# @example
|
|
164
|
-
# contact = ContactInfo.new(
|
|
165
|
-
# name: 'API Team',
|
|
166
|
-
# email: 'api@example.com',
|
|
167
|
-
# url: 'https://example.com'
|
|
168
|
-
# )
|
|
169
|
-
class ContactInfo
|
|
170
|
-
attr_accessor :name, :email, :url
|
|
171
|
-
|
|
172
|
-
# @param name [String, nil] Name of the contact person/organization
|
|
173
|
-
# @param email [String, nil] Email address for contact
|
|
174
|
-
# @param url [String, nil] URL for contact information
|
|
175
|
-
def initialize(name: nil, email: nil, url: nil)
|
|
176
|
-
@name = name
|
|
177
|
-
@email = email
|
|
178
|
-
@url = url
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# License information for OpenAPI documentation.
|
|
183
|
-
#
|
|
184
|
-
# @example
|
|
185
|
-
# license = LicenseInfo.new(
|
|
186
|
-
# name: 'MIT',
|
|
187
|
-
# url: 'https://opensource.org/licenses/MIT'
|
|
188
|
-
# )
|
|
189
|
-
class LicenseInfo
|
|
190
|
-
attr_accessor :name, :url
|
|
191
|
-
|
|
192
|
-
# @param name [String] License name (e.g., "MIT", "Apache 2.0")
|
|
193
|
-
# @param url [String, nil] URL to the full license text
|
|
194
|
-
def initialize(name:, url: nil)
|
|
195
|
-
@name = name
|
|
196
|
-
@url = url
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Server information for OpenAPI documentation.
|
|
201
|
-
#
|
|
202
|
-
# Multiple servers can be specified for different environments.
|
|
203
|
-
#
|
|
204
|
-
# @example
|
|
205
|
-
# server = ServerInfo.new(
|
|
206
|
-
# url: 'https://api.example.com',
|
|
207
|
-
# description: 'Production'
|
|
208
|
-
# )
|
|
209
|
-
class ServerInfo
|
|
210
|
-
attr_accessor :url, :description
|
|
211
|
-
|
|
212
|
-
# @param url [String] Server URL (e.g., "https://api.example.com")
|
|
213
|
-
# @param description [String, nil] Description of the server (e.g., "Production", "Staging")
|
|
214
|
-
def initialize(url:, description: nil)
|
|
215
|
-
@url = url
|
|
216
|
-
@description = description
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Security scheme configuration for OpenAPI documentation.
|
|
221
|
-
#
|
|
222
|
-
# Supports HTTP (Bearer/JWT) and API Key authentication schemes.
|
|
223
|
-
#
|
|
224
|
-
# @example HTTP Bearer
|
|
225
|
-
# scheme = SecuritySchemeInfo.new(
|
|
226
|
-
# type: 'http',
|
|
227
|
-
# scheme: 'bearer',
|
|
228
|
-
# bearer_format: 'JWT'
|
|
229
|
-
# )
|
|
230
|
-
#
|
|
231
|
-
# @example API Key
|
|
232
|
-
# scheme = SecuritySchemeInfo.new(
|
|
233
|
-
# type: 'apiKey',
|
|
234
|
-
# location: 'header',
|
|
235
|
-
# name: 'X-API-Key'
|
|
236
|
-
# )
|
|
237
|
-
class SecuritySchemeInfo
|
|
238
|
-
attr_accessor :type, :scheme, :bearer_format, :location, :name
|
|
239
|
-
|
|
240
|
-
# @param type [String] Security scheme type ("http" or "apiKey")
|
|
241
|
-
# @param scheme [String, nil] HTTP scheme (e.g., "bearer", "basic") - for type="http"
|
|
242
|
-
# @param bearer_format [String, nil] Format hint for Bearer tokens (e.g., "JWT") - for type="http"
|
|
243
|
-
# @param location [String, nil] Where to look for the API key ("header", "query", or "cookie") - for type="apiKey"
|
|
244
|
-
# @param name [String, nil] Parameter name (e.g., "X-API-Key") - for type="apiKey"
|
|
245
|
-
def initialize(type:, scheme: nil, bearer_format: nil, location: nil, name: nil)
|
|
246
|
-
@type = type
|
|
247
|
-
@scheme = scheme
|
|
248
|
-
@bearer_format = bearer_format
|
|
249
|
-
@location = location
|
|
250
|
-
@name = name
|
|
251
|
-
|
|
252
|
-
validate!
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
private
|
|
256
|
-
|
|
257
|
-
def validate!
|
|
258
|
-
case @type
|
|
259
|
-
when 'http'
|
|
260
|
-
raise ArgumentError, 'scheme is required for type="http"' if @scheme.nil?
|
|
261
|
-
when 'apiKey'
|
|
262
|
-
raise ArgumentError, 'location and name are required for type="apiKey"' if @location.nil? || @name.nil?
|
|
263
|
-
else
|
|
264
|
-
raise ArgumentError, "type must be 'http' or 'apiKey', got: #{@type.inspect}"
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# OpenAPI 3.1.0 documentation configuration.
|
|
270
|
-
#
|
|
271
|
-
# Spikard can automatically generate OpenAPI documentation from your routes.
|
|
272
|
-
# When enabled, it serves:
|
|
273
|
-
# - Swagger UI at /docs (customizable)
|
|
274
|
-
# - Redoc at /redoc (customizable)
|
|
275
|
-
# - OpenAPI JSON spec at /openapi.json (customizable)
|
|
276
|
-
#
|
|
277
|
-
# Security schemes are auto-detected from middleware configuration.
|
|
278
|
-
# Schemas are generated from your route type hints and validation.
|
|
279
|
-
#
|
|
280
|
-
# @example
|
|
281
|
-
# openapi = OpenApiConfig.new(
|
|
282
|
-
# enabled: true,
|
|
283
|
-
# title: 'My API',
|
|
284
|
-
# version: '1.0.0',
|
|
285
|
-
# description: 'A great API built with Spikard',
|
|
286
|
-
# contact: ContactInfo.new(
|
|
287
|
-
# name: 'API Team',
|
|
288
|
-
# email: 'api@example.com',
|
|
289
|
-
# url: 'https://example.com'
|
|
290
|
-
# ),
|
|
291
|
-
# license: LicenseInfo.new(
|
|
292
|
-
# name: 'MIT',
|
|
293
|
-
# url: 'https://opensource.org/licenses/MIT'
|
|
294
|
-
# ),
|
|
295
|
-
# servers: [
|
|
296
|
-
# ServerInfo.new(url: 'https://api.example.com', description: 'Production'),
|
|
297
|
-
# ServerInfo.new(url: 'http://localhost:8000', description: 'Development')
|
|
298
|
-
# ]
|
|
299
|
-
# )
|
|
300
|
-
class OpenApiConfig
|
|
301
|
-
attr_accessor :enabled, :title, :version, :description,
|
|
302
|
-
:swagger_ui_path, :redoc_path, :openapi_json_path,
|
|
303
|
-
:contact, :license, :servers, :security_schemes
|
|
304
|
-
|
|
305
|
-
# @param enabled [Boolean] Enable OpenAPI generation (default: false for zero overhead)
|
|
306
|
-
# @param title [String] API title (default: "API")
|
|
307
|
-
# @param version [String] API version (default: "1.0.0")
|
|
308
|
-
# @param description [String, nil] API description (supports Markdown)
|
|
309
|
-
# @param swagger_ui_path [String] Path to serve Swagger UI (default: "/docs")
|
|
310
|
-
# @param redoc_path [String] Path to serve Redoc (default: "/redoc")
|
|
311
|
-
# @param openapi_json_path [String] Path to serve OpenAPI JSON spec (default: "/openapi.json")
|
|
312
|
-
# @param contact [ContactInfo, nil] Contact information for the API
|
|
313
|
-
# @param license [LicenseInfo, nil] License information for the API
|
|
314
|
-
# @param servers [Array<ServerInfo>] List of server URLs for different environments (default: [])
|
|
315
|
-
# @param security_schemes [Hash<String, SecuritySchemeInfo>] Custom security schemes (auto-detected if not provided)
|
|
316
|
-
def initialize(
|
|
317
|
-
enabled: false,
|
|
318
|
-
title: 'API',
|
|
319
|
-
version: '1.0.0',
|
|
320
|
-
description: nil,
|
|
321
|
-
swagger_ui_path: '/docs',
|
|
322
|
-
redoc_path: '/redoc',
|
|
323
|
-
openapi_json_path: '/openapi.json',
|
|
324
|
-
contact: nil,
|
|
325
|
-
license: nil,
|
|
326
|
-
servers: [],
|
|
327
|
-
security_schemes: {}
|
|
328
|
-
)
|
|
329
|
-
@enabled = enabled
|
|
330
|
-
@title = title
|
|
331
|
-
@version = version
|
|
332
|
-
@description = description
|
|
333
|
-
@swagger_ui_path = swagger_ui_path
|
|
334
|
-
@redoc_path = redoc_path
|
|
335
|
-
@openapi_json_path = openapi_json_path
|
|
336
|
-
@contact = contact
|
|
337
|
-
@license = license
|
|
338
|
-
@servers = servers
|
|
339
|
-
@security_schemes = security_schemes
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
# JSON-RPC endpoint configuration.
|
|
344
|
-
class JsonRpcConfig
|
|
345
|
-
attr_accessor :enabled, :endpoint_path, :enable_batch, :max_batch_size
|
|
346
|
-
|
|
347
|
-
# @param enabled [Boolean] Enable JSON-RPC endpoint registration (default: true)
|
|
348
|
-
# @param endpoint_path [String] JSON-RPC endpoint path (default: "/rpc")
|
|
349
|
-
# @param enable_batch [Boolean] Enable JSON-RPC batch support (default: true)
|
|
350
|
-
# @param max_batch_size [Integer] Maximum batch size (default: 100)
|
|
351
|
-
def initialize(enabled: true, endpoint_path: '/rpc', enable_batch: true, max_batch_size: 100)
|
|
352
|
-
@enabled = normalize_boolean('enabled', enabled)
|
|
353
|
-
@endpoint_path = endpoint_path
|
|
354
|
-
@enable_batch = normalize_boolean('enable_batch', enable_batch)
|
|
355
|
-
@max_batch_size = normalize_positive_integer('max_batch_size', max_batch_size)
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
private
|
|
359
|
-
|
|
360
|
-
def normalize_boolean(name, value)
|
|
361
|
-
return value if [true, false].include?(value)
|
|
362
|
-
|
|
363
|
-
raise ArgumentError, "#{name} must be a boolean"
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
def normalize_positive_integer(name, value)
|
|
367
|
-
raise ArgumentError, "#{name} must be an Integer" unless value.is_a?(Integer)
|
|
368
|
-
return value if value.positive?
|
|
369
|
-
|
|
370
|
-
raise ArgumentError, "#{name} must be > 0"
|
|
371
|
-
end
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
# Complete server configuration for Spikard.
|
|
375
|
-
#
|
|
376
|
-
# This is the main configuration object that controls all aspects of the server
|
|
377
|
-
# including network settings, middleware, authentication, and more.
|
|
378
|
-
#
|
|
379
|
-
# @example
|
|
380
|
-
# config = ServerConfig.new(
|
|
381
|
-
# host: '0.0.0.0',
|
|
382
|
-
# port: 8080,
|
|
383
|
-
# workers: 4,
|
|
384
|
-
# compression: CompressionConfig.new(quality: 9),
|
|
385
|
-
# rate_limit: RateLimitConfig.new(per_second: 100, burst: 200),
|
|
386
|
-
# static_files: [
|
|
387
|
-
# StaticFilesConfig.new(
|
|
388
|
-
# directory: './public',
|
|
389
|
-
# route_prefix: '/static'
|
|
390
|
-
# )
|
|
391
|
-
# ],
|
|
392
|
-
# openapi: OpenApiConfig.new(
|
|
393
|
-
# enabled: true,
|
|
394
|
-
# title: 'My API',
|
|
395
|
-
# version: '1.0.0'
|
|
396
|
-
# )
|
|
397
|
-
# )
|
|
398
|
-
class ServerConfig
|
|
399
|
-
attr_accessor :host, :port, :workers,
|
|
400
|
-
:enable_request_id, :max_body_size, :request_timeout,
|
|
401
|
-
:compression, :rate_limit, :jwt_auth, :api_key_auth,
|
|
402
|
-
:static_files, :graceful_shutdown, :shutdown_timeout,
|
|
403
|
-
:openapi, :jsonrpc
|
|
404
|
-
|
|
405
|
-
# @param host [String] Host address to bind to (default: "127.0.0.1")
|
|
406
|
-
# @param port [Integer] Port number to listen on (default: 8000, range: 1-65535)
|
|
407
|
-
# @param workers [Integer] Number of worker processes (default: 1)
|
|
408
|
-
# @param enable_request_id [Boolean] Add X-Request-ID header to responses (default: true)
|
|
409
|
-
# @param max_body_size [Integer, nil] Maximum request body size in bytes (default: 10MB, nil for unlimited)
|
|
410
|
-
# @param request_timeout [Integer, nil] Request timeout in seconds (default: 30, nil for no timeout)
|
|
411
|
-
# @param compression [CompressionConfig, nil] Response compression configuration (default: enabled with defaults)
|
|
412
|
-
# @param rate_limit [RateLimitConfig, nil] Rate limiting configuration (default: nil/disabled)
|
|
413
|
-
# @param jwt_auth [JwtConfig, nil] JWT authentication configuration (default: nil/disabled)
|
|
414
|
-
# @param api_key_auth [ApiKeyConfig, nil] API key authentication configuration (default: nil/disabled)
|
|
415
|
-
# @param static_files [Array<StaticFilesConfig>] List of static file serving configurations (default: [])
|
|
416
|
-
# @param graceful_shutdown [Boolean] Enable graceful shutdown (default: true)
|
|
417
|
-
# @param shutdown_timeout [Integer] Graceful shutdown timeout in seconds (default: 30)
|
|
418
|
-
# @param openapi [OpenApiConfig, nil] OpenAPI configuration (default: nil/disabled)
|
|
419
|
-
# @param jsonrpc [JsonRpcConfig, nil] JSON-RPC configuration (default: nil/disabled)
|
|
420
|
-
def initialize(
|
|
421
|
-
host: '127.0.0.1',
|
|
422
|
-
port: 8000,
|
|
423
|
-
workers: 1,
|
|
424
|
-
enable_request_id: true,
|
|
425
|
-
max_body_size: 10 * 1024 * 1024, # 10MB
|
|
426
|
-
request_timeout: 30,
|
|
427
|
-
compression: CompressionConfig.new,
|
|
428
|
-
rate_limit: nil,
|
|
429
|
-
jwt_auth: nil,
|
|
430
|
-
api_key_auth: nil,
|
|
431
|
-
static_files: [],
|
|
432
|
-
graceful_shutdown: true,
|
|
433
|
-
shutdown_timeout: 30,
|
|
434
|
-
openapi: nil,
|
|
435
|
-
jsonrpc: nil
|
|
436
|
-
)
|
|
437
|
-
@host = host
|
|
438
|
-
@port = normalize_port(port)
|
|
439
|
-
@workers = normalize_workers(workers)
|
|
440
|
-
@enable_request_id = normalize_boolean('enable_request_id', enable_request_id)
|
|
441
|
-
@max_body_size = normalize_optional_nonnegative_integer('max_body_size', max_body_size)
|
|
442
|
-
@request_timeout = normalize_timeout('request_timeout', request_timeout)
|
|
443
|
-
@compression = compression
|
|
444
|
-
@rate_limit = rate_limit
|
|
445
|
-
@jwt_auth = jwt_auth
|
|
446
|
-
@api_key_auth = api_key_auth
|
|
447
|
-
@static_files = normalize_static_files(static_files)
|
|
448
|
-
@graceful_shutdown = normalize_boolean('graceful_shutdown', graceful_shutdown)
|
|
449
|
-
@shutdown_timeout = normalize_timeout('shutdown_timeout', shutdown_timeout)
|
|
450
|
-
@openapi = openapi
|
|
451
|
-
@jsonrpc = jsonrpc
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
private
|
|
455
|
-
|
|
456
|
-
def normalize_port(port)
|
|
457
|
-
raise ArgumentError, 'port must be an Integer' unless port.is_a?(Integer)
|
|
458
|
-
return port if port.between?(1, 65_535)
|
|
459
|
-
|
|
460
|
-
raise ArgumentError, 'port must be between 1 and 65535'
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
def normalize_workers(workers)
|
|
464
|
-
raise ArgumentError, 'workers must be an Integer' unless workers.is_a?(Integer)
|
|
465
|
-
return workers if workers >= 1
|
|
466
|
-
|
|
467
|
-
raise ArgumentError, 'workers must be >= 1'
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
def normalize_boolean(name, value)
|
|
471
|
-
return value if [true, false].include?(value)
|
|
472
|
-
|
|
473
|
-
raise ArgumentError, "#{name} must be a boolean"
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
def normalize_optional_nonnegative_integer(name, value)
|
|
477
|
-
return nil if value.nil?
|
|
478
|
-
raise ArgumentError, "#{name} must be an Integer" unless value.is_a?(Integer)
|
|
479
|
-
return value if value >= 0
|
|
480
|
-
|
|
481
|
-
raise ArgumentError, "#{name} must be >= 0"
|
|
482
|
-
end
|
|
483
|
-
|
|
484
|
-
def normalize_timeout(name, value)
|
|
485
|
-
return nil if value.nil?
|
|
486
|
-
raise ArgumentError, "#{name} must be a number" unless value.is_a?(Integer) || value.is_a?(Float)
|
|
487
|
-
|
|
488
|
-
normalized = value.to_i
|
|
489
|
-
return normalized if normalized >= 0
|
|
490
|
-
|
|
491
|
-
raise ArgumentError, "#{name} must be >= 0"
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
def normalize_static_files(static_files)
|
|
495
|
-
return [] if static_files.nil?
|
|
496
|
-
raise ArgumentError, 'static_files must be an Array' unless static_files.is_a?(Array)
|
|
497
|
-
|
|
498
|
-
static_files.each do |entry|
|
|
499
|
-
next if entry.is_a?(StaticFilesConfig)
|
|
500
|
-
|
|
501
|
-
raise ArgumentError, 'static_files entries must be StaticFilesConfig'
|
|
502
|
-
end
|
|
503
|
-
static_files
|
|
504
|
-
end
|
|
505
|
-
end
|
|
506
|
-
end
|
data/lib/spikard/converters.rb
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Spikard
|
|
4
|
-
# Conversion helpers between native Rust values and Ruby types.
|
|
5
|
-
module Converters
|
|
6
|
-
module_function
|
|
7
|
-
|
|
8
|
-
# No-op conversion now that Rust materialises UploadFile.
|
|
9
|
-
def convert_handler_body(body)
|
|
10
|
-
body
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
data/lib/spikard/grpc.rb
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Spikard
|
|
4
|
-
# gRPC support for Spikard
|
|
5
|
-
#
|
|
6
|
-
# This module provides Ruby bindings for handling gRPC requests through
|
|
7
|
-
# Spikard's Rust-based gRPC runtime. Handlers receive protobuf messages
|
|
8
|
-
# as binary strings and use the google-protobuf gem for serialization.
|
|
9
|
-
#
|
|
10
|
-
# @example Basic gRPC handler
|
|
11
|
-
# require 'spikard/grpc'
|
|
12
|
-
# require 'user_pb' # Generated protobuf
|
|
13
|
-
#
|
|
14
|
-
# class UserServiceHandler < Spikard::Grpc::Handler
|
|
15
|
-
# def handle_request(request)
|
|
16
|
-
# case request.method_name
|
|
17
|
-
# when 'GetUser'
|
|
18
|
-
# # Deserialize request
|
|
19
|
-
# req = Example::GetUserRequest.decode(request.payload)
|
|
20
|
-
#
|
|
21
|
-
# # Process request
|
|
22
|
-
# user = Example::User.new(id: req.id, name: 'John Doe')
|
|
23
|
-
#
|
|
24
|
-
# # Serialize response
|
|
25
|
-
# Spikard::Grpc::Response.new(payload: Example::User.encode(user))
|
|
26
|
-
# else
|
|
27
|
-
# raise "Unknown method: #{request.method_name}"
|
|
28
|
-
# end
|
|
29
|
-
# end
|
|
30
|
-
# end
|
|
31
|
-
module Grpc
|
|
32
|
-
# gRPC request object
|
|
33
|
-
#
|
|
34
|
-
# Represents an incoming gRPC request with service/method information
|
|
35
|
-
# and a binary protobuf payload.
|
|
36
|
-
#
|
|
37
|
-
# @!attribute [r] service_name
|
|
38
|
-
# @return [String] Fully qualified service name (e.g., "mypackage.MyService")
|
|
39
|
-
# @!attribute [r] method_name
|
|
40
|
-
# @return [String] Method name (e.g., "GetUser")
|
|
41
|
-
# @!attribute [r] payload
|
|
42
|
-
# @return [String] Binary string containing serialized protobuf message
|
|
43
|
-
# @!attribute [r] metadata
|
|
44
|
-
# @return [Hash<String, String>] gRPC metadata (headers)
|
|
45
|
-
# rubocop:disable Lint/EmptyClass -- Implementation in Rust via FFI
|
|
46
|
-
class Request
|
|
47
|
-
# These methods are implemented in Rust via Magnus FFI.
|
|
48
|
-
# See: crates/spikard-rb/src/grpc/handler.rs for implementation details.
|
|
49
|
-
end
|
|
50
|
-
# rubocop:enable Lint/EmptyClass
|
|
51
|
-
|
|
52
|
-
# gRPC response object
|
|
53
|
-
#
|
|
54
|
-
# Used to return gRPC responses from handlers. The payload should be
|
|
55
|
-
# a binary string containing a serialized protobuf message.
|
|
56
|
-
#
|
|
57
|
-
# @example Creating a response
|
|
58
|
-
# user = Example::User.new(id: 1, name: 'Alice')
|
|
59
|
-
# response = Spikard::Grpc::Response.new(payload: Example::User.encode(user))
|
|
60
|
-
#
|
|
61
|
-
# @example Adding metadata
|
|
62
|
-
# response = Spikard::Grpc::Response.new(payload: encoded_message)
|
|
63
|
-
# response.metadata = { 'x-custom-header' => 'value' }
|
|
64
|
-
class Response
|
|
65
|
-
# @!attribute [w] metadata
|
|
66
|
-
# @return [Hash<String, String>] gRPC metadata to include in response
|
|
67
|
-
|
|
68
|
-
# Create a new gRPC response
|
|
69
|
-
#
|
|
70
|
-
# @param payload [String] Binary string containing serialized protobuf message
|
|
71
|
-
# @raise [ArgumentError] if payload is not a String
|
|
72
|
-
#
|
|
73
|
-
# Note: Implementation in Rust (Magnus FFI)
|
|
74
|
-
# See: crates/spikard-rb/src/grpc/handler.rs
|
|
75
|
-
|
|
76
|
-
# Create an error response
|
|
77
|
-
#
|
|
78
|
-
# @param message [String] Error message
|
|
79
|
-
# @param metadata [Hash<String, String>] Optional gRPC metadata
|
|
80
|
-
# @return [Response] A response with error status
|
|
81
|
-
#
|
|
82
|
-
# @example
|
|
83
|
-
# response = Spikard::Grpc::Response.error('Method not implemented')
|
|
84
|
-
def self.error(message, metadata = {})
|
|
85
|
-
error_metadata = metadata.merge(
|
|
86
|
-
'grpc-status' => 'INTERNAL',
|
|
87
|
-
'grpc-message' => message
|
|
88
|
-
)
|
|
89
|
-
response = new(payload: '')
|
|
90
|
-
response.metadata = error_metadata
|
|
91
|
-
response
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Base class for gRPC handlers
|
|
96
|
-
#
|
|
97
|
-
# Subclass this to implement gRPC service handlers. Override
|
|
98
|
-
# {#handle_request} to process incoming requests.
|
|
99
|
-
#
|
|
100
|
-
# @example Implementing a handler
|
|
101
|
-
# class MyServiceHandler < Spikard::Grpc::Handler
|
|
102
|
-
# def handle_request(request)
|
|
103
|
-
# case request.method_name
|
|
104
|
-
# when 'MethodOne'
|
|
105
|
-
# # Handle MethodOne
|
|
106
|
-
# req = MyPackage::MethodOneRequest.decode(request.payload)
|
|
107
|
-
# resp = MyPackage::MethodOneResponse.new(...)
|
|
108
|
-
# Spikard::Grpc::Response.new(payload: MyPackage::MethodOneResponse.encode(resp))
|
|
109
|
-
# when 'MethodTwo'
|
|
110
|
-
# # Handle MethodTwo
|
|
111
|
-
# # ...
|
|
112
|
-
# else
|
|
113
|
-
# raise "Unknown method: #{request.method_name}"
|
|
114
|
-
# end
|
|
115
|
-
# end
|
|
116
|
-
# end
|
|
117
|
-
class Handler
|
|
118
|
-
# Handle a gRPC request
|
|
119
|
-
#
|
|
120
|
-
# This method must be overridden by subclasses to implement the
|
|
121
|
-
# actual request handling logic.
|
|
122
|
-
#
|
|
123
|
-
# @param request [Spikard::Grpc::Request] The incoming gRPC request
|
|
124
|
-
# @return [Spikard::Grpc::Response] The gRPC response
|
|
125
|
-
# @raise [NotImplementedError] if not overridden by subclass
|
|
126
|
-
def handle_request(request)
|
|
127
|
-
raise NotImplementedError, "#{self.class}#handle_request must be implemented"
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Service registry for gRPC handlers
|
|
132
|
-
#
|
|
133
|
-
# Manages registration and lookup of gRPC service handlers.
|
|
134
|
-
# Handlers are registered by service name and method.
|
|
135
|
-
#
|
|
136
|
-
# @example Registering a handler
|
|
137
|
-
# service = Spikard::Grpc::Service.new
|
|
138
|
-
# handler = UserServiceHandler.new
|
|
139
|
-
# service.register_handler('mypackage.UserService', handler)
|
|
140
|
-
class Service
|
|
141
|
-
def initialize
|
|
142
|
-
@handlers = {}
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Register a gRPC handler for a service
|
|
146
|
-
#
|
|
147
|
-
# @param service_name [String] Fully qualified service name
|
|
148
|
-
# @param handler [Spikard::Grpc::Handler] Handler instance
|
|
149
|
-
# @raise [ArgumentError] if service_name is invalid or handler doesn't respond to handle_request
|
|
150
|
-
def register_handler(service_name, handler)
|
|
151
|
-
raise ArgumentError, 'Service name cannot be empty' if service_name.nil? || service_name.empty?
|
|
152
|
-
|
|
153
|
-
raise ArgumentError, 'Handler must respond to :handle_request' unless handler.respond_to?(:handle_request)
|
|
154
|
-
|
|
155
|
-
@handlers[service_name] = handler
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Get a handler by service name
|
|
159
|
-
#
|
|
160
|
-
# @param service_name [String] Fully qualified service name
|
|
161
|
-
# @return [Spikard::Grpc::Handler, nil] The handler or nil if not found
|
|
162
|
-
def get_handler(service_name)
|
|
163
|
-
@handlers[service_name]
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# Get all registered service names
|
|
167
|
-
#
|
|
168
|
-
# @return [Array<String>] List of registered service names
|
|
169
|
-
def service_names
|
|
170
|
-
@handlers.keys
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Check if a service is registered
|
|
174
|
-
#
|
|
175
|
-
# @param service_name [String] Fully qualified service name
|
|
176
|
-
# @return [Boolean] true if the service is registered
|
|
177
|
-
def registered?(service_name)
|
|
178
|
-
@handlers.key?(service_name)
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|