spikard 0.4.0-x86_64-linux
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 +7 -0
- data/LICENSE +1 -0
- data/README.md +659 -0
- data/ext/spikard_rb/Cargo.toml +17 -0
- data/ext/spikard_rb/extconf.rb +10 -0
- data/ext/spikard_rb/src/lib.rs +6 -0
- data/lib/spikard/app.rb +405 -0
- data/lib/spikard/background.rb +27 -0
- data/lib/spikard/config.rb +396 -0
- data/lib/spikard/converters.rb +13 -0
- data/lib/spikard/handler_wrapper.rb +113 -0
- data/lib/spikard/provide.rb +214 -0
- data/lib/spikard/response.rb +173 -0
- data/lib/spikard/schema.rb +243 -0
- data/lib/spikard/sse.rb +111 -0
- data/lib/spikard/streaming_response.rb +44 -0
- data/lib/spikard/testing.rb +221 -0
- data/lib/spikard/upload_file.rb +131 -0
- data/lib/spikard/version.rb +5 -0
- data/lib/spikard/websocket.rb +59 -0
- data/lib/spikard.rb +43 -0
- data/sig/spikard.rbs +366 -0
- data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +139 -0
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +561 -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 +403 -0
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +274 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +25 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +298 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +637 -0
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +309 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +355 -0
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +502 -0
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +389 -0
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +413 -0
- data/vendor/crates/spikard-core/Cargo.toml +40 -0
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -0
- data/vendor/crates/spikard-core/src/debug.rs +63 -0
- data/vendor/crates/spikard-core/src/di/container.rs +726 -0
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -0
- data/vendor/crates/spikard-core/src/di/error.rs +118 -0
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -0
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -0
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -0
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -0
- data/vendor/crates/spikard-core/src/di/value.rs +283 -0
- data/vendor/crates/spikard-core/src/errors.rs +39 -0
- data/vendor/crates/spikard-core/src/http.rs +153 -0
- data/vendor/crates/spikard-core/src/lib.rs +29 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -0
- data/vendor/crates/spikard-core/src/metadata.rs +397 -0
- data/vendor/crates/spikard-core/src/parameters.rs +723 -0
- data/vendor/crates/spikard-core/src/problem.rs +310 -0
- data/vendor/crates/spikard-core/src/request_data.rs +189 -0
- data/vendor/crates/spikard-core/src/router.rs +249 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -0
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -0
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +689 -0
- data/vendor/crates/spikard-core/src/validation/mod.rs +459 -0
- data/vendor/crates/spikard-http/Cargo.toml +58 -0
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +147 -0
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +91 -0
- data/vendor/crates/spikard-http/src/auth.rs +247 -0
- data/vendor/crates/spikard-http/src/background.rs +1562 -0
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -0
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -0
- data/vendor/crates/spikard-http/src/cors.rs +490 -0
- data/vendor/crates/spikard-http/src/debug.rs +63 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +1878 -0
- data/vendor/crates/spikard-http/src/handler_response.rs +532 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +861 -0
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -0
- data/vendor/crates/spikard-http/src/lib.rs +524 -0
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -0
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +930 -0
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +541 -0
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -0
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -0
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +867 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +678 -0
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -0
- data/vendor/crates/spikard-http/src/response.rs +399 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +1557 -0
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +806 -0
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +630 -0
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +497 -0
- data/vendor/crates/spikard-http/src/sse.rs +961 -0
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -0
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -0
- data/vendor/crates/spikard-http/src/testing.rs +377 -0
- data/vendor/crates/spikard-http/src/websocket.rs +831 -0
- data/vendor/crates/spikard-http/tests/background_behavior.rs +918 -0
- data/vendor/crates/spikard-http/tests/common/handlers.rs +308 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +21 -0
- data/vendor/crates/spikard-http/tests/di_integration.rs +202 -0
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +4 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1135 -0
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +688 -0
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +324 -0
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +728 -0
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +724 -0
- data/vendor/crates/spikard-rb/Cargo.toml +43 -0
- data/vendor/crates/spikard-rb/build.rs +199 -0
- data/vendor/crates/spikard-rb/src/background.rs +63 -0
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/config/server_config.rs +283 -0
- data/vendor/crates/spikard-rb/src/conversion.rs +459 -0
- data/vendor/crates/spikard-rb/src/di/builder.rs +105 -0
- data/vendor/crates/spikard-rb/src/di/mod.rs +413 -0
- data/vendor/crates/spikard-rb/src/handler.rs +612 -0
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
- data/vendor/crates/spikard-rb/src/lib.rs +1857 -0
- data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -0
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +427 -0
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +326 -0
- data/vendor/crates/spikard-rb/src/server.rs +283 -0
- data/vendor/crates/spikard-rb/src/sse.rs +231 -0
- data/vendor/crates/spikard-rb/src/testing/client.rs +404 -0
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
- data/vendor/crates/spikard-rb/src/testing/sse.rs +143 -0
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +221 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -0
- data/vendor/crates/spikard-rb/tests/magnus_ffi_tests.rs +14 -0
- metadata +213 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
//! Route metadata extraction and validation
|
|
2
|
+
//!
|
|
3
|
+
//! This module handles the validation and normalization of route metadata,
|
|
4
|
+
//! including schema validation and parameter extraction. It serves as the
|
|
5
|
+
//! bridge between language bindings and the core HTTP server.
|
|
6
|
+
|
|
7
|
+
use serde::{Deserialize, Serialize};
|
|
8
|
+
use serde_json::Value;
|
|
9
|
+
use std::collections::HashMap;
|
|
10
|
+
|
|
11
|
+
/// Metadata for a single route parameter
|
|
12
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
13
|
+
pub struct ParameterMetadata {
|
|
14
|
+
/// Parameter name
|
|
15
|
+
pub name: String,
|
|
16
|
+
/// Parameter source (path, query, header, cookie)
|
|
17
|
+
pub source: ParameterSource,
|
|
18
|
+
/// Expected JSON type
|
|
19
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
20
|
+
pub schema_type: Option<String>,
|
|
21
|
+
/// Whether parameter is required
|
|
22
|
+
pub required: bool,
|
|
23
|
+
/// Optional validation schema
|
|
24
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
25
|
+
pub schema: Option<Value>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Parameter source type
|
|
29
|
+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
30
|
+
#[serde(rename_all = "lowercase")]
|
|
31
|
+
pub enum ParameterSource {
|
|
32
|
+
/// Path parameter (from URL path)
|
|
33
|
+
Path,
|
|
34
|
+
/// Query parameter (from query string)
|
|
35
|
+
Query,
|
|
36
|
+
/// Header parameter
|
|
37
|
+
Header,
|
|
38
|
+
/// Cookie parameter
|
|
39
|
+
Cookie,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl std::fmt::Display for ParameterSource {
|
|
43
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
44
|
+
match self {
|
|
45
|
+
Self::Path => write!(f, "path"),
|
|
46
|
+
Self::Query => write!(f, "query"),
|
|
47
|
+
Self::Header => write!(f, "header"),
|
|
48
|
+
Self::Cookie => write!(f, "cookie"),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl std::str::FromStr for ParameterSource {
|
|
54
|
+
type Err = String;
|
|
55
|
+
|
|
56
|
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
57
|
+
match s.to_lowercase().as_str() {
|
|
58
|
+
"path" => Ok(Self::Path),
|
|
59
|
+
"query" => Ok(Self::Query),
|
|
60
|
+
"header" => Ok(Self::Header),
|
|
61
|
+
"cookie" => Ok(Self::Cookie),
|
|
62
|
+
_ => Err(format!("Unknown parameter source: {}", s)),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Route-level metadata extracted from handler signatures
|
|
68
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
69
|
+
pub struct ExtractedRouteMetadata {
|
|
70
|
+
/// Extracted parameters from function signature
|
|
71
|
+
pub parameters: Vec<ParameterMetadata>,
|
|
72
|
+
/// Request body schema
|
|
73
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
74
|
+
pub request_schema: Option<Value>,
|
|
75
|
+
/// Response schema
|
|
76
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
77
|
+
pub response_schema: Option<Value>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Builder for extracting and validating route metadata
|
|
81
|
+
pub struct MetadataBuilder {
|
|
82
|
+
parameters: Vec<ParameterMetadata>,
|
|
83
|
+
request_schema: Option<Value>,
|
|
84
|
+
response_schema: Option<Value>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl Default for MetadataBuilder {
|
|
88
|
+
fn default() -> Self {
|
|
89
|
+
Self::new()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
impl MetadataBuilder {
|
|
94
|
+
/// Create a new metadata builder
|
|
95
|
+
pub fn new() -> Self {
|
|
96
|
+
Self {
|
|
97
|
+
parameters: Vec::new(),
|
|
98
|
+
request_schema: None,
|
|
99
|
+
response_schema: None,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Add a parameter to the metadata
|
|
104
|
+
pub fn with_parameter(mut self, param: ParameterMetadata) -> Self {
|
|
105
|
+
self.parameters.push(param);
|
|
106
|
+
self
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Add multiple parameters
|
|
110
|
+
pub fn with_parameters(mut self, params: Vec<ParameterMetadata>) -> Self {
|
|
111
|
+
self.parameters.extend(params);
|
|
112
|
+
self
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Set the request schema
|
|
116
|
+
pub fn with_request_schema(mut self, schema: Option<Value>) -> Self {
|
|
117
|
+
self.request_schema = schema;
|
|
118
|
+
self
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Set the response schema
|
|
122
|
+
pub fn with_response_schema(mut self, schema: Option<Value>) -> Self {
|
|
123
|
+
self.response_schema = schema;
|
|
124
|
+
self
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Build the extracted metadata
|
|
128
|
+
pub fn build(self) -> ExtractedRouteMetadata {
|
|
129
|
+
ExtractedRouteMetadata {
|
|
130
|
+
parameters: self.parameters,
|
|
131
|
+
request_schema: self.request_schema,
|
|
132
|
+
response_schema: self.response_schema,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Extract path parameters from a URL pattern
|
|
138
|
+
///
|
|
139
|
+
/// Given a URL pattern like "/users/{user_id}/posts/{post_id}", extracts
|
|
140
|
+
/// the parameter names and creates path parameter metadata.
|
|
141
|
+
pub fn extract_path_parameters(path: &str) -> Vec<ParameterMetadata> {
|
|
142
|
+
let mut params = Vec::new();
|
|
143
|
+
let mut in_brace = false;
|
|
144
|
+
let mut current_param = String::new();
|
|
145
|
+
|
|
146
|
+
for ch in path.chars() {
|
|
147
|
+
match ch {
|
|
148
|
+
'{' => in_brace = true,
|
|
149
|
+
'}' => {
|
|
150
|
+
if !current_param.is_empty() {
|
|
151
|
+
params.push(ParameterMetadata {
|
|
152
|
+
name: current_param.clone(),
|
|
153
|
+
source: ParameterSource::Path,
|
|
154
|
+
schema_type: Some("string".to_string()),
|
|
155
|
+
required: true,
|
|
156
|
+
schema: None,
|
|
157
|
+
});
|
|
158
|
+
current_param.clear();
|
|
159
|
+
}
|
|
160
|
+
in_brace = false;
|
|
161
|
+
}
|
|
162
|
+
_ if in_brace => current_param.push(ch),
|
|
163
|
+
_ => {}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
params
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// Parse parameter schema from a JSON Schema
|
|
171
|
+
///
|
|
172
|
+
/// Extracts parameter definitions from a JSON Schema object that follows the
|
|
173
|
+
/// parameter schema format with "properties" and "required" keys.
|
|
174
|
+
pub fn parse_parameter_schema(schema: &Value) -> Result<Vec<ParameterMetadata>, String> {
|
|
175
|
+
let mut params = Vec::new();
|
|
176
|
+
|
|
177
|
+
let Some(props) = schema.get("properties").and_then(|p| p.as_object()) else {
|
|
178
|
+
return Ok(params);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
let required: Vec<String> = schema
|
|
182
|
+
.get("required")
|
|
183
|
+
.and_then(|r| r.as_array())
|
|
184
|
+
.map(|arr| {
|
|
185
|
+
arr.iter()
|
|
186
|
+
.filter_map(|v| v.as_str().map(String::from))
|
|
187
|
+
.collect()
|
|
188
|
+
})
|
|
189
|
+
.unwrap_or_default();
|
|
190
|
+
|
|
191
|
+
for (param_name, param_schema) in props {
|
|
192
|
+
let is_required = required.contains(param_name);
|
|
193
|
+
|
|
194
|
+
// Extract source from schema
|
|
195
|
+
let source = param_schema
|
|
196
|
+
.get("source")
|
|
197
|
+
.and_then(|s| s.as_str())
|
|
198
|
+
.and_then(|s| s.parse().ok())
|
|
199
|
+
.unwrap_or(ParameterSource::Query);
|
|
200
|
+
|
|
201
|
+
// Extract type
|
|
202
|
+
let schema_type = param_schema
|
|
203
|
+
.get("type")
|
|
204
|
+
.and_then(|t| t.as_str())
|
|
205
|
+
.map(String::from);
|
|
206
|
+
|
|
207
|
+
params.push(ParameterMetadata {
|
|
208
|
+
name: param_name.clone(),
|
|
209
|
+
source,
|
|
210
|
+
schema_type,
|
|
211
|
+
required: is_required,
|
|
212
|
+
schema: Some(param_schema.clone()),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Ok(params)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Validate that extracted metadata conforms to expected structure
|
|
220
|
+
pub fn validate_metadata(metadata: &ExtractedRouteMetadata) -> Result<(), Vec<String>> {
|
|
221
|
+
let mut errors = Vec::new();
|
|
222
|
+
|
|
223
|
+
// Validate parameter names are not empty
|
|
224
|
+
for param in &metadata.parameters {
|
|
225
|
+
if param.name.is_empty() {
|
|
226
|
+
errors.push("Parameter name cannot be empty".to_string());
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Validate schema if present
|
|
230
|
+
if let Some(schema) = ¶m.schema {
|
|
231
|
+
if !schema.is_object() {
|
|
232
|
+
errors.push(format!("Parameter schema for '{}' must be an object", param.name));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validate request schema structure if present
|
|
238
|
+
if let Some(schema) = &metadata.request_schema {
|
|
239
|
+
if !schema.is_object() {
|
|
240
|
+
errors.push("Request schema must be an object".to_string());
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Validate response schema structure if present
|
|
245
|
+
if let Some(schema) = &metadata.response_schema {
|
|
246
|
+
if !schema.is_object() {
|
|
247
|
+
errors.push("Response schema must be an object".to_string());
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if errors.is_empty() {
|
|
252
|
+
Ok(())
|
|
253
|
+
} else {
|
|
254
|
+
Err(errors)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Merge path parameters with parameter schema
|
|
259
|
+
///
|
|
260
|
+
/// Combines path parameters extracted from the URL pattern with parameters
|
|
261
|
+
/// defined in the schema, giving precedence to explicit schema definitions.
|
|
262
|
+
pub fn merge_parameters(
|
|
263
|
+
path_params: Vec<ParameterMetadata>,
|
|
264
|
+
schema: Option<&Value>,
|
|
265
|
+
) -> Result<Vec<ParameterMetadata>, String> {
|
|
266
|
+
let mut merged: HashMap<String, ParameterMetadata> = HashMap::new();
|
|
267
|
+
|
|
268
|
+
// Add path parameters first
|
|
269
|
+
for param in path_params {
|
|
270
|
+
merged.insert(param.name.clone(), param);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Merge with schema parameters (schema takes precedence)
|
|
274
|
+
if let Some(schema_obj) = schema {
|
|
275
|
+
let schema_params = parse_parameter_schema(schema_obj)?;
|
|
276
|
+
for param in schema_params {
|
|
277
|
+
merged.insert(param.name.clone(), param);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Ok(merged.into_values().collect())
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[cfg(test)]
|
|
285
|
+
mod tests {
|
|
286
|
+
use super::*;
|
|
287
|
+
use serde_json::json;
|
|
288
|
+
|
|
289
|
+
#[test]
|
|
290
|
+
fn test_extract_path_parameters() {
|
|
291
|
+
let params = extract_path_parameters("/users/{user_id}/posts/{post_id}");
|
|
292
|
+
assert_eq!(params.len(), 2);
|
|
293
|
+
assert_eq!(params[0].name, "user_id");
|
|
294
|
+
assert_eq!(params[1].name, "post_id");
|
|
295
|
+
assert!(params.iter().all(|p| p.source == ParameterSource::Path));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#[test]
|
|
299
|
+
fn test_extract_no_path_parameters() {
|
|
300
|
+
let params = extract_path_parameters("/users");
|
|
301
|
+
assert!(params.is_empty());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[test]
|
|
305
|
+
fn test_parse_parameter_schema() {
|
|
306
|
+
let schema = json!({
|
|
307
|
+
"type": "object",
|
|
308
|
+
"properties": {
|
|
309
|
+
"name": {"type": "string", "source": "query"},
|
|
310
|
+
"age": {"type": "integer", "source": "query"}
|
|
311
|
+
},
|
|
312
|
+
"required": ["name"]
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
let params = parse_parameter_schema(&schema).unwrap();
|
|
316
|
+
assert_eq!(params.len(), 2);
|
|
317
|
+
|
|
318
|
+
let name_param = params.iter().find(|p| p.name == "name").unwrap();
|
|
319
|
+
assert!(name_param.required);
|
|
320
|
+
|
|
321
|
+
let age_param = params.iter().find(|p| p.name == "age").unwrap();
|
|
322
|
+
assert!(!age_param.required);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#[test]
|
|
326
|
+
fn test_merge_parameters() {
|
|
327
|
+
let path_params = vec![ParameterMetadata {
|
|
328
|
+
name: "user_id".to_string(),
|
|
329
|
+
source: ParameterSource::Path,
|
|
330
|
+
schema_type: Some("string".to_string()),
|
|
331
|
+
required: true,
|
|
332
|
+
schema: None,
|
|
333
|
+
}];
|
|
334
|
+
|
|
335
|
+
let schema = json!({
|
|
336
|
+
"type": "object",
|
|
337
|
+
"properties": {
|
|
338
|
+
"limit": {"type": "integer", "source": "query"}
|
|
339
|
+
},
|
|
340
|
+
"required": []
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
let merged = merge_parameters(path_params, Some(&schema)).unwrap();
|
|
344
|
+
assert_eq!(merged.len(), 2);
|
|
345
|
+
assert!(merged.iter().any(|p| p.name == "user_id"));
|
|
346
|
+
assert!(merged.iter().any(|p| p.name == "limit"));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
#[test]
|
|
350
|
+
fn test_parameter_source_display() {
|
|
351
|
+
assert_eq!(ParameterSource::Path.to_string(), "path");
|
|
352
|
+
assert_eq!(ParameterSource::Query.to_string(), "query");
|
|
353
|
+
assert_eq!(ParameterSource::Header.to_string(), "header");
|
|
354
|
+
assert_eq!(ParameterSource::Cookie.to_string(), "cookie");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#[test]
|
|
358
|
+
fn test_parameter_source_from_str() {
|
|
359
|
+
assert_eq!("path".parse(), Ok(ParameterSource::Path));
|
|
360
|
+
assert_eq!("query".parse(), Ok(ParameterSource::Query));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#[test]
|
|
364
|
+
fn test_validate_metadata_success() {
|
|
365
|
+
let metadata = ExtractedRouteMetadata {
|
|
366
|
+
parameters: vec![ParameterMetadata {
|
|
367
|
+
name: "id".to_string(),
|
|
368
|
+
source: ParameterSource::Path,
|
|
369
|
+
schema_type: Some("string".to_string()),
|
|
370
|
+
required: true,
|
|
371
|
+
schema: None,
|
|
372
|
+
}],
|
|
373
|
+
request_schema: None,
|
|
374
|
+
response_schema: None,
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
assert!(validate_metadata(&metadata).is_ok());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
#[test]
|
|
381
|
+
fn test_validate_metadata_empty_param_name() {
|
|
382
|
+
let metadata = ExtractedRouteMetadata {
|
|
383
|
+
parameters: vec![ParameterMetadata {
|
|
384
|
+
name: String::new(),
|
|
385
|
+
source: ParameterSource::Query,
|
|
386
|
+
schema_type: None,
|
|
387
|
+
required: false,
|
|
388
|
+
schema: None,
|
|
389
|
+
}],
|
|
390
|
+
request_schema: None,
|
|
391
|
+
response_schema: None,
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
let result = validate_metadata(&metadata);
|
|
395
|
+
assert!(result.is_err());
|
|
396
|
+
}
|
|
397
|
+
}
|