spikard 0.3.6 → 0.5.0
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/README.md +21 -6
- data/ext/spikard_rb/Cargo.toml +2 -2
- data/lib/spikard/app.rb +33 -14
- data/lib/spikard/testing.rb +47 -12
- data/lib/spikard/version.rb +1 -1
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -0
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -0
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -0
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -0
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -0
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -0
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -0
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -0
- data/vendor/crates/spikard-core/Cargo.toml +4 -4
- data/vendor/crates/spikard-core/src/debug.rs +64 -0
- data/vendor/crates/spikard-core/src/di/container.rs +3 -27
- data/vendor/crates/spikard-core/src/di/factory.rs +1 -5
- data/vendor/crates/spikard-core/src/di/graph.rs +8 -47
- data/vendor/crates/spikard-core/src/di/mod.rs +1 -1
- data/vendor/crates/spikard-core/src/di/resolved.rs +1 -7
- data/vendor/crates/spikard-core/src/di/value.rs +2 -4
- data/vendor/crates/spikard-core/src/errors.rs +30 -0
- data/vendor/crates/spikard-core/src/http.rs +262 -0
- data/vendor/crates/spikard-core/src/lib.rs +1 -1
- data/vendor/crates/spikard-core/src/lifecycle.rs +764 -0
- data/vendor/crates/spikard-core/src/metadata.rs +389 -0
- data/vendor/crates/spikard-core/src/parameters.rs +1962 -159
- data/vendor/crates/spikard-core/src/problem.rs +34 -0
- data/vendor/crates/spikard-core/src/request_data.rs +966 -1
- data/vendor/crates/spikard-core/src/router.rs +263 -2
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +688 -0
- data/vendor/crates/spikard-core/src/{validation.rs → validation/mod.rs} +26 -268
- data/vendor/crates/spikard-http/Cargo.toml +12 -16
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -0
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -0
- data/vendor/crates/spikard-http/src/auth.rs +65 -16
- data/vendor/crates/spikard-http/src/background.rs +1614 -3
- data/vendor/crates/spikard-http/src/cors.rs +515 -0
- data/vendor/crates/spikard-http/src/debug.rs +65 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +1322 -77
- data/vendor/crates/spikard-http/src/handler_response.rs +711 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +607 -5
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +6 -0
- data/vendor/crates/spikard-http/src/lib.rs +33 -28
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +81 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +765 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +372 -117
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +836 -10
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +409 -43
- data/vendor/crates/spikard-http/src/middleware/validation.rs +513 -65
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +345 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1055 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +473 -3
- data/vendor/crates/spikard-http/src/query_parser.rs +455 -31
- data/vendor/crates/spikard-http/src/response.rs +321 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +1572 -9
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +136 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +875 -178
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +674 -23
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +599 -0
- data/vendor/crates/spikard-http/src/sse.rs +983 -21
- data/vendor/crates/spikard-http/src/testing/form.rs +38 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -2
- data/vendor/crates/spikard-http/src/testing.rs +7 -7
- data/vendor/crates/spikard-http/src/websocket.rs +1055 -4
- data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -0
- data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +26 -0
- data/vendor/crates/spikard-http/tests/di_integration.rs +192 -0
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -0
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -0
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -0
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -0
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -0
- data/vendor/crates/spikard-rb/Cargo.toml +10 -4
- data/vendor/crates/spikard-rb/build.rs +196 -5
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/{config.rs → config/server_config.rs} +100 -109
- data/vendor/crates/spikard-rb/src/conversion.rs +121 -20
- data/vendor/crates/spikard-rb/src/di/builder.rs +100 -0
- data/vendor/crates/spikard-rb/src/{di.rs → di/mod.rs} +12 -46
- data/vendor/crates/spikard-rb/src/handler.rs +100 -107
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
- data/vendor/crates/spikard-rb/src/lib.rs +467 -1428
- data/vendor/crates/spikard-rb/src/lifecycle.rs +1 -0
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +447 -0
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -0
- data/vendor/crates/spikard-rb/src/server.rs +47 -22
- data/vendor/crates/spikard-rb/src/{test_client.rs → testing/client.rs} +187 -40
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +635 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +178 -37
- metadata +46 -13
- data/vendor/crates/spikard-http/src/parameters.rs +0 -1
- data/vendor/crates/spikard-http/src/problem.rs +0 -1
- data/vendor/crates/spikard-http/src/router.rs +0 -1
- data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
- data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
- data/vendor/crates/spikard-http/src/validation.rs +0 -1
- data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
- /data/vendor/crates/spikard-rb/src/{test_sse.rs → testing/sse.rs} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab9d6a7c6017e06cb3fcc9eb7396ae3771e8e4176907d0d3a5b5af0597823310
|
|
4
|
+
data.tar.gz: e37b6cb0b218622a2abbfdb58f2a20c1bfa50cea53af005e5802573bdfe51f52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5af5495f8b41a896fbc792914b319902f61ec1807db95e648bd41afa141f6a0021ca899311bf4d4d8fd17217911ed05f77e3238e756a4a332814382d259fa944
|
|
7
|
+
data.tar.gz: 9664886689994eee085ef08e03a345e598d8529de7a6df27b96dd8626923c6f5bbff442b14730747e9ca11d4bdf9a525b77a83a762b8d004ba60ae18425b259d
|
data/README.md
CHANGED
|
@@ -4,12 +4,8 @@
|
|
|
4
4
|
[](https://rubygems.org/gems/spikard)
|
|
5
5
|
[](https://rubygems.org/gems/spikard)
|
|
6
6
|
[](https://www.ruby-lang.org/)
|
|
7
|
-
[](LICENSE)
|
|
8
7
|
[](https://codecov.io/gh/Goldziher/spikard)
|
|
9
|
-
[](https://www.npmjs.com/package/spikard)
|
|
11
|
-
[](https://crates.io/crates/spikard)
|
|
12
|
-
[](https://packagist.org/packages/spikard/spikard)
|
|
8
|
+
[](LICENSE)
|
|
13
9
|
|
|
14
10
|
High-performance Ruby web framework with a Rust core. Build REST APIs with Sinatra-style routing and zero-overhead async handlers backed by Axum and Tower-HTTP.
|
|
15
11
|
|
|
@@ -245,7 +241,7 @@ app.provide("db_pool", depends_on: ["config"], singleton: true) do |config:|
|
|
|
245
241
|
{ url: config["db_url"], driver: "pool" }
|
|
246
242
|
end
|
|
247
243
|
|
|
248
|
-
app.get "/stats" do |
|
|
244
|
+
app.get "/stats" do |request, config:, db_pool:|
|
|
249
245
|
{ db: db_pool[:url], env: config["db_url"] }
|
|
250
246
|
end
|
|
251
247
|
```
|
|
@@ -617,6 +613,25 @@ Ruby bindings use:
|
|
|
617
613
|
- Idiomatic Ruby blocks and procs
|
|
618
614
|
- GC-safe handler storage
|
|
619
615
|
|
|
616
|
+
### CI Benchmarks (2025-12-20)
|
|
617
|
+
|
|
618
|
+
Run: `snapshots/benchmarks/20397054933` (commit `25e4fdf`, oha, 50 concurrency, 10s, Linux x86_64).
|
|
619
|
+
|
|
620
|
+
| Metric | Value |
|
|
621
|
+
| --- | --- |
|
|
622
|
+
| Avg RPS (all workloads) | 8,271 |
|
|
623
|
+
| Avg latency (ms) | 6.50 |
|
|
624
|
+
|
|
625
|
+
Category breakdown:
|
|
626
|
+
|
|
627
|
+
| Category | Avg RPS | Avg latency (ms) |
|
|
628
|
+
| --- | --- | --- |
|
|
629
|
+
| path-params | 9,591 | 5.22 |
|
|
630
|
+
| json-bodies | 8,648 | 5.78 |
|
|
631
|
+
| forms | 7,989 | 6.27 |
|
|
632
|
+
| query-params | 7,984 | 6.33 |
|
|
633
|
+
| multipart | 5,604 | 10.36 |
|
|
634
|
+
|
|
620
635
|
## Examples
|
|
621
636
|
|
|
622
637
|
The [examples directory](../../examples/) contains comprehensive demonstrations:
|
data/ext/spikard_rb/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "spikard-rb-ext"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
|
|
@@ -12,6 +12,6 @@ crate-type = ["cdylib"]
|
|
|
12
12
|
[workspace]
|
|
13
13
|
|
|
14
14
|
[dependencies]
|
|
15
|
-
magnus = {
|
|
15
|
+
magnus = { version = "0.8.2", features = ["rb-sys"] }
|
|
16
16
|
# Use vendored crates for packaged gem distribution
|
|
17
17
|
spikard_rb_core = { package = "spikard-rb", path = "../../vendor/crates/spikard-rb" }
|
data/lib/spikard/app.rb
CHANGED
|
@@ -119,7 +119,7 @@ module Spikard
|
|
|
119
119
|
|
|
120
120
|
HTTP_METHODS = %w[GET POST PUT PATCH DELETE OPTIONS HEAD TRACE].freeze
|
|
121
121
|
SUPPORTED_OPTIONS = %i[request_schema response_schema parameter_schema file_params is_async cors
|
|
122
|
-
body_param_name].freeze
|
|
122
|
+
body_param_name jsonrpc_method].freeze
|
|
123
123
|
|
|
124
124
|
attr_reader :routes
|
|
125
125
|
|
|
@@ -138,19 +138,38 @@ module Spikard
|
|
|
138
138
|
handler_name = handler_name&.to_s
|
|
139
139
|
validate_route_arguments!(block, options)
|
|
140
140
|
metadata = if defined?(Spikard::Native) && Spikard::Native.respond_to?(:build_route_metadata)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
141
|
+
begin
|
|
142
|
+
Spikard::Native.build_route_metadata(
|
|
143
|
+
method,
|
|
144
|
+
path,
|
|
145
|
+
handler_name,
|
|
146
|
+
options[:request_schema],
|
|
147
|
+
options[:response_schema],
|
|
148
|
+
options[:parameter_schema],
|
|
149
|
+
options[:file_params],
|
|
150
|
+
options.fetch(:is_async, false),
|
|
151
|
+
options[:cors],
|
|
152
|
+
options[:body_param_name]&.to_s,
|
|
153
|
+
options[:jsonrpc_method],
|
|
154
|
+
block
|
|
155
|
+
)
|
|
156
|
+
rescue ArgumentError => e
|
|
157
|
+
raise unless e.message.include?('wrong number of arguments')
|
|
158
|
+
|
|
159
|
+
Spikard::Native.build_route_metadata(
|
|
160
|
+
method,
|
|
161
|
+
path,
|
|
162
|
+
handler_name,
|
|
163
|
+
options[:request_schema],
|
|
164
|
+
options[:response_schema],
|
|
165
|
+
options[:parameter_schema],
|
|
166
|
+
options[:file_params],
|
|
167
|
+
options.fetch(:is_async, false),
|
|
168
|
+
options[:cors],
|
|
169
|
+
options[:body_param_name]&.to_s,
|
|
170
|
+
block
|
|
171
|
+
)
|
|
172
|
+
end
|
|
154
173
|
else
|
|
155
174
|
handler_name ||= default_handler_name(method, path)
|
|
156
175
|
|
data/lib/spikard/testing.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'timeout'
|
|
4
5
|
|
|
5
6
|
module Spikard
|
|
6
7
|
# Testing helpers that wrap the native Ruby extension.
|
|
@@ -8,25 +9,37 @@ module Spikard
|
|
|
8
9
|
module_function
|
|
9
10
|
|
|
10
11
|
def create_test_client(app, config: nil)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
trace('create_test_client:start')
|
|
13
|
+
ensure_native_test_client!
|
|
14
|
+
config = resolve_test_config(app, config)
|
|
15
|
+
native = build_native_test_client(app, config)
|
|
16
|
+
trace('create_test_client:done')
|
|
17
|
+
TestClient.new(native)
|
|
18
|
+
end
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
if
|
|
17
|
-
|
|
20
|
+
def ensure_native_test_client!
|
|
21
|
+
return if defined?(Spikard::Native::TestClient)
|
|
22
|
+
|
|
23
|
+
raise LoadError, 'Spikard native test client is not available. Build the native extension before running tests.'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def resolve_test_config(app, config)
|
|
27
|
+
return config if config
|
|
28
|
+
|
|
29
|
+
if app.instance_variable_defined?(:@__spikard_test_config)
|
|
30
|
+
return app.instance_variable_get(:@__spikard_test_config)
|
|
18
31
|
end
|
|
19
32
|
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
Spikard::ServerConfig.new
|
|
34
|
+
end
|
|
22
35
|
|
|
36
|
+
def build_native_test_client(app, config)
|
|
23
37
|
routes_json = app.normalized_routes_json
|
|
24
38
|
handlers = app.handler_map.transform_keys(&:to_sym)
|
|
25
39
|
ws_handlers = app.websocket_handlers || {}
|
|
26
40
|
sse_producers = app.sse_producers || {}
|
|
27
41
|
dependencies = app.dependencies || {}
|
|
28
|
-
|
|
29
|
-
TestClient.new(native)
|
|
42
|
+
Spikard::Native::TestClient.new(routes_json, handlers, config, ws_handlers, sse_producers, dependencies)
|
|
30
43
|
end
|
|
31
44
|
|
|
32
45
|
# High level wrapper around the native test client.
|
|
@@ -50,7 +63,9 @@ module Spikard
|
|
|
50
63
|
end
|
|
51
64
|
|
|
52
65
|
def websocket(path)
|
|
66
|
+
Testing.trace("websocket:start #{path}")
|
|
53
67
|
native_ws = @native.websocket(path)
|
|
68
|
+
Testing.trace("websocket:connected #{path}")
|
|
54
69
|
WebSocketTestConnection.new(native_ws)
|
|
55
70
|
end
|
|
56
71
|
|
|
@@ -77,22 +92,26 @@ module Spikard
|
|
|
77
92
|
end
|
|
78
93
|
|
|
79
94
|
def send_text(text)
|
|
95
|
+
Testing.trace('websocket:send_text')
|
|
80
96
|
@native_ws.send_text(JSON.generate(text))
|
|
81
97
|
end
|
|
82
98
|
|
|
83
99
|
def send_json(obj)
|
|
100
|
+
Testing.trace('websocket:send_json')
|
|
84
101
|
@native_ws.send_json(obj)
|
|
85
102
|
end
|
|
86
103
|
|
|
87
104
|
def receive_text
|
|
88
|
-
|
|
105
|
+
Testing.trace('websocket:receive_text')
|
|
106
|
+
raw = with_timeout { @native_ws.receive_text }
|
|
89
107
|
JSON.parse(raw)
|
|
90
108
|
rescue JSON::ParserError
|
|
91
109
|
raw
|
|
92
110
|
end
|
|
93
111
|
|
|
94
112
|
def receive_json
|
|
95
|
-
|
|
113
|
+
Testing.trace('websocket:receive_json')
|
|
114
|
+
with_timeout { @native_ws.receive_json }
|
|
96
115
|
end
|
|
97
116
|
|
|
98
117
|
def receive_bytes
|
|
@@ -105,8 +124,18 @@ module Spikard
|
|
|
105
124
|
end
|
|
106
125
|
|
|
107
126
|
def close
|
|
127
|
+
Testing.trace('websocket:close')
|
|
108
128
|
@native_ws.close
|
|
109
129
|
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def with_timeout(&)
|
|
134
|
+
timeout_ms = ENV.fetch('SPIKARD_RB_TEST_TIMEOUT_MS', nil)
|
|
135
|
+
return yield unless timeout_ms
|
|
136
|
+
|
|
137
|
+
Timeout.timeout(timeout_ms.to_f / 1000.0, &)
|
|
138
|
+
end
|
|
110
139
|
end
|
|
111
140
|
|
|
112
141
|
# WebSocket message wrapper
|
|
@@ -217,5 +246,11 @@ module Spikard
|
|
|
217
246
|
JSON.parse(@data)
|
|
218
247
|
end
|
|
219
248
|
end
|
|
249
|
+
|
|
250
|
+
def trace(message)
|
|
251
|
+
return unless ENV['SPIKARD_RB_TEST_TRACE'] == '1'
|
|
252
|
+
|
|
253
|
+
warn("[spikard-rb-test] #{message}")
|
|
254
|
+
end
|
|
220
255
|
end
|
|
221
256
|
end
|
data/lib/spikard/version.rb
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "spikard-bindings-shared"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
repository = "https://github.com/Goldziher/spikard"
|
|
8
|
+
homepage = "https://github.com/Goldziher/spikard"
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
12
|
+
serde_json = "1.0"
|
|
13
|
+
axum = { version = "0.8", features = ["multipart", "ws"] }
|
|
14
|
+
tokio = { version = "1", features = ["full"] }
|
|
15
|
+
thiserror = "2.0"
|
|
16
|
+
spikard-core = { path = "../spikard-core", features = ["di"] }
|
|
17
|
+
spikard-http = { path = "../spikard-http", features = ["di"] }
|
|
18
|
+
tracing = "0.1"
|
|
19
|
+
http = "1.4"
|
|
20
|
+
http-body-util = "0.1"
|
|
21
|
+
|
|
22
|
+
[features]
|
|
23
|
+
default = []
|
|
24
|
+
python-support = ["pyo3", "pyo3-async-runtimes"]
|
|
25
|
+
node-support = ["napi", "napi-derive"]
|
|
26
|
+
ruby-support = ["magnus"]
|
|
27
|
+
php-support = ["ext-php-rs"]
|
|
28
|
+
wasm-support = ["wasm-bindgen"]
|
|
29
|
+
|
|
30
|
+
[dependencies.pyo3]
|
|
31
|
+
version = "0.27"
|
|
32
|
+
optional = true
|
|
33
|
+
features = ["abi3-py310"]
|
|
34
|
+
|
|
35
|
+
[dependencies.pyo3-async-runtimes]
|
|
36
|
+
version = "0.27"
|
|
37
|
+
optional = true
|
|
38
|
+
features = ["tokio-runtime"]
|
|
39
|
+
|
|
40
|
+
[dependencies.napi]
|
|
41
|
+
version = "3"
|
|
42
|
+
optional = true
|
|
43
|
+
default-features = false
|
|
44
|
+
features = ["napi9", "async"]
|
|
45
|
+
|
|
46
|
+
[dependencies.napi-derive]
|
|
47
|
+
version = "3"
|
|
48
|
+
optional = true
|
|
49
|
+
|
|
50
|
+
[dependencies.magnus]
|
|
51
|
+
version = "0.8.2"
|
|
52
|
+
optional = true
|
|
53
|
+
|
|
54
|
+
[dependencies.ext-php-rs]
|
|
55
|
+
version = "0.15"
|
|
56
|
+
optional = true
|
|
57
|
+
|
|
58
|
+
[dependencies.wasm-bindgen]
|
|
59
|
+
version = "0.2"
|
|
60
|
+
optional = true
|
|
61
|
+
|
|
62
|
+
[dev-dependencies]
|
|
63
|
+
pretty_assertions = "1.4"
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//! Example of implementing ConfigSource for a language binding
|
|
2
|
+
//!
|
|
3
|
+
//! This example demonstrates how a language binding (e.g., Python, Node.js, Ruby, PHP)
|
|
4
|
+
//! would implement the `ConfigSource` trait to extract ServerConfig from language-specific objects.
|
|
5
|
+
|
|
6
|
+
use spikard_bindings_shared::{ConfigExtractor, ConfigSource};
|
|
7
|
+
use std::collections::HashMap;
|
|
8
|
+
|
|
9
|
+
/// Example: PyO3 Python dict wrapper
|
|
10
|
+
struct PyDictWrapper {
|
|
11
|
+
data: HashMap<String, String>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl PyDictWrapper {
|
|
15
|
+
fn new() -> Self {
|
|
16
|
+
Self { data: HashMap::new() }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fn insert(&mut self, key: &str, value: &str) {
|
|
20
|
+
self.data.insert(key.to_string(), value.to_string());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl ConfigSource for PyDictWrapper {
|
|
25
|
+
fn get_bool(&self, key: &str) -> Option<bool> {
|
|
26
|
+
self.data.get(key).and_then(|v| match v.as_str() {
|
|
27
|
+
"true" | "True" => Some(true),
|
|
28
|
+
"false" | "False" => Some(false),
|
|
29
|
+
_ => v.parse().ok(),
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn get_u64(&self, key: &str) -> Option<u64> {
|
|
34
|
+
self.data.get(key).and_then(|v| v.parse().ok())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn get_u16(&self, key: &str) -> Option<u16> {
|
|
38
|
+
self.data.get(key).and_then(|v| v.parse().ok())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn get_string(&self, key: &str) -> Option<String> {
|
|
42
|
+
self.data.get(key).cloned()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn get_vec_string(&self, key: &str) -> Option<Vec<String>> {
|
|
46
|
+
self.data
|
|
47
|
+
.get(key)
|
|
48
|
+
.map(|s| s.split(',').map(|item| item.trim().to_string()).collect())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn get_nested(&self, _key: &str) -> Option<Box<dyn ConfigSource + '_>> {
|
|
52
|
+
None
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn has_key(&self, key: &str) -> bool {
|
|
56
|
+
self.data.contains_key(key)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn main() {
|
|
61
|
+
println!("=== ConfigExtractor Example ===\n");
|
|
62
|
+
|
|
63
|
+
println!("1. Extracting compression configuration:");
|
|
64
|
+
let mut compression_config = PyDictWrapper::new();
|
|
65
|
+
compression_config.insert("gzip", "true");
|
|
66
|
+
compression_config.insert("brotli", "true");
|
|
67
|
+
compression_config.insert("min_size", "2048");
|
|
68
|
+
compression_config.insert("quality", "9");
|
|
69
|
+
|
|
70
|
+
match ConfigExtractor::extract_compression_config(&compression_config) {
|
|
71
|
+
Ok(config) => {
|
|
72
|
+
println!(" gzip: {}", config.gzip);
|
|
73
|
+
println!(" brotli: {}", config.brotli);
|
|
74
|
+
println!(" min_size: {}", config.min_size);
|
|
75
|
+
println!(" quality: {}\n", config.quality);
|
|
76
|
+
}
|
|
77
|
+
Err(e) => println!(" Error: {}\n", e),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
println!("2. Extracting JWT authentication configuration:");
|
|
81
|
+
let mut jwt_config = PyDictWrapper::new();
|
|
82
|
+
jwt_config.insert("secret", "my-secret-key");
|
|
83
|
+
jwt_config.insert("algorithm", "HS256");
|
|
84
|
+
jwt_config.insert("leeway", "60");
|
|
85
|
+
|
|
86
|
+
match ConfigExtractor::extract_jwt_config(&jwt_config) {
|
|
87
|
+
Ok(config) => {
|
|
88
|
+
println!(" secret: [REDACTED]");
|
|
89
|
+
println!(" algorithm: {}", config.algorithm);
|
|
90
|
+
println!(" leeway: {}\n", config.leeway);
|
|
91
|
+
}
|
|
92
|
+
Err(e) => println!(" Error: {}\n", e),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
println!("3. Extracting API Key authentication configuration:");
|
|
96
|
+
let mut api_key_config = PyDictWrapper::new();
|
|
97
|
+
api_key_config.insert("keys", "key1,key2,key3");
|
|
98
|
+
api_key_config.insert("header_name", "X-API-Key");
|
|
99
|
+
|
|
100
|
+
match ConfigExtractor::extract_api_key_config(&api_key_config) {
|
|
101
|
+
Ok(config) => {
|
|
102
|
+
println!(" keys: {:?}", config.keys);
|
|
103
|
+
println!(" header_name: {}\n", config.header_name);
|
|
104
|
+
}
|
|
105
|
+
Err(e) => println!(" Error: {}\n", e),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
println!("4. Extracting rate limit configuration:");
|
|
109
|
+
let mut rate_limit_config = PyDictWrapper::new();
|
|
110
|
+
rate_limit_config.insert("per_second", "100");
|
|
111
|
+
rate_limit_config.insert("burst", "20");
|
|
112
|
+
rate_limit_config.insert("ip_based", "true");
|
|
113
|
+
|
|
114
|
+
match ConfigExtractor::extract_rate_limit_config(&rate_limit_config) {
|
|
115
|
+
Ok(config) => {
|
|
116
|
+
println!(" per_second: {}", config.per_second);
|
|
117
|
+
println!(" burst: {}", config.burst);
|
|
118
|
+
println!(" ip_based: {}\n", config.ip_based);
|
|
119
|
+
}
|
|
120
|
+
Err(e) => println!(" Error: {}\n", e),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
println!("5. Testing error handling (missing 'burst' field):");
|
|
124
|
+
let rate_limit_config = PyDictWrapper::new();
|
|
125
|
+
|
|
126
|
+
match ConfigExtractor::extract_rate_limit_config(&rate_limit_config) {
|
|
127
|
+
Ok(_config) => println!(" Success (unexpected!)"),
|
|
128
|
+
Err(e) => println!(" Expected error: {}\n", e),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
println!("=== Example Complete ===");
|
|
132
|
+
}
|