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
|
@@ -1,8 +1,199 @@
|
|
|
1
|
-
use
|
|
1
|
+
use std::env;
|
|
2
|
+
use std::fs;
|
|
3
|
+
use std::path::PathBuf;
|
|
2
4
|
|
|
3
|
-
fn main() {
|
|
4
|
-
let mut rbconfig = RbConfig::current();
|
|
5
|
-
rbconfig.link_ruby(false);
|
|
6
|
-
rbconfig.print_cargo_args();
|
|
5
|
+
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
7
6
|
println!("cargo:rerun-if-changed=build.rs");
|
|
7
|
+
|
|
8
|
+
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
|
|
9
|
+
let workspace_root = manifest_dir
|
|
10
|
+
.parent()
|
|
11
|
+
.and_then(|p| p.parent())
|
|
12
|
+
.ok_or("Failed to resolve workspace root from manifest dir")?;
|
|
13
|
+
|
|
14
|
+
let target = workspace_root.join("packages/ruby/lib/spikard/response.rb");
|
|
15
|
+
if let Some(parent) = target.parent() {
|
|
16
|
+
fs::create_dir_all(parent)?;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fs::write(&target, generated_response_file())?;
|
|
20
|
+
Ok(())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn generated_response_file() -> String {
|
|
24
|
+
r##"# frozen_string_literal: true
|
|
25
|
+
|
|
26
|
+
# ⚠️ GENERATED BY crates/spikard-rb/build.rs — DO NOT EDIT BY HAND
|
|
27
|
+
module Spikard
|
|
28
|
+
class Response # :nodoc: Native-backed HTTP response facade generated from Rust metadata.
|
|
29
|
+
attr_reader :content, :status_code, :headers, :native_response
|
|
30
|
+
|
|
31
|
+
def initialize(content: nil, body: nil, status_code: 200, headers: nil, content_type: nil)
|
|
32
|
+
@content = content.nil? ? body : content
|
|
33
|
+
@status_code = Integer(status_code || 200)
|
|
34
|
+
@headers = normalize_headers(headers)
|
|
35
|
+
set_header('content-type', content_type) if content_type
|
|
36
|
+
rebuild_native!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def status
|
|
40
|
+
@status_code
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def status_code=(value)
|
|
44
|
+
@status_code = Integer(value)
|
|
45
|
+
rebuild_native!
|
|
46
|
+
rescue ArgumentError, TypeError
|
|
47
|
+
raise ArgumentError, 'status_code must be an integer'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def headers=(value)
|
|
51
|
+
@headers = normalize_headers(value)
|
|
52
|
+
rebuild_native!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def content=(value)
|
|
56
|
+
@content = value
|
|
57
|
+
rebuild_native!
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_header(name, value)
|
|
61
|
+
@headers[name.to_s] = value.to_s
|
|
62
|
+
rebuild_native!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def set_cookie(name, value, **options)
|
|
66
|
+
raise ArgumentError, 'cookie name required' if name.nil? || name.empty?
|
|
67
|
+
|
|
68
|
+
header_value = ["#{name}=#{value}", *cookie_parts(options)].join('; ')
|
|
69
|
+
set_header('set-cookie', header_value)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_native_response
|
|
73
|
+
@native_response
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def rebuild_native!
|
|
79
|
+
ensure_native!
|
|
80
|
+
@native_response = Spikard::Native.build_response(@content, @status_code, @headers)
|
|
81
|
+
return unless @native_response
|
|
82
|
+
|
|
83
|
+
@status_code = @native_response.status_code
|
|
84
|
+
@headers = @native_response.headers
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def ensure_native!
|
|
88
|
+
return if defined?(Spikard::Native) && Spikard::Native.respond_to?(:build_response)
|
|
89
|
+
|
|
90
|
+
raise 'Spikard native extension is not loaded'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def cookie_parts(options)
|
|
94
|
+
[
|
|
95
|
+
options[:max_age] && "Max-Age=#{Integer(options[:max_age])}",
|
|
96
|
+
options[:domain] && "Domain=#{options[:domain]}",
|
|
97
|
+
"Path=#{options.fetch(:path, '/') || '/'}",
|
|
98
|
+
options[:secure] ? 'Secure' : nil,
|
|
99
|
+
options[:httponly] ? 'HttpOnly' : nil,
|
|
100
|
+
options[:samesite] && "SameSite=#{options[:samesite]}"
|
|
101
|
+
].compact
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def normalize_headers(value)
|
|
105
|
+
case value
|
|
106
|
+
when nil
|
|
107
|
+
{}
|
|
108
|
+
when Hash
|
|
109
|
+
value.each_with_object({}) do |(key, val), acc|
|
|
110
|
+
acc[key.to_s.downcase] = val.to_s
|
|
111
|
+
end
|
|
112
|
+
else
|
|
113
|
+
raise ArgumentError, 'headers must be a Hash'
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class StreamingResponse # :nodoc: Streaming response wrapper backed by the native Rust builder.
|
|
119
|
+
attr_reader :stream, :status_code, :headers, :native_response
|
|
120
|
+
|
|
121
|
+
def initialize(stream, status_code: 200, headers: nil)
|
|
122
|
+
unless stream.respond_to?(:next) || stream.respond_to?(:each)
|
|
123
|
+
raise ArgumentError, 'StreamingResponse requires an object responding to #next or #each'
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
@stream = stream.respond_to?(:to_enum) ? stream.to_enum : stream
|
|
127
|
+
@status_code = Integer(status_code || 200)
|
|
128
|
+
header_hash = headers || {}
|
|
129
|
+
@headers = header_hash.each_with_object({}) do |(key, value), memo|
|
|
130
|
+
memo[String(key)] = String(value)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
rebuild_native!
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def to_native_response
|
|
137
|
+
@native_response
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def rebuild_native!
|
|
143
|
+
ensure_native!
|
|
144
|
+
@native_response = Spikard::Native.build_streaming_response(@stream, @status_code, @headers)
|
|
145
|
+
return unless @native_response
|
|
146
|
+
|
|
147
|
+
@status_code = @native_response.status_code
|
|
148
|
+
@headers = @native_response.headers
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def ensure_native!
|
|
152
|
+
return if defined?(Spikard::Native) && Spikard::Native.respond_to?(:build_streaming_response)
|
|
153
|
+
|
|
154
|
+
raise 'Spikard native extension is not loaded'
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
module Testing
|
|
159
|
+
class Response # :nodoc: Lightweight response wrapper used by the test client.
|
|
160
|
+
attr_reader :status_code, :headers, :body
|
|
161
|
+
|
|
162
|
+
def initialize(payload)
|
|
163
|
+
@status_code = payload[:status_code]
|
|
164
|
+
@headers = payload[:headers] || {}
|
|
165
|
+
@body = payload[:body]
|
|
166
|
+
@body_text = payload[:body_text]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def status
|
|
170
|
+
@status_code
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def body_bytes
|
|
174
|
+
@body || ''.b
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def body_text
|
|
178
|
+
@body_text || @body&.dup&.force_encoding(Encoding::UTF_8)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def text
|
|
182
|
+
body_text
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def json
|
|
186
|
+
return nil if @body.nil? || @body.empty?
|
|
187
|
+
|
|
188
|
+
JSON.parse(@body)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def bytes
|
|
192
|
+
body_bytes.bytes
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
"##
|
|
198
|
+
.to_string()
|
|
8
199
|
}
|
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
//! ServerConfig extraction from Ruby
|
|
1
|
+
//! ServerConfig extraction from Ruby objects.
|
|
2
2
|
//!
|
|
3
|
-
//! This module handles
|
|
4
|
-
//!
|
|
5
|
-
|
|
6
|
-
#![allow(dead_code)]
|
|
3
|
+
//! This module handles converting Ruby ServerConfig objects to the Rust
|
|
4
|
+
//! spikard_http::ServerConfig type.
|
|
7
5
|
|
|
8
6
|
use magnus::prelude::*;
|
|
9
7
|
use magnus::{Error, RArray, RHash, Ruby, TryConvert, Value};
|
|
10
8
|
use spikard_http::{
|
|
11
|
-
ApiKeyConfig, CompressionConfig, ContactInfo, JwtConfig, LicenseInfo, OpenApiConfig, RateLimitConfig,
|
|
12
|
-
|
|
9
|
+
ApiKeyConfig, CompressionConfig, ContactInfo, JwtConfig, LicenseInfo, OpenApiConfig, RateLimitConfig, ServerInfo,
|
|
10
|
+
StaticFilesConfig,
|
|
13
11
|
};
|
|
14
12
|
use std::collections::HashMap;
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/// Extract a ServerConfig from a Ruby configuration object.
|
|
19
|
-
///
|
|
20
|
-
/// Handles all ServerConfig properties including middleware configs,
|
|
21
|
-
/// authentication, static files, and OpenAPI documentation settings.
|
|
22
|
-
pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerConfig, Error> {
|
|
14
|
+
/// Extract ServerConfig from Ruby ServerConfig object
|
|
15
|
+
pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<spikard_http::ServerConfig, Error> {
|
|
23
16
|
let host: String = config_value.funcall("host", ())?;
|
|
24
17
|
|
|
25
18
|
let port: u32 = config_value.funcall("port", ())?;
|
|
@@ -46,7 +39,6 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
46
39
|
|
|
47
40
|
let shutdown_timeout: u64 = config_value.funcall("shutdown_timeout", ())?;
|
|
48
41
|
|
|
49
|
-
// Compression config
|
|
50
42
|
let compression_value: Value = config_value.funcall("compression", ())?;
|
|
51
43
|
let compression = if compression_value.is_nil() {
|
|
52
44
|
None
|
|
@@ -63,7 +55,6 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
63
55
|
})
|
|
64
56
|
};
|
|
65
57
|
|
|
66
|
-
// Rate limit config
|
|
67
58
|
let rate_limit_value: Value = config_value.funcall("rate_limit", ())?;
|
|
68
59
|
let rate_limit = if rate_limit_value.is_nil() {
|
|
69
60
|
None
|
|
@@ -78,7 +69,6 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
78
69
|
})
|
|
79
70
|
};
|
|
80
71
|
|
|
81
|
-
// JWT auth config
|
|
82
72
|
let jwt_auth_value: Value = config_value.funcall("jwt_auth", ())?;
|
|
83
73
|
let jwt_auth = if jwt_auth_value.is_nil() {
|
|
84
74
|
None
|
|
@@ -107,7 +97,6 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
107
97
|
})
|
|
108
98
|
};
|
|
109
99
|
|
|
110
|
-
// API key auth config
|
|
111
100
|
let api_key_auth_value: Value = config_value.funcall("api_key_auth", ())?;
|
|
112
101
|
let api_key_auth = if api_key_auth_value.is_nil() {
|
|
113
102
|
None
|
|
@@ -117,7 +106,6 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
117
106
|
Some(ApiKeyConfig { keys, header_name })
|
|
118
107
|
};
|
|
119
108
|
|
|
120
|
-
// Static files config
|
|
121
109
|
let static_files_value: Value = config_value.funcall("static_files", ())?;
|
|
122
110
|
let static_files_array = RArray::from_value(static_files_value)
|
|
123
111
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "static_files must be an Array"))?;
|
|
@@ -142,7 +130,6 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
142
130
|
});
|
|
143
131
|
}
|
|
144
132
|
|
|
145
|
-
// OpenAPI config
|
|
146
133
|
let openapi_value: Value = config_value.funcall("openapi", ())?;
|
|
147
134
|
let openapi = if openapi_value.is_nil() {
|
|
148
135
|
None
|
|
@@ -160,9 +147,80 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
160
147
|
let redoc_path: String = openapi_value.funcall("redoc_path", ())?;
|
|
161
148
|
let openapi_json_path: String = openapi_value.funcall("openapi_json_path", ())?;
|
|
162
149
|
|
|
163
|
-
let
|
|
164
|
-
let
|
|
165
|
-
|
|
150
|
+
let contact_value: Value = openapi_value.funcall("contact", ())?;
|
|
151
|
+
let contact = if contact_value.is_nil() {
|
|
152
|
+
None
|
|
153
|
+
} else if let Some(contact_hash) = RHash::from_value(contact_value) {
|
|
154
|
+
let name = get_optional_string_from_hash(contact_hash, "name")?;
|
|
155
|
+
let email = get_optional_string_from_hash(contact_hash, "email")?;
|
|
156
|
+
let url = get_optional_string_from_hash(contact_hash, "url")?;
|
|
157
|
+
Some(ContactInfo { name, email, url })
|
|
158
|
+
} else {
|
|
159
|
+
let name_value: Value = contact_value.funcall("name", ())?;
|
|
160
|
+
let email_value: Value = contact_value.funcall("email", ())?;
|
|
161
|
+
let url_value: Value = contact_value.funcall("url", ())?;
|
|
162
|
+
Some(ContactInfo {
|
|
163
|
+
name: if name_value.is_nil() {
|
|
164
|
+
None
|
|
165
|
+
} else {
|
|
166
|
+
Some(String::try_convert(name_value)?)
|
|
167
|
+
},
|
|
168
|
+
email: if email_value.is_nil() {
|
|
169
|
+
None
|
|
170
|
+
} else {
|
|
171
|
+
Some(String::try_convert(email_value)?)
|
|
172
|
+
},
|
|
173
|
+
url: if url_value.is_nil() {
|
|
174
|
+
None
|
|
175
|
+
} else {
|
|
176
|
+
Some(String::try_convert(url_value)?)
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
let license_value: Value = openapi_value.funcall("license", ())?;
|
|
182
|
+
let license = if license_value.is_nil() {
|
|
183
|
+
None
|
|
184
|
+
} else if let Some(license_hash) = RHash::from_value(license_value) {
|
|
185
|
+
let name = get_required_string_from_hash(license_hash, "name", ruby)?;
|
|
186
|
+
let url = get_optional_string_from_hash(license_hash, "url")?;
|
|
187
|
+
Some(LicenseInfo { name, url })
|
|
188
|
+
} else {
|
|
189
|
+
let name: String = license_value.funcall("name", ())?;
|
|
190
|
+
let url_value: Value = license_value.funcall("url", ())?;
|
|
191
|
+
let url = if url_value.is_nil() {
|
|
192
|
+
None
|
|
193
|
+
} else {
|
|
194
|
+
Some(String::try_convert(url_value)?)
|
|
195
|
+
};
|
|
196
|
+
Some(LicenseInfo { name, url })
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let servers_value: Value = openapi_value.funcall("servers", ())?;
|
|
200
|
+
let servers_array = RArray::from_value(servers_value)
|
|
201
|
+
.ok_or_else(|| Error::new(ruby.exception_type_error(), "servers must be an Array"))?;
|
|
202
|
+
|
|
203
|
+
let mut servers = Vec::new();
|
|
204
|
+
for i in 0..servers_array.len() {
|
|
205
|
+
let server_value = servers_array.entry::<Value>(i as isize)?;
|
|
206
|
+
|
|
207
|
+
let (url, description) = if let Some(server_hash) = RHash::from_value(server_value) {
|
|
208
|
+
let url = get_required_string_from_hash(server_hash, "url", ruby)?;
|
|
209
|
+
let description = get_optional_string_from_hash(server_hash, "description")?;
|
|
210
|
+
(url, description)
|
|
211
|
+
} else {
|
|
212
|
+
let url: String = server_value.funcall("url", ())?;
|
|
213
|
+
let description_value: Value = server_value.funcall("description", ())?;
|
|
214
|
+
let description = if description_value.is_nil() {
|
|
215
|
+
None
|
|
216
|
+
} else {
|
|
217
|
+
Some(String::try_convert(description_value)?)
|
|
218
|
+
};
|
|
219
|
+
(url, description)
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
servers.push(ServerInfo { url, description });
|
|
223
|
+
}
|
|
166
224
|
|
|
167
225
|
let security_schemes = HashMap::new();
|
|
168
226
|
|
|
@@ -181,7 +239,7 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
181
239
|
})
|
|
182
240
|
};
|
|
183
241
|
|
|
184
|
-
Ok(ServerConfig {
|
|
242
|
+
Ok(spikard_http::ServerConfig {
|
|
185
243
|
host,
|
|
186
244
|
port: port as u16,
|
|
187
245
|
workers,
|
|
@@ -196,99 +254,32 @@ pub fn extract_server_config(ruby: &Ruby, config_value: Value) -> Result<ServerC
|
|
|
196
254
|
graceful_shutdown,
|
|
197
255
|
shutdown_timeout,
|
|
198
256
|
background_tasks: spikard_http::BackgroundTaskConfig::default(),
|
|
257
|
+
enable_http_trace: false,
|
|
199
258
|
openapi,
|
|
259
|
+
jsonrpc: None,
|
|
200
260
|
lifecycle_hooks: None,
|
|
201
|
-
#[cfg(feature = "di")]
|
|
202
261
|
di_container: None,
|
|
203
262
|
})
|
|
204
263
|
}
|
|
205
264
|
|
|
206
|
-
///
|
|
207
|
-
fn
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
265
|
+
/// Helper to extract an optional string from a Ruby Hash
|
|
266
|
+
pub fn get_optional_string_from_hash(hash: RHash, key: &str) -> Result<Option<String>, Error> {
|
|
267
|
+
match hash.get(String::from(key)) {
|
|
268
|
+
Some(v) if !v.is_nil() => Ok(Some(String::try_convert(v)?)),
|
|
269
|
+
_ => Ok(None),
|
|
211
270
|
}
|
|
212
|
-
|
|
213
|
-
if let Some(contact_hash) = RHash::from_value(contact_value) {
|
|
214
|
-
let name = get_optional_string_from_hash(contact_hash, "name")?;
|
|
215
|
-
let email = get_optional_string_from_hash(contact_hash, "email")?;
|
|
216
|
-
let url = get_optional_string_from_hash(contact_hash, "url")?;
|
|
217
|
-
return Ok(Some(ContactInfo { name, email, url }));
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
let name_value: Value = contact_value.funcall("name", ())?;
|
|
221
|
-
let email_value: Value = contact_value.funcall("email", ())?;
|
|
222
|
-
let url_value: Value = contact_value.funcall("url", ())?;
|
|
223
|
-
Ok(Some(ContactInfo {
|
|
224
|
-
name: if name_value.is_nil() {
|
|
225
|
-
None
|
|
226
|
-
} else {
|
|
227
|
-
Some(String::try_convert(name_value)?)
|
|
228
|
-
},
|
|
229
|
-
email: if email_value.is_nil() {
|
|
230
|
-
None
|
|
231
|
-
} else {
|
|
232
|
-
Some(String::try_convert(email_value)?)
|
|
233
|
-
},
|
|
234
|
-
url: if url_value.is_nil() {
|
|
235
|
-
None
|
|
236
|
-
} else {
|
|
237
|
-
Some(String::try_convert(url_value)?)
|
|
238
|
-
},
|
|
239
|
-
}))
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/// Extract license information from OpenAPI config.
|
|
243
|
-
fn extract_license_info(ruby: &Ruby, openapi_value: &Value) -> Result<Option<LicenseInfo>, Error> {
|
|
244
|
-
let license_value: Value = openapi_value.funcall("license", ())?;
|
|
245
|
-
if license_value.is_nil() {
|
|
246
|
-
return Ok(None);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if let Some(license_hash) = RHash::from_value(license_value) {
|
|
250
|
-
let name = get_required_string_from_hash(license_hash, "name", ruby)?;
|
|
251
|
-
let url = get_optional_string_from_hash(license_hash, "url")?;
|
|
252
|
-
return Ok(Some(LicenseInfo { name, url }));
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let name: String = license_value.funcall("name", ())?;
|
|
256
|
-
let url_value: Value = license_value.funcall("url", ())?;
|
|
257
|
-
let url = if url_value.is_nil() {
|
|
258
|
-
None
|
|
259
|
-
} else {
|
|
260
|
-
Some(String::try_convert(url_value)?)
|
|
261
|
-
};
|
|
262
|
-
Ok(Some(LicenseInfo { name, url }))
|
|
263
271
|
}
|
|
264
272
|
|
|
265
|
-
///
|
|
266
|
-
fn
|
|
267
|
-
let
|
|
268
|
-
|
|
269
|
-
.ok_or_else(|| Error::new(ruby.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
let (url, description) = if let Some(server_hash) = RHash::from_value(server_value) {
|
|
276
|
-
let url = get_required_string_from_hash(server_hash, "url", ruby)?;
|
|
277
|
-
let description = get_optional_string_from_hash(server_hash, "description")?;
|
|
278
|
-
(url, description)
|
|
279
|
-
} else {
|
|
280
|
-
let url: String = server_value.funcall("url", ())?;
|
|
281
|
-
let description_value: Value = server_value.funcall("description", ())?;
|
|
282
|
-
let description = if description_value.is_nil() {
|
|
283
|
-
None
|
|
284
|
-
} else {
|
|
285
|
-
Some(String::try_convert(description_value)?)
|
|
286
|
-
};
|
|
287
|
-
(url, description)
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
servers.push(ServerInfo { url, description });
|
|
273
|
+
/// Helper to extract a required string from a Ruby Hash
|
|
274
|
+
pub fn get_required_string_from_hash(hash: RHash, key: &str, ruby: &Ruby) -> Result<String, Error> {
|
|
275
|
+
let value = hash
|
|
276
|
+
.get(String::from(key))
|
|
277
|
+
.ok_or_else(|| Error::new(ruby.exception_arg_error(), format!("missing required key '{}'", key)))?;
|
|
278
|
+
if value.is_nil() {
|
|
279
|
+
return Err(Error::new(
|
|
280
|
+
ruby.exception_arg_error(),
|
|
281
|
+
format!("key '{}' cannot be nil", key),
|
|
282
|
+
));
|
|
291
283
|
}
|
|
292
|
-
|
|
293
|
-
Ok(servers)
|
|
284
|
+
String::try_convert(value)
|
|
294
285
|
}
|