spikard 0.8.2 → 0.8.3

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/ext/spikard_rb/Cargo.lock +6 -6
  3. data/ext/spikard_rb/Cargo.toml +1 -1
  4. data/lib/spikard/version.rb +1 -1
  5. data/vendor/crates/spikard-bindings-shared/Cargo.toml +9 -1
  6. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +61 -23
  7. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +16 -0
  8. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +1 -1
  9. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +22 -19
  10. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +14 -12
  11. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +15 -6
  12. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +6 -0
  13. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +42 -36
  14. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +6 -1
  15. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +18 -6
  16. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +28 -10
  17. data/vendor/crates/spikard-core/Cargo.toml +9 -1
  18. data/vendor/crates/spikard-core/src/bindings/response.rs +6 -9
  19. data/vendor/crates/spikard-core/src/debug.rs +2 -2
  20. data/vendor/crates/spikard-core/src/di/container.rs +1 -1
  21. data/vendor/crates/spikard-core/src/di/error.rs +1 -1
  22. data/vendor/crates/spikard-core/src/di/factory.rs +7 -3
  23. data/vendor/crates/spikard-core/src/di/graph.rs +1 -0
  24. data/vendor/crates/spikard-core/src/di/resolved.rs +23 -0
  25. data/vendor/crates/spikard-core/src/di/value.rs +1 -0
  26. data/vendor/crates/spikard-core/src/errors.rs +3 -0
  27. data/vendor/crates/spikard-core/src/http.rs +19 -18
  28. data/vendor/crates/spikard-core/src/lifecycle.rs +42 -18
  29. data/vendor/crates/spikard-core/src/parameters.rs +61 -35
  30. data/vendor/crates/spikard-core/src/problem.rs +18 -4
  31. data/vendor/crates/spikard-core/src/request_data.rs +9 -8
  32. data/vendor/crates/spikard-core/src/router.rs +20 -6
  33. data/vendor/crates/spikard-core/src/schema_registry.rs +23 -8
  34. data/vendor/crates/spikard-core/src/type_hints.rs +11 -5
  35. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +29 -15
  36. data/vendor/crates/spikard-core/src/validation/mod.rs +45 -32
  37. data/vendor/crates/spikard-http/Cargo.toml +8 -1
  38. data/vendor/crates/spikard-rb/Cargo.toml +9 -1
  39. data/vendor/crates/spikard-rb/build.rs +1 -0
  40. data/vendor/crates/spikard-rb/src/lib.rs +58 -0
  41. data/vendor/crates/spikard-rb/src/lifecycle.rs +2 -2
  42. data/vendor/crates/spikard-rb-macros/Cargo.toml +9 -1
  43. data/vendor/crates/spikard-rb-macros/src/lib.rs +4 -5
  44. metadata +1 -1
