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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 812ea62c2af7b3d443a8485aa055c197cdb1b65b04a3cc916ffd7bd6b0dfc81a
4
- data.tar.gz: 825b7329c8ce47f3d74ebcc97fdbfa5ebbd5cbc16294b2721362bc1bd62fc7c6
3
+ metadata.gz: add40ab33a80f54c472f6b888638ec6e3859a05d616a5233d5347c3b07ee414d
4
+ data.tar.gz: cf1ab4efa6bacd7edc6c23ffebd96382804754811b9e9da7e91300c6eee9af3b
5
5
  SHA512:
6
- metadata.gz: 8c27c069e2a10c5dc6b43f721f5baf2c56f5361f3fde3362b7ee3242b0deda62d9e4f198853cbba3e3f44c5f7aa0028495623961299fa99cc9bc9518d6f726a6
7
- data.tar.gz: 6179700d82a1aff3aa93b87ac1152be28722eda2559a8becd8946d3e462cd32acc73328154c4906b79fab39e0188f0e7d84fefaa0e2aa4cc18358bb0fec6a84e
6
+ metadata.gz: 4e569ced4b7aba06c0f2c590807517987e8c6dd4a37da90a0c4caf248f99d3413482a418b429e183ac02c6ca1a658c1bb99a00e17104747057ecf21e74b56a13
7
+ data.tar.gz: 0b87cf1a9c439c58e078d0104b6b86e8264d28b194b9044f03d2cfdc49c11a1b33c5997523ead13bd5a0a720a1600a07908d414e876798c1c3cd4613de07d30d
@@ -2515,7 +2515,7 @@ dependencies = [
2515
2515
 
2516
2516
  [[package]]
2517
2517
  name = "spikard-bindings-shared"
2518
- version = "0.7.5"
2518
+ version = "0.8.2"
2519
2519
  dependencies = [
2520
2520
  "axum",
2521
2521
  "http",
@@ -2532,7 +2532,7 @@ dependencies = [
2532
2532
 
2533
2533
  [[package]]
2534
2534
  name = "spikard-core"
2535
- version = "0.7.5"
2535
+ version = "0.8.2"
2536
2536
  dependencies = [
2537
2537
  "anyhow",
2538
2538
  "base64",
@@ -2556,7 +2556,7 @@ dependencies = [
2556
2556
 
2557
2557
  [[package]]
2558
2558
  name = "spikard-http"
2559
- version = "0.7.5"
2559
+ version = "0.8.2"
2560
2560
  dependencies = [
2561
2561
  "anyhow",
2562
2562
  "axum",
@@ -2603,7 +2603,7 @@ dependencies = [
2603
2603
 
2604
2604
  [[package]]
2605
2605
  name = "spikard-rb"
2606
- version = "0.7.5"
2606
+ version = "0.8.2"
2607
2607
  dependencies = [
2608
2608
  "async-stream",
2609
2609
  "axum",
@@ -2633,7 +2633,7 @@ dependencies = [
2633
2633
 
2634
2634
  [[package]]
2635
2635
  name = "spikard-rb-ext"
2636
- version = "0.7.5"
2636
+ version = "0.8.2"
2637
2637
  dependencies = [
2638
2638
  "magnus",
2639
2639
  "spikard-rb",
@@ -2641,7 +2641,7 @@ dependencies = [
2641
2641
 
2642
2642
  [[package]]
2643
2643
  name = "spikard-rb-macros"
2644
- version = "0.7.5"
2644
+ version = "0.8.2"
2645
2645
  dependencies = [
2646
2646
  "proc-macro2",
2647
2647
  "quote",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-rb-ext"
3
- version = "0.8.2"
3
+ version = "0.8.3"
4
4
  edition = "2024"
5
5
  license = "MIT"
6
6
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spikard
4
- VERSION = '0.8.2'
4
+ VERSION = '0.8.3'
5
5
  end
@@ -1,12 +1,20 @@
1
1
  [package]
2
2
  name = "spikard-bindings-shared"
3
- version = "0.8.2"
3
+ version = "0.8.3"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
7
7
  repository = "https://github.com/Goldziher/spikard"
8
8
  homepage = "https://github.com/Goldziher/spikard"
9
9
 
10
+ [lints.rust]
11
+ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
12
+
13
+ [lints.clippy]
14
+ all = { level = "deny", priority = 0 }
15
+ pedantic = { level = "deny", priority = 0 }
16
+ nursery = { level = "deny", priority = 0 }
17
+
10
18
  [dependencies]
11
19
  serde = { version = "1.0", features = ["derive"] }
12
20
  serde_json = "1.0"
@@ -1,6 +1,6 @@
1
1
  //! Configuration extraction trait and implementation for language bindings
2
2
  //!
3
- //! This module provides a trait-based abstraction for extracting ServerConfig and related
3
+ //! This module provides a trait-based abstraction for extracting `ServerConfig` and related
4
4
  //! configuration structs from language-specific objects (Python dicts, JavaScript objects, etc.)
5
5
  //! without duplicating extraction logic across bindings.
6
6
  //!
@@ -17,7 +17,7 @@ use std::collections::HashMap;
17
17
  /// Trait for reading configuration from language-specific objects
18
18
  ///
19
19
  /// Bindings implement this trait to provide unified access to configuration values
20
- /// regardless of the language-specific representation (PyDict, JavaScript Object, etc.).
20
+ /// regardless of the language-specific representation (`PyDict`, JavaScript Object, etc.).
21
21
  pub trait ConfigSource {
22
22
  /// Get a boolean value from the source
23
23
  fn get_bool(&self, key: &str) -> Option<bool>;
@@ -34,7 +34,7 @@ pub trait ConfigSource {
34
34
  /// Get a vector of strings from the source
35
35
  fn get_vec_string(&self, key: &str) -> Option<Vec<String>>;
36
36
 
37
- /// Get a nested ConfigSource for nested objects
37
+ /// Get a nested `ConfigSource` for nested objects
38
38
  fn get_nested(&self, key: &str) -> Option<Box<dyn ConfigSource + '_>>;
39
39
 
40
40
  /// Check if a key exists in the source
@@ -61,11 +61,15 @@ pub trait ConfigSource {
61
61
  }
62
62
  }
63
63
 
64
- /// Configuration extractor that works with any ConfigSource
64
+ /// Configuration extractor that works with any `ConfigSource`
65
65
  pub struct ConfigExtractor;
66
66
 
67
67
  impl ConfigExtractor {
68
- /// Extract a complete ServerConfig from a ConfigSource
68
+ /// Extract a complete `ServerConfig` from a `ConfigSource`
69
+ ///
70
+ /// # Errors
71
+ ///
72
+ /// Returns an error if required configuration fields are invalid or missing.
69
73
  pub fn extract_server_config(source: &dyn ConfigSource) -> Result<ServerConfig, String> {
70
74
  let mut config = ServerConfig::default();
71
75
 
@@ -73,10 +77,14 @@ impl ConfigExtractor {
73
77
  config.host = host;
74
78
  }
75
79
 
76
- if let Some(port) = source
77
- .get_u16("port")
78
- .or_else(|| source.get_u32("port").map(|p| p as u16))
79
- {
80
+ if let Some(port) = source.get_u16("port").or_else(|| {
81
+ source.get_u32("port").map(|p| {
82
+ #[allow(clippy::cast_possible_truncation)]
83
+ {
84
+ p as u16
85
+ }
86
+ })
87
+ }) {
80
88
  config.port = port;
81
89
  }
82
90
 
@@ -144,7 +152,11 @@ impl ConfigExtractor {
144
152
  Ok(config)
145
153
  }
146
154
 
147
- /// Extract CompressionConfig from a ConfigSource
155
+ /// Extract `CompressionConfig` from a `ConfigSource`
156
+ ///
157
+ /// # Errors
158
+ ///
159
+ /// Returns an error if required configuration fields are invalid.
148
160
  pub fn extract_compression_config(source: &dyn ConfigSource) -> Result<CompressionConfig, String> {
149
161
  let gzip = source.get_bool("gzip").unwrap_or(true);
150
162
  let brotli = source.get_bool("brotli").unwrap_or(true);
@@ -162,7 +174,11 @@ impl ConfigExtractor {
162
174
  })
163
175
  }
164
176
 
165
- /// Extract RateLimitConfig from a ConfigSource
177
+ /// Extract `RateLimitConfig` from a `ConfigSource`
178
+ ///
179
+ /// # Errors
180
+ ///
181
+ /// Returns an error if required fields `per_second` or `burst` are missing.
166
182
  pub fn extract_rate_limit_config(source: &dyn ConfigSource) -> Result<RateLimitConfig, String> {
167
183
  let per_second = source.get_u64("per_second").ok_or("Rate limit requires 'per_second'")?;
168
184
 
@@ -177,7 +193,11 @@ impl ConfigExtractor {
177
193
  })
178
194
  }
179
195
 
180
- /// Extract JwtConfig from a ConfigSource
196
+ /// Extract `JwtConfig` from a `ConfigSource`
197
+ ///
198
+ /// # Errors
199
+ ///
200
+ /// Returns an error if the required `secret` field is missing.
181
201
  pub fn extract_jwt_config(source: &dyn ConfigSource) -> Result<JwtConfig, String> {
182
202
  let secret = source.get_string("secret").ok_or("JWT auth requires 'secret'")?;
183
203
 
@@ -198,7 +218,11 @@ impl ConfigExtractor {
198
218
  })
199
219
  }
200
220
 
201
- /// Extract ApiKeyConfig from a ConfigSource
221
+ /// Extract `ApiKeyConfig` from a `ConfigSource`
222
+ ///
223
+ /// # Errors
224
+ ///
225
+ /// Returns an error if the required `keys` field is missing.
202
226
  pub fn extract_api_key_config(source: &dyn ConfigSource) -> Result<ApiKeyConfig, String> {
203
227
  let keys = source
204
228
  .get_vec_string("keys")
@@ -211,7 +235,11 @@ impl ConfigExtractor {
211
235
  Ok(ApiKeyConfig { keys, header_name })
212
236
  }
213
237
 
214
- /// Extract static files configuration list from a ConfigSource
238
+ /// Extract static files configuration list from a `ConfigSource`
239
+ ///
240
+ /// # Errors
241
+ ///
242
+ /// Returns an error if array elements are invalid or missing required fields.
215
243
  pub fn extract_static_files_config(source: &dyn ConfigSource) -> Result<Vec<StaticFilesConfig>, String> {
216
244
  let length = source.get_array_length("static_files").unwrap_or(0);
217
245
  if length == 0 {
@@ -247,7 +275,11 @@ impl ConfigExtractor {
247
275
  Ok(configs)
248
276
  }
249
277
 
250
- /// Extract OpenApiConfig from a ConfigSource
278
+ /// Extract `OpenApiConfig` from a `ConfigSource`
279
+ ///
280
+ /// # Errors
281
+ ///
282
+ /// Returns an error if required configuration fields are invalid.
251
283
  pub fn extract_openapi_config(source: &dyn ConfigSource) -> Result<OpenApiConfig, String> {
252
284
  let enabled = source.get_bool("enabled").unwrap_or(false);
253
285
  let title = source.get_string("title").unwrap_or_else(|| "API".to_string());
@@ -279,7 +311,7 @@ impl ConfigExtractor {
279
311
 
280
312
  let servers = Self::extract_servers_config(source)?;
281
313
 
282
- let security_schemes = Self::extract_security_schemes_config(source)?;
314
+ let security_schemes = Self::extract_security_schemes_config(source);
283
315
 
284
316
  Ok(OpenApiConfig {
285
317
  enabled,
@@ -296,7 +328,11 @@ impl ConfigExtractor {
296
328
  })
297
329
  }
298
330
 
299
- /// Extract servers list from OpenAPI config
331
+ /// Extract servers list from `OpenAPI` config
332
+ ///
333
+ /// # Errors
334
+ ///
335
+ /// Returns an error if array elements are invalid or missing.
300
336
  fn extract_servers_config(source: &dyn ConfigSource) -> Result<Vec<ServerInfo>, String> {
301
337
  let length = source.get_array_length("servers").unwrap_or(0);
302
338
  if length == 0 {
@@ -319,15 +355,17 @@ impl ConfigExtractor {
319
355
  Ok(servers)
320
356
  }
321
357
 
322
- /// Extract security schemes from OpenAPI config
323
- fn extract_security_schemes_config(
324
- _source: &dyn ConfigSource,
325
- ) -> Result<HashMap<String, SecuritySchemeInfo>, String> {
358
+ /// Extract security schemes from `OpenAPI` config
359
+ fn extract_security_schemes_config(_source: &dyn ConfigSource) -> HashMap<String, SecuritySchemeInfo> {
326
360
  // TODO: Implement when bindings support iterating HashMap-like structures
327
- Ok(HashMap::new())
361
+ HashMap::new()
328
362
  }
329
363
 
330
- /// Extract JsonRpcConfig from a ConfigSource
364
+ /// Extract `JsonRpcConfig` from a `ConfigSource`
365
+ ///
366
+ /// # Errors
367
+ ///
368
+ /// Returns an error if required configuration fields are invalid.
331
369
  pub fn extract_jsonrpc_config(source: &dyn ConfigSource) -> Result<JsonRpcConfig, String> {
332
370
  let enabled = source.get_bool("enabled").unwrap_or(true);
333
371
  let endpoint_path = source.get_string("endpoint_path").unwrap_or_else(|| "/rpc".to_string());
@@ -8,6 +8,10 @@ pub trait FromLanguage: Sized {
8
8
  type Error: std::fmt::Display;
9
9
 
10
10
  /// Convert from a language-specific value
11
+ ///
12
+ /// # Errors
13
+ ///
14
+ /// Returns an error if the value cannot be converted to the expected type.
11
15
  fn from_any(value: &(dyn Any + Send + Sync)) -> Result<Self, Self::Error>;
12
16
  }
13
17
 
@@ -17,6 +21,10 @@ pub trait ToLanguage {
17
21
  type Error: std::fmt::Display;
18
22
 
19
23
  /// Convert to a language-specific value
24
+ ///
25
+ /// # Errors
26
+ ///
27
+ /// Returns an error if the conversion fails.
20
28
  fn to_any(&self) -> Result<Box<dyn Any + Send + Sync>, Self::Error>;
21
29
  }
22
30
 
@@ -26,9 +34,17 @@ pub trait JsonConvertible: Sized {
26
34
  type Error: std::fmt::Display;
27
35
 
28
36
  /// Convert from a JSON value
37
+ ///
38
+ /// # Errors
39
+ ///
40
+ /// Returns an error if the JSON value is not valid for the target type.
29
41
  fn from_json(value: serde_json::Value) -> Result<Self, Self::Error>;
30
42
 
31
43
  /// Convert to a JSON value
44
+ ///
45
+ /// # Errors
46
+ ///
47
+ /// Returns an error if the conversion fails.
32
48
  fn to_json(&self) -> Result<serde_json::Value, Self::Error>;
33
49
  }
34
50
 
@@ -33,7 +33,7 @@ pub trait ValueDependencyAdapter: Send + Sync {
33
33
  /// Adapter trait for factory dependencies across language bindings
34
34
  ///
35
35
  /// Language bindings should implement this trait to wrap their
36
- /// language-specific callable storage (e.g., Py<PyAny>, ThreadsafeFunction, etc.)
36
+ /// language-specific callable storage (e.g., Py<PyAny>, `ThreadsafeFunction`, etc.)
37
37
  pub trait FactoryDependencyAdapter: Send + Sync {
38
38
  /// Get the dependency key
39
39
  fn key(&self) -> &str;
@@ -15,7 +15,7 @@ pub struct ErrorResponseBuilder;
15
15
  impl ErrorResponseBuilder {
16
16
  /// Create a structured error response with status code and error details
17
17
  ///
18
- /// Returns a tuple of (StatusCode, JSON body as String)
18
+ /// Returns a tuple of (`StatusCode`, JSON body as String)
19
19
  ///
20
20
  /// # Arguments
21
21
  /// * `status` - HTTP status code
@@ -42,7 +42,7 @@ impl ErrorResponseBuilder {
42
42
 
43
43
  /// Create an error response with additional details
44
44
  ///
45
- /// Returns a tuple of (StatusCode, JSON body as String)
45
+ /// Returns a tuple of (`StatusCode`, JSON body as String)
46
46
  ///
47
47
  /// # Arguments
48
48
  /// * `status` - HTTP status code
@@ -79,17 +79,18 @@ impl ErrorResponseBuilder {
79
79
  (status, body)
80
80
  }
81
81
 
82
- /// Create an error response from a StructuredError
82
+ /// Create an error response from a `StructuredError`
83
83
  ///
84
- /// Returns a tuple of (StatusCode, JSON body as String)
84
+ /// Returns a tuple of (`StatusCode`, JSON body as String)
85
85
  ///
86
86
  /// # Arguments
87
87
  /// * `error` - The structured error
88
88
  ///
89
89
  /// # Note
90
- /// Uses INTERNAL_SERVER_ERROR as the default status code. Override with
90
+ /// Uses `INTERNAL_SERVER_ERROR` as the default status code. Override with
91
91
  /// `structured_error()` or `with_details()` for specific status codes.
92
- pub fn from_structured_error(error: StructuredError) -> (StatusCode, String) {
92
+ #[must_use]
93
+ pub fn from_structured_error(error: &StructuredError) -> (StatusCode, String) {
93
94
  let status = StatusCode::INTERNAL_SERVER_ERROR;
94
95
  let body = serde_json::to_string(&error)
95
96
  .unwrap_or_else(|_| r#"{"error":"serialization_failed","code":"internal_error","details":{}}"#.to_string());
@@ -98,8 +99,8 @@ impl ErrorResponseBuilder {
98
99
 
99
100
  /// Create a validation error response
100
101
  ///
101
- /// Converts ValidationError to RFC 9457 Problem Details format.
102
- /// Returns (StatusCode::UNPROCESSABLE_ENTITY, JSON body)
102
+ /// Converts `ValidationError` to RFC 9457 Problem Details format.
103
+ /// Returns (`StatusCode::UNPROCESSABLE_ENTITY`, JSON body)
103
104
  ///
104
105
  /// # Arguments
105
106
  /// * `validation_error` - The validation error containing one or more details
@@ -124,6 +125,7 @@ impl ErrorResponseBuilder {
124
125
  ///
125
126
  /// let (status, body) = ErrorResponseBuilder::validation_error(&validation_error);
126
127
  /// ```
128
+ #[must_use]
127
129
  pub fn validation_error(validation_error: &ValidationError) -> (StatusCode, String) {
128
130
  let problem = ProblemDetails::from_validation_error(validation_error);
129
131
  let status = problem.status_code();
@@ -136,7 +138,7 @@ impl ErrorResponseBuilder {
136
138
 
137
139
  /// Create an RFC 9457 Problem Details response
138
140
  ///
139
- /// Returns a tuple of (StatusCode, JSON body as String)
141
+ /// Returns a tuple of (`StatusCode`, JSON body as String)
140
142
  ///
141
143
  /// # Arguments
142
144
  /// * `problem` - The Problem Details object
@@ -150,6 +152,7 @@ impl ErrorResponseBuilder {
150
152
  /// let problem = ProblemDetails::not_found("User with id 123 not found");
151
153
  /// let (status, body) = ErrorResponseBuilder::problem_details_response(&problem);
152
154
  /// ```
155
+ #[must_use]
153
156
  pub fn problem_details_response(problem: &ProblemDetails) -> (StatusCode, String) {
154
157
  let status = problem.status_code();
155
158
  let body = serde_json::to_string(problem).unwrap_or_else(|_| {
@@ -161,70 +164,70 @@ impl ErrorResponseBuilder {
161
164
 
162
165
  /// Create a generic bad request error
163
166
  ///
164
- /// Returns (StatusCode::BAD_REQUEST, JSON body)
167
+ /// Returns (`StatusCode::BAD_REQUEST`, JSON body)
165
168
  pub fn bad_request(message: impl Into<String>) -> (StatusCode, String) {
166
169
  Self::structured_error(StatusCode::BAD_REQUEST, "bad_request", message)
167
170
  }
168
171
 
169
172
  /// Create a generic internal server error
170
173
  ///
171
- /// Returns (StatusCode::INTERNAL_SERVER_ERROR, JSON body)
174
+ /// Returns (`StatusCode::INTERNAL_SERVER_ERROR`, JSON body)
172
175
  pub fn internal_error(message: impl Into<String>) -> (StatusCode, String) {
173
176
  Self::structured_error(StatusCode::INTERNAL_SERVER_ERROR, "internal_error", message)
174
177
  }
175
178
 
176
179
  /// Create an unauthorized error
177
180
  ///
178
- /// Returns (StatusCode::UNAUTHORIZED, JSON body)
181
+ /// Returns (`StatusCode::UNAUTHORIZED`, JSON body)
179
182
  pub fn unauthorized(message: impl Into<String>) -> (StatusCode, String) {
180
183
  Self::structured_error(StatusCode::UNAUTHORIZED, "unauthorized", message)
181
184
  }
182
185
 
183
186
  /// Create a forbidden error
184
187
  ///
185
- /// Returns (StatusCode::FORBIDDEN, JSON body)
188
+ /// Returns (`StatusCode::FORBIDDEN`, JSON body)
186
189
  pub fn forbidden(message: impl Into<String>) -> (StatusCode, String) {
187
190
  Self::structured_error(StatusCode::FORBIDDEN, "forbidden", message)
188
191
  }
189
192
 
190
193
  /// Create a not found error
191
194
  ///
192
- /// Returns (StatusCode::NOT_FOUND, JSON body)
195
+ /// Returns (`StatusCode::NOT_FOUND`, JSON body)
193
196
  pub fn not_found(message: impl Into<String>) -> (StatusCode, String) {
194
197
  Self::structured_error(StatusCode::NOT_FOUND, "not_found", message)
195
198
  }
196
199
 
197
200
  /// Create a method not allowed error
198
201
  ///
199
- /// Returns (StatusCode::METHOD_NOT_ALLOWED, JSON body)
202
+ /// Returns (`StatusCode::METHOD_NOT_ALLOWED`, JSON body)
200
203
  pub fn method_not_allowed(message: impl Into<String>) -> (StatusCode, String) {
201
204
  Self::structured_error(StatusCode::METHOD_NOT_ALLOWED, "method_not_allowed", message)
202
205
  }
203
206
 
204
207
  /// Create an unprocessable entity error (validation failed)
205
208
  ///
206
- /// Returns (StatusCode::UNPROCESSABLE_ENTITY, JSON body)
209
+ /// Returns (`StatusCode::UNPROCESSABLE_ENTITY`, JSON body)
207
210
  pub fn unprocessable_entity(message: impl Into<String>) -> (StatusCode, String) {
208
211
  Self::structured_error(StatusCode::UNPROCESSABLE_ENTITY, "unprocessable_entity", message)
209
212
  }
210
213
 
211
214
  /// Create a conflict error
212
215
  ///
213
- /// Returns (StatusCode::CONFLICT, JSON body)
216
+ /// Returns (`StatusCode::CONFLICT`, JSON body)
214
217
  pub fn conflict(message: impl Into<String>) -> (StatusCode, String) {
215
218
  Self::structured_error(StatusCode::CONFLICT, "conflict", message)
216
219
  }
217
220
 
218
221
  /// Create a service unavailable error
219
222
  ///
220
- /// Returns (StatusCode::SERVICE_UNAVAILABLE, JSON body)
223
+ /// Returns (`StatusCode::SERVICE_UNAVAILABLE`, JSON body)
221
224
  pub fn service_unavailable(message: impl Into<String>) -> (StatusCode, String) {
222
225
  Self::structured_error(StatusCode::SERVICE_UNAVAILABLE, "service_unavailable", message)
223
226
  }
224
227
 
225
228
  /// Create a request timeout error
226
229
  ///
227
- /// Returns (StatusCode::REQUEST_TIMEOUT, JSON body)
230
+ /// Returns (`StatusCode::REQUEST_TIMEOUT`, JSON body)
228
231
  pub fn request_timeout(message: impl Into<String>) -> (StatusCode, String) {
229
232
  Self::structured_error(StatusCode::REQUEST_TIMEOUT, "request_timeout", message)
230
233
  }
@@ -6,20 +6,20 @@
6
6
  use std::collections::HashMap;
7
7
  use tonic::metadata::{MetadataKey, MetadataMap, MetadataValue};
8
8
 
9
- /// Extract metadata from gRPC MetadataMap to a simple HashMap.
9
+ /// Extract metadata from gRPC `MetadataMap` to a simple `HashMap`.
10
10
  ///
11
- /// This function converts gRPC metadata to a language-agnostic HashMap format
11
+ /// This function converts gRPC metadata to a language-agnostic `HashMap` format
12
12
  /// that can be easily passed to language bindings. Only ASCII metadata is
13
13
  /// included; binary metadata is skipped with optional logging.
14
14
  ///
15
15
  /// # Arguments
16
16
  ///
17
- /// * `metadata` - The gRPC MetadataMap to extract from
17
+ /// * `metadata` - The gRPC `MetadataMap` to extract from
18
18
  /// * `log_binary_skip` - Whether to log when binary metadata is skipped
19
19
  ///
20
20
  /// # Returns
21
21
  ///
22
- /// A HashMap containing all ASCII metadata key-value pairs
22
+ /// A `HashMap` containing all ASCII metadata key-value pairs
23
23
  ///
24
24
  /// # Examples
25
25
  ///
@@ -55,19 +55,19 @@ pub fn extract_metadata_to_hashmap(metadata: &MetadataMap, log_binary_skip: bool
55
55
  map
56
56
  }
57
57
 
58
- /// Convert a HashMap to gRPC MetadataMap.
58
+ /// Convert a `HashMap` to gRPC `MetadataMap`.
59
59
  ///
60
- /// This function converts a language-agnostic HashMap into a gRPC MetadataMap
60
+ /// This function converts a language-agnostic `HashMap` into a gRPC `MetadataMap`
61
61
  /// that can be used in responses. All keys and values are validated and errors
62
62
  /// are returned if any are invalid.
63
63
  ///
64
64
  /// # Arguments
65
65
  ///
66
- /// * `map` - The HashMap to convert
66
+ /// * `map` - The `HashMap` to convert
67
67
  ///
68
68
  /// # Returns
69
69
  ///
70
- /// A Result containing the MetadataMap or an error message
70
+ /// A Result containing the `MetadataMap` or an error message
71
71
  ///
72
72
  /// # Errors
73
73
  ///
@@ -87,15 +87,17 @@ pub fn extract_metadata_to_hashmap(metadata: &MetadataMap, log_binary_skip: bool
87
87
  /// let metadata = hashmap_to_metadata(&map).unwrap();
88
88
  /// assert!(metadata.contains_key("content-type"));
89
89
  /// ```
90
- pub fn hashmap_to_metadata(map: &HashMap<String, String>) -> Result<MetadataMap, String> {
90
+ pub fn hashmap_to_metadata<S: std::hash::BuildHasher>(
91
+ map: &std::collections::HashMap<String, String, S>,
92
+ ) -> Result<MetadataMap, String> {
91
93
  let mut metadata = MetadataMap::new();
92
94
 
93
95
  for (key, value) in map {
94
- let metadata_key = MetadataKey::from_bytes(key.as_bytes())
95
- .map_err(|err| format!("Invalid metadata key '{}': {}", key, err))?;
96
+ let metadata_key =
97
+ MetadataKey::from_bytes(key.as_bytes()).map_err(|err| format!("Invalid metadata key '{key}': {err}"))?;
96
98
 
97
99
  let metadata_value =
98
- MetadataValue::try_from(value).map_err(|err| format!("Invalid metadata value for '{}': {}", key, err))?;
100
+ MetadataValue::try_from(value).map_err(|err| format!("Invalid metadata value for '{key}': {err}"))?;
99
101
 
100
102
  metadata.insert(metadata_key, metadata_value);
101
103
  }
@@ -33,7 +33,7 @@ pub enum HandlerError {
33
33
 
34
34
  impl From<ValidationError> for HandlerError {
35
35
  fn from(err: ValidationError) -> Self {
36
- HandlerError::Validation(format!("{:?}", err))
36
+ Self::Validation(format!("{err:?}"))
37
37
  }
38
38
  }
39
39
 
@@ -54,6 +54,10 @@ pub trait LanguageHandler: Send + Sync {
54
54
  type Output: Send;
55
55
 
56
56
  /// Prepare request data for passing to the language handler
57
+ ///
58
+ /// # Errors
59
+ ///
60
+ /// Returns an error if request preparation fails.
57
61
  fn prepare_request(&self, request_data: &RequestData) -> Result<Self::Input, HandlerError>;
58
62
 
59
63
  /// Invoke the language-specific handler with the prepared input
@@ -63,6 +67,10 @@ pub trait LanguageHandler: Send + Sync {
63
67
  ) -> Pin<Box<dyn Future<Output = Result<Self::Output, HandlerError>> + Send + '_>>;
64
68
 
65
69
  /// Interpret the handler's output and convert it to an HTTP response
70
+ ///
71
+ /// # Errors
72
+ ///
73
+ /// Returns an error if response interpretation fails.
66
74
  fn interpret_response(&self, output: Self::Output) -> Result<Response<Body>, HandlerError>;
67
75
  }
68
76
 
@@ -81,7 +89,7 @@ pub struct HandlerExecutor<L: LanguageHandler> {
81
89
 
82
90
  impl<L: LanguageHandler> HandlerExecutor<L> {
83
91
  /// Create a new handler executor
84
- pub fn new(language_handler: Arc<L>, request_validator: Option<Arc<SchemaValidator>>) -> Self {
92
+ pub const fn new(language_handler: Arc<L>, request_validator: Option<Arc<SchemaValidator>>) -> Self {
85
93
  Self {
86
94
  language_handler,
87
95
  request_validator,
@@ -89,7 +97,7 @@ impl<L: LanguageHandler> HandlerExecutor<L> {
89
97
  }
90
98
 
91
99
  /// Create a handler executor with only a language handler
92
- pub fn with_handler(language_handler: Arc<L>) -> Self {
100
+ pub const fn with_handler(language_handler: Arc<L>) -> Self {
93
101
  Self {
94
102
  language_handler,
95
103
  request_validator: None,
@@ -97,6 +105,7 @@ impl<L: LanguageHandler> HandlerExecutor<L> {
97
105
  }
98
106
 
99
107
  /// Add request validation to this executor
108
+ #[must_use]
100
109
  pub fn with_request_validator(mut self, validator: Arc<SchemaValidator>) -> Self {
101
110
  self.request_validator = Some(validator);
102
111
  self
@@ -119,18 +128,18 @@ impl<L: LanguageHandler + 'static> Handler for HandlerExecutor<L> {
119
128
  let input = self
120
129
  .language_handler
121
130
  .prepare_request(&request_data)
122
- .map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to prepare request: {}", e)))?;
131
+ .map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to prepare request: {e}")))?;
123
132
 
124
133
  let output = self
125
134
  .language_handler
126
135
  .invoke_handler(input)
127
136
  .await
128
- .map_err(|e| ErrorResponseBuilder::internal_error(format!("Handler execution failed: {}", e)))?;
137
+ .map_err(|e| ErrorResponseBuilder::internal_error(format!("Handler execution failed: {e}")))?;
129
138
 
130
139
  let response = self
131
140
  .language_handler
132
141
  .interpret_response(output)
133
- .map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to interpret response: {}", e)))?;
142
+ .map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to interpret response: {e}")))?;
134
143
 
135
144
  Ok(response)
136
145
  })