@@ -88,6 +88,9 @@ impl ResolvedDependencies {
88
88
  /// let config = Arc::new("production".to_string());
89
89
  /// resolved.insert("config".to_string(), config);
90
90
  /// ```
91
+ ///
92
+ /// # Panics
93
+ /// Panics if the lock is poisoned.
91
94
  pub fn insert(&mut self, key: String, value: Arc<dyn Any + Send + Sync>) {
92
95
  self.dependencies.lock().unwrap().insert(key, value);
93
96
  }
@@ -126,6 +129,10 @@ impl ResolvedDependencies {
126
129
  /// let missing: Option<Arc<i32>> = resolved.get("missing");
127
130
  /// assert!(missing.is_none());
128
131
  /// ```
132
+ ///
133
+ /// # Panics
134
+ /// Panics if the lock is poisoned.
135
+ #[must_use]
129
136
  pub fn get<T: Send + Sync + 'static>(&self, key: &str) -> Option<Arc<T>> {
130
137
  self.dependencies
131
138
  .lock()
@@ -155,6 +162,10 @@ impl ResolvedDependencies {
155
162
  /// let any_ref = resolved.get_arc("data");
156
163
  /// assert!(any_ref.is_some());
157
164
  /// ```
165
+ ///
166
+ /// # Panics
167
+ /// Panics if the lock is poisoned.
168
+ #[must_use]
158
169
  pub fn get_arc(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
159
170
  self.dependencies.lock().unwrap().get(key).cloned()
160
171
  }
@@ -177,6 +188,9 @@ impl ResolvedDependencies {
177
188
  /// assert!(resolved.contains("exists"));
178
189
  /// assert!(!resolved.contains("missing"));
179
190
  /// ```
191
+ ///
192
+ /// # Panics
193
+ /// Panics if the lock is poisoned.
180
194
  #[must_use]
181
195
  pub fn contains(&self, key: &str) -> bool {
182
196
  self.dependencies.lock().unwrap().contains_key(key)
@@ -202,6 +216,9 @@ impl ResolvedDependencies {
202
216
  /// assert!(keys.contains(&"config".to_string()));
203
217
  /// assert!(keys.contains(&"db".to_string()));
204
218
  /// ```
219
+ ///
220
+ /// # Panics
221
+ /// Panics if the lock is poisoned.
205
222
  #[must_use]
206
223
  pub fn keys(&self) -> Vec<String> {
207
224
  self.dependencies.lock().unwrap().keys().cloned().collect()
@@ -233,6 +250,9 @@ impl ResolvedDependencies {
233
250
  /// resolved.cleanup().await;
234
251
  /// # });
235
252
  /// ```
253
+ ///
254
+ /// # Panics
255
+ /// Panics if the lock is poisoned.
236
256
  pub fn add_cleanup_task(&self, task: CleanupTask) {
237
257
  self.cleanup_tasks.lock().unwrap().push(task);
238
258
  }
@@ -275,6 +295,9 @@ impl ResolvedDependencies {
275
295
  /// assert_eq!(*order.lock().unwrap(), vec![2, 1]);
276
296
  /// # });
277
297
  /// ```
298
+ ///
299
+ /// # Panics
300
+ /// Panics if the lock is poisoned.
278
301
  pub async fn cleanup(self) {
279
302
  let tasks = {
280
303
  let mut cleanup_tasks = self.cleanup_tasks.lock().unwrap();
@@ -134,6 +134,7 @@ impl<T: Clone + Send + Sync + 'static> std::fmt::Debug for ValueDependency<T> {
134
134
  f.debug_struct("ValueDependency")
135
135
  .field("key", &self.key)
136
136
  .field("value_type", &std::any::type_name::<T>())
137
+ .field("value", &"<T>")
137
138
  .finish()
138
139
  }
139
140
  }
@@ -31,6 +31,9 @@ impl StructuredError {
31
31
  }
32
32
 
33
33
  /// Catch panics and convert to a structured error so they don't cross FFI boundaries.
34
+ ///
35
+ /// # Errors
36
+ /// Returns a structured error if a panic occurs during function execution.
34
37
  pub fn shield_panic<T, F>(f: F) -> Result<T, StructuredError>
35
38
  where
36
39
  F: FnOnce() -> T + UnwindSafe,
@@ -15,16 +15,17 @@ pub enum Method {
15
15
  }
16
16
 
17
17
  impl Method {
18
- pub fn as_str(&self) -> &'static str {
18
+ #[must_use]
19
+ pub const fn as_str(&self) -> &'static str {
19
20
  match self {
20
- Method::Get => "GET",
21
- Method::Post => "POST",
22
- Method::Put => "PUT",
23
- Method::Patch => "PATCH",
24
- Method::Delete => "DELETE",
25
- Method::Head => "HEAD",
26
- Method::Options => "OPTIONS",
27
- Method::Trace => "TRACE",
21
+ Self::Get => "GET",
22
+ Self::Post => "POST",
23
+ Self::Put => "PUT",
24
+ Self::Patch => "PATCH",
25
+ Self::Delete => "DELETE",
26
+ Self::Head => "HEAD",
27
+ Self::Options => "OPTIONS",
28
+ Self::Trace => "TRACE",
28
29
  }
29
30
  }
30
31
  }
@@ -40,15 +41,15 @@ impl std::str::FromStr for Method {
40
41
 
41
42
  fn from_str(s: &str) -> Result<Self, Self::Err> {
42
43
  match s.to_uppercase().as_str() {
43
- "GET" => Ok(Method::Get),
44
- "POST" => Ok(Method::Post),
45
- "PUT" => Ok(Method::Put),
46
- "PATCH" => Ok(Method::Patch),
47
- "DELETE" => Ok(Method::Delete),
48
- "HEAD" => Ok(Method::Head),
49
- "OPTIONS" => Ok(Method::Options),
50
- "TRACE" => Ok(Method::Trace),
51
- _ => Err(format!("Unknown HTTP method: {}", s)),
44
+ "GET" => Ok(Self::Get),
45
+ "POST" => Ok(Self::Post),
46
+ "PUT" => Ok(Self::Put),
47
+ "PATCH" => Ok(Self::Patch),
48
+ "DELETE" => Ok(Self::Delete),
49
+ "HEAD" => Ok(Self::Head),
50
+ "OPTIONS" => Ok(Self::Options),
51
+ "TRACE" => Ok(Self::Trace),
52
+ _ => Err(format!("Unknown HTTP method: {s}")),
52
53
  }
53
54
  }
54
55
  }
@@ -29,10 +29,10 @@ pub trait NativeLifecycleHook<Req, Resp>: Send + Sync {
29
29
  fn name(&self) -> &str;
30
30
 
31
31
  /// Execute hook with a request
32
- fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureSend<'a, Req, Resp>;
32
+ fn execute_request<'a>(&self, req: Req) -> RequestHookFutureSend<'a, Req, Resp>;
33
33
 
34
34
  /// Execute hook with a response
35
- fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureSend<'a, Resp>;
35
+ fn execute_response<'a>(&self, resp: Resp) -> ResponseHookFutureSend<'a, Resp>;
36
36
  }
37
37
 
38
38
  /// Trait for lifecycle hooks on local (wasm) targets (no Send requirements).
@@ -41,10 +41,10 @@ pub trait LocalLifecycleHook<Req, Resp> {
41
41
  fn name(&self) -> &str;
42
42
 
43
43
  /// Execute hook with a request
44
- fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureLocal<'a, Req, Resp>;
44
+ fn execute_request<'a>(&self, req: Req) -> RequestHookFutureLocal<'a, Req, Resp>;
45
45
 
46
46
  /// Execute hook with a response
47
- fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureLocal<'a, Resp>;
47
+ fn execute_response<'a>(&self, resp: Resp) -> ResponseHookFutureLocal<'a, Resp>;
48
48
  }
49
49
 
50
50
  #[cfg(target_arch = "wasm32")]
@@ -97,17 +97,19 @@ impl<Req, Resp> std::fmt::Debug for LifecycleHooks<Req, Resp> {
97
97
 
98
98
  impl<Req, Resp> LifecycleHooks<Req, Resp> {
99
99
  /// Create a new empty hooks container
100
+ #[must_use]
100
101
  pub fn new() -> Self {
101
102
  Self::default()
102
103
  }
103
104
 
104
105
  /// Builder constructor for ergonomic hook registration
106
+ #[must_use]
105
107
  pub fn builder() -> LifecycleHooksBuilder<Req, Resp> {
106
108
  LifecycleHooksBuilder::new()
107
109
  }
108
110
 
109
111
  /// Check if any hooks are registered
110
- #[inline(always)]
112
+ #[must_use]
111
113
  pub fn is_empty(&self) -> bool {
112
114
  self.on_request.is_empty()
113
115
  && self.pre_validation.is_empty()
@@ -136,6 +138,8 @@ impl<Req, Resp> LifecycleHooks<Req, Resp> {
136
138
  self.on_error.push(hook);
137
139
  }
138
140
 
141
+ /// # Errors
142
+ /// Returns an error string if a hook execution fails.
139
143
  pub async fn execute_on_request(&self, mut req: Req) -> Result<HookResult<Req, Resp>, String> {
140
144
  if self.on_request.is_empty() {
141
145
  return Ok(HookResult::Continue(req));
@@ -151,6 +155,8 @@ impl<Req, Resp> LifecycleHooks<Req, Resp> {
151
155
  Ok(HookResult::Continue(req))
152
156
  }
153
157
 
158
+ /// # Errors
159
+ /// Returns an error string if a hook execution fails.
154
160
  pub async fn execute_pre_validation(&self, mut req: Req) -> Result<HookResult<Req, Resp>, String> {
155
161
  if self.pre_validation.is_empty() {
156
162
  return Ok(HookResult::Continue(req));
@@ -166,6 +172,8 @@ impl<Req, Resp> LifecycleHooks<Req, Resp> {
166
172
  Ok(HookResult::Continue(req))
167
173
  }
168
174
 
175
+ /// # Errors
176
+ /// Returns an error string if a hook execution fails.
169
177
  pub async fn execute_pre_handler(&self, mut req: Req) -> Result<HookResult<Req, Resp>, String> {
170
178
  if self.pre_handler.is_empty() {
171
179
  return Ok(HookResult::Continue(req));
@@ -181,6 +189,8 @@ impl<Req, Resp> LifecycleHooks<Req, Resp> {
181
189
  Ok(HookResult::Continue(req))
182
190
  }
183
191
 
192
+ /// # Errors
193
+ /// Returns an error string if a hook execution fails.
184
194
  pub async fn execute_on_response(&self, mut resp: Resp) -> Result<Resp, String> {
185
195
  if self.on_response.is_empty() {
186
196
  return Ok(resp);
@@ -188,14 +198,15 @@ impl<Req, Resp> LifecycleHooks<Req, Resp> {
188
198
 
189
199
  for hook in &self.on_response {
190
200
  match hook.execute_response(resp).await? {
191
- HookResult::Continue(r) => resp = r,
192
- HookResult::ShortCircuit(r) => resp = r,
201
+ HookResult::Continue(r) | HookResult::ShortCircuit(r) => resp = r,
193
202
  }
194
203
  }
195
204
 
196
205
  Ok(resp)
197
206
  }
198
207
 
208
+ /// # Errors
209
+ /// Returns an error string if a hook execution fails.
199
210
  pub async fn execute_on_error(&self, mut resp: Resp) -> Result<Resp, String> {
200
211
  if self.on_error.is_empty() {
201
212
  return Ok(resp);
@@ -203,8 +214,7 @@ impl<Req, Resp> LifecycleHooks<Req, Resp> {
203
214
 
204
215
  for hook in &self.on_error {
205
216
  match hook.execute_response(resp).await? {
206
- HookResult::Continue(r) => resp = r,
207
- HookResult::ShortCircuit(r) => resp = r,
217
+ HookResult::Continue(r) | HookResult::ShortCircuit(r) => resp = r,
208
218
  }
209
219
  }
210
220
 
@@ -237,11 +247,11 @@ where
237
247
  &self.name
238
248
  }
239
249
 
240
- fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureSend<'a, Req, Resp> {
250
+ fn execute_request<'a>(&self, req: Req) -> RequestHookFutureSend<'a, Req, Resp> {
241
251
  Box::pin((self.func)(req))
242
252
  }
243
253
 
244
- fn execute_response<'a>(&'a self, _resp: Resp) -> ResponseHookFutureSend<'a, Resp> {
254
+ fn execute_response<'a>(&self, _resp: Resp) -> ResponseHookFutureSend<'a, Resp> {
245
255
  Box::pin(async move { Err("Request hook called with response - this is a bug".to_string()) })
246
256
  }
247
257
  }
@@ -258,11 +268,11 @@ where
258
268
  &self.name
259
269
  }
260
270
 
261
- fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureLocal<'a, Req, Resp> {
271
+ fn execute_request<'a>(&self, req: Req) -> RequestHookFutureLocal<'a, Req, Resp> {
262
272
  Box::pin((self.func)(req))
263
273
  }
264
274
 
265
- fn execute_response<'a>(&'a self, _resp: Resp) -> ResponseHookFutureLocal<'a, Resp> {
275
+ fn execute_response<'a>(&self, _resp: Resp) -> ResponseHookFutureLocal<'a, Resp> {
266
276
  Box::pin(async move { Err("Request hook called with response - this is a bug".to_string()) })
267
277
  }
268
278
  }
@@ -279,11 +289,11 @@ where
279
289
  &self.name
280
290
  }
281
291
 
282
- fn execute_request<'a>(&'a self, _req: Req) -> RequestHookFutureSend<'a, Req, Resp> {
292
+ fn execute_request<'a>(&self, _req: Req) -> RequestHookFutureSend<'a, Req, Resp> {
283
293
  Box::pin(async move { Err("Response hook called with request - this is a bug".to_string()) })
284
294
  }
285
295
 
286
- fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureSend<'a, Resp> {
296
+ fn execute_response<'a>(&self, resp: Resp) -> ResponseHookFutureSend<'a, Resp> {
287
297
  Box::pin((self.func)(resp))
288
298
  }
289
299
  }
@@ -300,52 +310,66 @@ where
300
310
  &self.name
301
311
  }
302
312
 
303
- fn execute_request<'a>(&'a self, _req: Req) -> RequestHookFutureLocal<'a, Req, Resp> {
313
+ fn execute_request<'a>(&self, _req: Req) -> RequestHookFutureLocal<'a, Req, Resp> {
304
314
  Box::pin(async move { Err("Response hook called with request - this is a bug".to_string()) })
305
315
  }
306
316
 
307
- fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureLocal<'a, Resp> {
317
+ fn execute_response<'a>(&self, resp: Resp) -> ResponseHookFutureLocal<'a, Resp> {
308
318
  Box::pin((self.func)(resp))
309
319
  }
310
320
  }
311
321
 
312
- /// Builder Pattern for LifecycleHooks
322
+ /// Builder pattern for `LifecycleHooks`
313
323
  pub struct LifecycleHooksBuilder<Req, Resp> {
314
324
  hooks: LifecycleHooks<Req, Resp>,
315
325
  }
316
326
 
317
327
  impl<Req, Resp> LifecycleHooksBuilder<Req, Resp> {
328
+ /// Create a new builder
329
+ #[must_use]
318
330
  pub fn new() -> Self {
319
331
  Self {
320
332
  hooks: LifecycleHooks::default(),
321
333
  }
322
334
  }
323
335
 
336
+ /// Add an `on_request` hook
337
+ #[must_use]
324
338
  pub fn on_request(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
325
339
  self.hooks.add_on_request(hook);
326
340
  self
327
341
  }
328
342
 
343
+ /// Add a `pre_validation` hook
344
+ #[must_use]
329
345
  pub fn pre_validation(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
330
346
  self.hooks.add_pre_validation(hook);
331
347
  self
332
348
  }
333
349
 
350
+ /// Add a `pre_handler` hook
351
+ #[must_use]
334
352
  pub fn pre_handler(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
335
353
  self.hooks.add_pre_handler(hook);
336
354
  self
337
355
  }
338
356
 
357
+ /// Add an `on_response` hook
358
+ #[must_use]
339
359
  pub fn on_response(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
340
360
  self.hooks.add_on_response(hook);
341
361
  self
342
362
  }
343
363
 
364
+ /// Add an `on_error` hook
365
+ #[must_use]
344
366
  pub fn on_error(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
345
367
  self.hooks.add_on_error(hook);
346
368
  self
347
369
  }
348
370
 
371
+ /// Build the `LifecycleHooks` instance
372
+ #[must_use]
349
373
  pub fn build(self) -> LifecycleHooks<Req, Resp> {
350
374
  self.hooks
351
375
  }
@@ -69,6 +69,9 @@ impl ParameterValidator {
69
69
  ///
70
70
  /// The schema should describe all parameters with their types and constraints.
71
71
  /// Each property MUST have a "source" field indicating where the parameter comes from.
72
+ ///
73
+ /// # Errors
74
+ /// Returns an error if the schema is invalid or malformed.
72
75
  pub fn new(schema: Value) -> Result<Self, String> {
73
76
  let parameter_defs = Self::extract_parameter_defs(&schema)?;
74
77
  let validation_schema = Self::create_validation_schema(&schema);
@@ -88,6 +91,7 @@ impl ParameterValidator {
88
91
  }
89
92
 
90
93
  /// Whether this validator needs access to request headers.
94
+ #[must_use]
91
95
  pub fn requires_headers(&self) -> bool {
92
96
  self.inner
93
97
  .parameter_defs
@@ -96,6 +100,7 @@ impl ParameterValidator {
96
100
  }
97
101
 
98
102
  /// Whether this validator needs access to request cookies.
103
+ #[must_use]
99
104
  pub fn requires_cookies(&self) -> bool {
100
105
  self.inner
101
106
  .parameter_defs
@@ -104,6 +109,7 @@ impl ParameterValidator {
104
109
  }
105
110
 
106
111
  /// Whether the validator has any parameter definitions.
112
+ #[must_use]
107
113
  pub fn has_params(&self) -> bool {
108
114
  !self.inner.parameter_defs.is_empty()
109
115
  }
@@ -125,12 +131,22 @@ impl ParameterValidator {
125
131
 
126
132
  for (key, child) in obj {
127
133
  match key.as_str() {
128
- // Structural keywords we support in the coercion pass.
129
- "type" | "format" | "properties" | "required" | "items" | "additionalProperties" => {}
130
-
131
- // Metadata keywords which don't affect validation semantics.
132
- "title" | "description" | "default" | "examples" | "deprecated" | "readOnly" | "writeOnly"
133
- | "$schema" | "$id" => {}
134
+ // Structural keywords we support in the coercion pass, and metadata keywords.
135
+ "type"
136
+ | "format"
137
+ | "properties"
138
+ | "required"
139
+ | "items"
140
+ | "additionalProperties"
141
+ | "title"
142
+ | "description"
143
+ | "default"
144
+ | "examples"
145
+ | "deprecated"
146
+ | "readOnly"
147
+ | "writeOnly"
148
+ | "$schema"
149
+ | "$id" => {}
134
150
 
135
151
  // Anything else may impose constraints we don't enforce manually.
136
152
  _ => return true,
@@ -166,15 +182,14 @@ impl ParameterValidator {
166
182
  for (name, prop) in properties {
167
183
  let source_str = prop.get("source").and_then(|s| s.as_str()).ok_or_else(|| {
168
184
  anyhow::anyhow!("Invalid parameter schema")
169
- .context(format!("Parameter '{}' missing required 'source' field", name))
185
+ .context(format!("Parameter '{name}' missing required 'source' field"))
170
186
  .to_string()
171
187
  })?;
172
188
 
173
189
  let source = ParameterSource::from_str(source_str).ok_or_else(|| {
174
190
  anyhow::anyhow!("Invalid parameter schema")
175
191
  .context(format!(
176
- "Invalid source '{}' for parameter '{}' (expected: query, path, header, or cookie)",
177
- source_str, name
192
+ "Invalid source '{source_str}' for parameter '{name}' (expected: query, path, header, or cookie)"
178
193
  ))
179
194
  .to_string()
180
195
  })?;
@@ -182,7 +197,10 @@ impl ParameterValidator {
182
197
  let expected_type = prop.get("type").and_then(|t| t.as_str()).map(String::from);
183
198
  let format = prop.get("format").and_then(|f| f.as_str()).map(String::from);
184
199
 
185
- let is_optional = prop.get("optional").and_then(|v| v.as_bool()).unwrap_or(false);
200
+ let is_optional = prop
201
+ .get("optional")
202
+ .and_then(serde_json::Value::as_bool)
203
+ .unwrap_or(false);
186
204
  let required = required_list.contains(&name.as_str()) && !is_optional;
187
205
 
188
206
  let (lookup_key, error_key) = if source == ParameterSource::Header {
@@ -207,6 +225,7 @@ impl ParameterValidator {
207
225
  }
208
226
 
209
227
  /// Get the underlying JSON Schema
228
+ #[must_use]
210
229
  pub fn schema(&self) -> &Value {
211
230
  &self.inner.schema
212
231
  }
@@ -217,6 +236,10 @@ impl ParameterValidator {
217
236
  /// It performs type coercion (e.g., "123" → 123) based on the schema.
218
237
  ///
219
238
  /// Returns the validated JSON object that can be directly converted to Python kwargs.
239
+ ///
240
+ /// # Errors
241
+ /// Returns a validation error if parameter validation fails.
242
+ #[allow(clippy::too_many_lines)]
220
243
  pub fn validate_and_extract(
221
244
  &self,
222
245
  query_params: &Value,
@@ -279,11 +302,11 @@ impl ParameterValidator {
279
302
  "Input should be a valid boolean, unable to interpret input".to_string()
280
303
  }
281
304
  Some("string") => match item_format {
282
- Some("uuid") => format!("Input should be a valid UUID, {}", e),
283
- Some("date") => format!("Input should be a valid date, {}", e),
284
- Some("date-time") => format!("Input should be a valid datetime, {}", e),
285
- Some("time") => format!("Input should be a valid time, {}", e),
286
- Some("duration") => format!("Input should be a valid duration, {}", e),
305
+ Some("uuid") => format!("Input should be a valid UUID, {e}"),
306
+ Some("date") => format!("Input should be a valid date, {e}"),
307
+ Some("date-time") => format!("Input should be a valid datetime, {e}"),
308
+ Some("time") => format!("Input should be a valid time, {e}"),
309
+ Some("duration") => format!("Input should be a valid duration, {e}"),
287
310
  _ => e,
288
311
  },
289
312
  _ => e,
@@ -303,6 +326,7 @@ impl ParameterValidator {
303
326
  };
304
327
  let (item_type, item_format) = self.array_item_type_and_format(&param_def.name);
305
328
 
329
+ #[allow(clippy::option_if_let_else)]
306
330
  let coerced_items = match array_value.as_array() {
307
331
  Some(items) => {
308
332
  let mut out = Vec::with_capacity(items.len());
@@ -332,11 +356,11 @@ impl ParameterValidator {
332
356
  Some("number") => "Input should be a valid number, unable to parse string as a number".to_string(),
333
357
  Some("boolean") => "Input should be a valid boolean, unable to interpret input".to_string(),
334
358
  Some("string") => match item_format {
335
- Some("uuid") => format!("Input should be a valid UUID, {}", e),
336
- Some("date") => format!("Input should be a valid date, {}", e),
337
- Some("date-time") => format!("Input should be a valid datetime, {}", e),
338
- Some("time") => format!("Input should be a valid time, {}", e),
339
- Some("duration") => format!("Input should be a valid duration, {}", e),
359
+ Some("uuid") => format!("Input should be a valid UUID, {e}"),
360
+ Some("date") => format!("Input should be a valid date, {e}"),
361
+ Some("date-time") => format!("Input should be a valid datetime, {e}"),
362
+ Some("time") => format!("Input should be a valid time, {e}"),
363
+ Some("duration") => format!("Input should be a valid duration, {e}"),
340
364
  _ => e.clone(),
341
365
  },
342
366
  _ => e.clone(),
@@ -410,19 +434,19 @@ impl ParameterValidator {
410
434
  "Input should be a valid boolean, unable to interpret input".to_string(),
411
435
  ),
412
436
  (Some("string"), Some("uuid")) => {
413
- ("uuid_parsing", format!("Input should be a valid UUID, {}", e))
437
+ ("uuid_parsing", format!("Input should be a valid UUID, {e}"))
414
438
  }
415
439
  (Some("string"), Some("date")) => {
416
- ("date_parsing", format!("Input should be a valid date, {}", e))
440
+ ("date_parsing", format!("Input should be a valid date, {e}"))
417
441
  }
418
442
  (Some("string"), Some("date-time")) => {
419
- ("datetime_parsing", format!("Input should be a valid datetime, {}", e))
443
+ ("datetime_parsing", format!("Input should be a valid datetime, {e}"))
420
444
  }
421
445
  (Some("string"), Some("time")) => {
422
- ("time_parsing", format!("Input should be a valid time, {}", e))
446
+ ("time_parsing", format!("Input should be a valid time, {e}"))
423
447
  }
424
448
  (Some("string"), Some("duration")) => {
425
- ("duration_parsing", format!("Input should be a valid duration, {}", e))
449
+ ("duration_parsing", format!("Input should be a valid duration, {e}"))
426
450
  }
427
451
  _ => ("type_error", e),
428
452
  };
@@ -445,7 +469,7 @@ impl ParameterValidator {
445
469
  let params_json = Value::Object(params_map);
446
470
  if let Some(schema_validator) = &self.inner.schema_validator {
447
471
  match schema_validator.validate(&params_json) {
448
- Ok(_) => Ok(params_json),
472
+ Ok(()) => Ok(params_json),
449
473
  Err(mut validation_err) => {
450
474
  for error in &mut validation_err.errors {
451
475
  if error.loc.len() >= 2 && error.loc[0] == "body" {
@@ -459,7 +483,7 @@ impl ParameterValidator {
459
483
  };
460
484
  error.loc[0] = source_str.to_string();
461
485
  if param_def.source == ParameterSource::Header {
462
- error.loc[1] = param_def.error_key.clone();
486
+ error.loc[1].clone_from(&param_def.error_key);
463
487
  }
464
488
  if let Some(raw_value) =
465
489
  self.raw_value_for_error(param_def, raw_query_params, path_params, headers, cookies)
@@ -477,6 +501,7 @@ impl ParameterValidator {
477
501
  }
478
502
  }
479
503
 
504
+ #[allow(clippy::unused_self)]
480
505
  fn raw_value_for_error<'a>(
481
506
  &self,
482
507
  param_def: &ParameterDef,
@@ -485,6 +510,7 @@ impl ParameterValidator {
485
510
  headers: &'a HashMap<String, String>,
486
511
  cookies: &'a HashMap<String, String>,
487
512
  ) -> Option<&'a str> {
513
+ #[allow(clippy::too_many_arguments)]
488
514
  match param_def.source {
489
515
  ParameterSource::Query => raw_query_params
490
516
  .get(&param_def.lookup_key)
@@ -548,11 +574,11 @@ impl ParameterValidator {
548
574
  Some("integer") => value
549
575
  .parse::<i64>()
550
576
  .map(|i| json!(i))
551
- .map_err(|e| format!("Invalid integer: {}", e)),
577
+ .map_err(|e| format!("Invalid integer: {e}")),
552
578
  Some("number") => value
553
579
  .parse::<f64>()
554
580
  .map(|f| json!(f))
555
- .map_err(|e| format!("Invalid number: {}", e)),
581
+ .map_err(|e| format!("Invalid number: {e}")),
556
582
  Some("boolean") => {
557
583
  if value.is_empty() {
558
584
  return Ok(json!(false));
@@ -563,7 +589,7 @@ impl ParameterValidator {
563
589
  } else if value_lower == "false" || value == "0" {
564
590
  Ok(json!(false))
565
591
  } else {
566
- Err(format!("Invalid boolean: {}", value))
592
+ Err(format!("Invalid boolean: {value}"))
567
593
  }
568
594
  }
569
595
  _ => Ok(json!(value)),
@@ -574,7 +600,7 @@ impl ParameterValidator {
574
600
  fn validate_date_format(value: &str) -> Result<(), String> {
575
601
  jiff::civil::Date::strptime("%Y-%m-%d", value)
576
602
  .map(|_| ())
577
- .map_err(|e| format!("Invalid date format: {}", e))
603
+ .map_err(|e| format!("Invalid date format: {e}"))
578
604
  }
579
605
 
580
606
  /// Validate ISO 8601 datetime format
@@ -582,7 +608,7 @@ impl ParameterValidator {
582
608
  use std::str::FromStr;
583
609
  jiff::Timestamp::from_str(value)
584
610
  .map(|_| ())
585
- .map_err(|e| format!("Invalid datetime format: {}", e))
611
+ .map_err(|e| format!("Invalid datetime format: {e}"))
586
612
  }
587
613
 
588
614
  /// Validate ISO 8601 time format: HH:MM:SS or HH:MM:SS.ffffff
@@ -608,7 +634,7 @@ impl ParameterValidator {
608
634
  };
609
635
 
610
636
  let base_time = time_part.split('.').next().unwrap_or(time_part);
611
- jiff::civil::Time::strptime("%H:%M:%S", base_time).map_err(|e| format!("Invalid time format: {}", e))?;
637
+ jiff::civil::Time::strptime("%H:%M:%S", base_time).map_err(|e| format!("Invalid time format: {e}"))?;
612
638
 
613
639
  if let Some((_, frac)) = time_part.split_once('.')
614
640
  && (frac.is_empty() || frac.len() > 9 || !frac.chars().all(|c| c.is_ascii_digit()))
@@ -648,7 +674,7 @@ impl ParameterValidator {
648
674
  use std::str::FromStr;
649
675
  jiff::Span::from_str(value)
650
676
  .map(|_| ())
651
- .map_err(|e| format!("Invalid duration format: {}", e))
677
+ .map_err(|e| format!("Invalid duration format: {e}"))
652
678
  }
653
679
 
654
680
  /// Validate UUID format
@@ -671,7 +697,7 @@ impl ParameterValidator {
671
697
  for (name, prop) in properties.iter_mut() {
672
698
  if let Some(obj) = prop.as_object_mut() {
673
699
  obj.remove("source");
674
- if obj.get("optional").and_then(|v| v.as_bool()) == Some(true) {
700
+ if obj.get("optional").and_then(serde_json::Value::as_bool) == Some(true) {
675
701
  optional_fields.push(name.clone());
676
702
  }
677
703
  obj.remove("optional");