spikard 0.8.1 → 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 (60) 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/grpc.rb +5 -5
  5. data/lib/spikard/version.rb +1 -1
  6. data/vendor/crates/spikard-bindings-shared/Cargo.toml +9 -1
  7. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +61 -23
  8. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +16 -0
  9. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +1 -1
  10. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +22 -19
  11. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +16 -14
  12. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +15 -6
  13. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +6 -0
  14. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +42 -36
  15. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +6 -1
  16. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +18 -6
  17. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +28 -10
  18. data/vendor/crates/spikard-core/Cargo.toml +9 -1
  19. data/vendor/crates/spikard-core/src/bindings/response.rs +6 -9
  20. data/vendor/crates/spikard-core/src/debug.rs +2 -2
  21. data/vendor/crates/spikard-core/src/di/container.rs +1 -1
  22. data/vendor/crates/spikard-core/src/di/error.rs +1 -1
  23. data/vendor/crates/spikard-core/src/di/factory.rs +7 -3
  24. data/vendor/crates/spikard-core/src/di/graph.rs +1 -0
  25. data/vendor/crates/spikard-core/src/di/resolved.rs +23 -0
  26. data/vendor/crates/spikard-core/src/di/value.rs +1 -0
  27. data/vendor/crates/spikard-core/src/errors.rs +3 -0
  28. data/vendor/crates/spikard-core/src/http.rs +19 -18
  29. data/vendor/crates/spikard-core/src/lifecycle.rs +42 -18
  30. data/vendor/crates/spikard-core/src/metadata.rs +3 -14
  31. data/vendor/crates/spikard-core/src/parameters.rs +61 -35
  32. data/vendor/crates/spikard-core/src/problem.rs +18 -4
  33. data/vendor/crates/spikard-core/src/request_data.rs +9 -8
  34. data/vendor/crates/spikard-core/src/router.rs +20 -6
  35. data/vendor/crates/spikard-core/src/schema_registry.rs +23 -8
  36. data/vendor/crates/spikard-core/src/type_hints.rs +11 -5
  37. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +29 -15
  38. data/vendor/crates/spikard-core/src/validation/mod.rs +45 -32
  39. data/vendor/crates/spikard-http/Cargo.toml +8 -1
  40. data/vendor/crates/spikard-http/src/grpc/mod.rs +1 -1
  41. data/vendor/crates/spikard-http/src/grpc/service.rs +11 -11
  42. data/vendor/crates/spikard-http/src/grpc/streaming.rs +5 -1
  43. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +59 -20
  44. data/vendor/crates/spikard-http/src/server/routing_factory.rs +179 -201
  45. data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +49 -60
  46. data/vendor/crates/spikard-http/tests/common/handlers.rs +5 -5
  47. data/vendor/crates/spikard-http/tests/common/mod.rs +7 -8
  48. data/vendor/crates/spikard-http/tests/common/test_builders.rs +14 -19
  49. data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +68 -69
  50. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +1 -3
  51. data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +98 -84
  52. data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +76 -57
  53. data/vendor/crates/spikard-rb/Cargo.toml +9 -1
  54. data/vendor/crates/spikard-rb/build.rs +1 -0
  55. data/vendor/crates/spikard-rb/src/grpc/handler.rs +30 -25
  56. data/vendor/crates/spikard-rb/src/lib.rs +59 -2
  57. data/vendor/crates/spikard-rb/src/lifecycle.rs +2 -2
  58. data/vendor/crates/spikard-rb-macros/Cargo.toml +9 -1
  59. data/vendor/crates/spikard-rb-macros/src/lib.rs +4 -5
  60. metadata +1 -1
@@ -51,31 +51,35 @@ pub enum ErrorCondition {
51
51
 
52
52
  impl ErrorCondition {
53
53
  /// Determine the error condition from schema path and error message
54
+ #[must_use]
55
+ #[allow(clippy::ignored_unit_patterns)]
54
56
  pub fn from_schema_error(schema_path_str: &str, error_msg: &str) -> Self {
55
57
  match () {
56
- _ if schema_path_str.contains("minLength") => Self::StringTooShort { min_length: None },
57
- _ if schema_path_str.contains("maxLength") => Self::StringTooLong { max_length: None },
58
- _ if schema_path_str.contains("exclusiveMinimum")
58
+ () if schema_path_str.contains("minLength") => Self::StringTooShort { min_length: None },
59
+ () if schema_path_str.contains("maxLength") => Self::StringTooLong { max_length: None },
60
+ () if schema_path_str.contains("exclusiveMinimum")
59
61
  || (error_msg.contains("less than or equal to") && error_msg.contains("minimum")) =>
60
62
  {
61
63
  Self::GreaterThan { value: None }
62
64
  }
63
- _ if schema_path_str.contains("minimum") || error_msg.contains("less than the minimum") => {
65
+ () if schema_path_str.contains("minimum") || error_msg.contains("less than the minimum") => {
64
66
  Self::GreaterThanEqual { value: None }
65
67
  }
66
- _ if schema_path_str.contains("exclusiveMaximum")
68
+ () if schema_path_str.contains("exclusiveMaximum")
67
69
  || (error_msg.contains("greater than or equal to") && error_msg.contains("maximum")) =>
68
70
  {
69
71
  Self::LessThan { value: None }
70
72
  }
71
- _ if schema_path_str.contains("maximum") || error_msg.contains("greater than the maximum") => {
73
+ () if schema_path_str.contains("maximum") || error_msg.contains("greater than the maximum") => {
72
74
  Self::LessThanEqual { value: None }
73
75
  }
74
- _ if schema_path_str.contains("enum") || error_msg.contains("is not one of") => Self::Enum { values: None },
75
- _ if schema_path_str.contains("pattern") || error_msg.contains("does not match") => {
76
+ () if schema_path_str.contains("enum") || error_msg.contains("is not one of") => {
77
+ Self::Enum { values: None }
78
+ }
79
+ () if schema_path_str.contains("pattern") || error_msg.contains("does not match") => {
76
80
  Self::StringPatternMismatch { pattern: None }
77
81
  }
78
- _ if schema_path_str.contains("format") => {
82
+ () if schema_path_str.contains("format") => {
79
83
  if error_msg.contains("email") {
80
84
  Self::EmailFormat
81
85
  } else if error_msg.contains("uuid") {
@@ -104,7 +108,8 @@ impl ErrorCondition {
104
108
  }
105
109
 
106
110
  /// Get the error type code for this condition
107
- pub fn error_type(&self) -> &'static str {
111
+ #[must_use]
112
+ pub const fn error_type(&self) -> &'static str {
108
113
  match self {
109
114
  Self::StringTooShort { .. } => "string_too_short",
110
115
  Self::StringTooLong { .. } => "string_too_long",
@@ -113,23 +118,22 @@ impl ErrorCondition {
113
118
  Self::LessThan { .. } => "less_than",
114
119
  Self::LessThanEqual { .. } => "less_than_equal",
115
120
  Self::Enum { .. } => "enum",
116
- Self::StringPatternMismatch { .. } => "string_pattern_mismatch",
117
- Self::EmailFormat => "string_pattern_mismatch",
121
+ Self::StringPatternMismatch { .. } | Self::EmailFormat => "string_pattern_mismatch",
118
122
  Self::UuidFormat => "uuid_parsing",
119
123
  Self::DatetimeFormat => "datetime_parsing",
120
124
  Self::DateFormat => "date_parsing",
121
125
  Self::FormatError => "format_error",
122
126
  Self::TypeMismatch { .. } => "type_error",
123
127
  Self::Missing => "missing",
124
- Self::AdditionalProperties { .. } => "validation_error",
128
+ Self::AdditionalProperties { .. } | Self::ValidationError => "validation_error",
125
129
  Self::TooFewItems { .. } => "too_short",
126
130
  Self::TooManyItems => "too_long",
127
- Self::ValidationError => "validation_error",
128
131
  }
129
132
  }
130
133
 
131
134
  /// Get default message for this error condition
132
- pub fn default_message(&self) -> &'static str {
135
+ #[must_use]
136
+ pub const fn default_message(&self) -> &'static str {
133
137
  match self {
134
138
  Self::StringTooShort { .. } => "String is too short",
135
139
  Self::StringTooLong { .. } => "String is too long",
@@ -159,6 +163,16 @@ pub struct ErrorMapper;
159
163
 
160
164
  impl ErrorMapper {
161
165
  /// Map an error condition to its type, message, and context
166
+ ///
167
+ /// # Panics
168
+ /// Panics if accessing `.last()` on an empty vector for enum values extraction.
169
+ #[must_use]
170
+ #[allow(
171
+ clippy::too_many_lines,
172
+ clippy::option_if_let_else,
173
+ clippy::redundant_closure_for_method_calls,
174
+ clippy::uninlined_format_args
175
+ )]
162
176
  pub fn map_error(
163
177
  condition: &ErrorCondition,
164
178
  schema: &Value,
@@ -18,6 +18,9 @@ pub struct SchemaValidator {
18
18
 
19
19
  impl SchemaValidator {
20
20
  /// Create a new validator from a JSON Schema
21
+ ///
22
+ /// # Errors
23
+ /// Returns an error if the schema is invalid or compilation fails.
21
24
  pub fn new(schema: Value) -> Result<Self, String> {
22
25
  let compiled = jsonschema::options()
23
26
  .with_draft(jsonschema::Draft::Draft202012)
@@ -26,7 +29,7 @@ impl SchemaValidator {
26
29
  .build(&schema)
27
30
  .map_err(|e| {
28
31
  anyhow::anyhow!("Invalid JSON Schema")
29
- .context(format!("Schema compilation failed: {}", e))
32
+ .context(format!("Schema compilation failed: {e}"))
30
33
  .to_string()
31
34
  })?;
32
35
 
@@ -37,16 +40,17 @@ impl SchemaValidator {
37
40
  }
38
41
 
39
42
  /// Get the underlying JSON Schema
40
- pub fn schema(&self) -> &Value {
43
+ #[must_use]
44
+ pub const fn schema(&self) -> &Value {
41
45
  &self.schema
42
46
  }
43
47
 
44
- /// Pre-process data to convert file objects to strings for format: "binary" validation
48
+ /// Pre-process data to convert file objects to strings for format: `binary` validation
45
49
  ///
46
50
  /// Files uploaded via multipart are converted to objects like:
47
- /// {"filename": "...", "size": N, "content": "...", "content_type": "..."}
51
+ /// `{"filename": "...", "size": N, "content": "...", "content_type": "..."}`
48
52
  ///
49
- /// But schemas define them as: {"type": "string", "format": "binary"}
53
+ /// But schemas define them as: `{"type": "string", "format": "binary"}`
50
54
  ///
51
55
  /// This method recursively processes the data and converts file objects to their content strings
52
56
  /// so that validation passes, while preserving the original structure for handlers to use.
@@ -54,7 +58,7 @@ impl SchemaValidator {
54
58
  self.preprocess_value_with_schema(data, &self.schema)
55
59
  }
56
60
 
57
- #[allow(clippy::only_used_in_recursion)]
61
+ #[allow(clippy::only_used_in_recursion, clippy::self_only_used_in_recursion)]
58
62
  fn preprocess_value_with_schema(&self, data: &Value, schema: &Value) -> Value {
59
63
  if let Some(schema_obj) = schema.as_object() {
60
64
  let is_string_type = schema_obj.get("type").and_then(|t| t.as_str()) == Some("string");
@@ -110,6 +114,13 @@ impl SchemaValidator {
110
114
  }
111
115
 
112
116
  /// Validate JSON data against the schema
117
+ ///
118
+ /// # Errors
119
+ /// Returns a `ValidationError` if the data does not conform to the schema.
120
+ ///
121
+ /// # Too Many Lines
122
+ /// This function is complex due to error mapping logic.
123
+ #[allow(clippy::option_if_let_else, clippy::uninlined_format_args, clippy::too_many_lines)]
113
124
  pub fn validate(&self, data: &Value) -> Result<(), ValidationError> {
114
125
  let processed_data = self.preprocess_binary_fields(data);
115
126
 
@@ -131,41 +142,39 @@ impl SchemaValidator {
131
142
  if let Some(end) = error_msg[start + 1..].find('"') {
132
143
  error_msg[start + 1..start + 1 + end].to_string()
133
144
  } else {
134
- "".to_string()
145
+ String::new()
135
146
  }
136
147
  } else {
137
- "".to_string()
148
+ String::new()
138
149
  };
139
150
 
140
- if !instance_path.is_empty() && instance_path.starts_with('/') && instance_path.len() > 1 {
151
+ if instance_path.starts_with('/') && instance_path.len() > 1 {
141
152
  let base_path = &instance_path[1..];
142
- if !field_name.is_empty() {
143
- format!("{}/{}", base_path, field_name)
144
- } else {
153
+ if field_name.is_empty() {
145
154
  base_path.to_string()
155
+ } else {
156
+ format!("{base_path}/{field_name}")
146
157
  }
147
- } else if !field_name.is_empty() {
148
- field_name
149
- } else {
158
+ } else if field_name.is_empty() {
150
159
  "body".to_string()
160
+ } else {
161
+ field_name
151
162
  }
152
163
  } else if schema_path_str.contains("/additionalProperties") {
153
164
  if let Some(start) = error_msg.find('(') {
154
165
  if let Some(quote_start) = error_msg[start..].find('\'') {
155
166
  let abs_start = start + quote_start + 1;
156
- if let Some(quote_end) = error_msg[abs_start..].find('\'') {
157
- let property_name = error_msg[abs_start..abs_start + quote_end].to_string();
158
- if !instance_path.is_empty()
159
- && instance_path.starts_with('/')
160
- && instance_path.len() > 1
161
- {
162
- format!("{}/{}", &instance_path[1..], property_name)
163
- } else {
164
- property_name
165
- }
166
- } else {
167
- instance_path[1..].to_string()
168
- }
167
+ error_msg[abs_start..].find('\'').map_or_else(
168
+ || instance_path[1..].to_string(),
169
+ |quote_end| {
170
+ let property_name = error_msg[abs_start..abs_start + quote_end].to_string();
171
+ if instance_path.starts_with('/') && instance_path.len() > 1 {
172
+ format!("{}/{property_name}", &instance_path[1..])
173
+ } else {
174
+ property_name
175
+ }
176
+ },
177
+ )
169
178
  } else {
170
179
  instance_path[1..].to_string()
171
180
  }
@@ -184,7 +193,7 @@ impl SchemaValidator {
184
193
 
185
194
  let loc_parts: Vec<String> = if param_name.contains('/') {
186
195
  let mut parts = vec!["body".to_string()];
187
- parts.extend(param_name.split('/').map(|s| s.to_string()));
196
+ parts.extend(param_name.split('/').map(ToString::to_string));
188
197
  parts
189
198
  } else if param_name == "body" {
190
199
  vec!["body".to_string()]
@@ -201,7 +210,7 @@ impl SchemaValidator {
201
210
  let schema_prop_path = if param_name.contains('/') {
202
211
  format!("/properties/{}", param_name.replace('/', "/properties/"))
203
212
  } else {
204
- format!("/properties/{}", param_name)
213
+ format!("/properties/{param_name}")
205
214
  };
206
215
 
207
216
  let mut error_condition = ErrorCondition::from_schema_error(schema_path_str, &error_msg);
@@ -210,13 +219,14 @@ impl SchemaValidator {
210
219
  ErrorCondition::TypeMismatch { .. } => {
211
220
  let expected_type = self
212
221
  .schema
213
- .pointer(&format!("{}/type", schema_prop_path))
222
+ .pointer(&format!("{schema_prop_path}/type"))
214
223
  .and_then(|v| v.as_str())
215
224
  .unwrap_or("unknown")
216
225
  .to_string();
217
226
  ErrorCondition::TypeMismatch { expected_type }
218
227
  }
219
228
  ErrorCondition::AdditionalProperties { .. } => {
229
+ #[allow(clippy::redundant_clone)]
220
230
  let unexpected_field = if param_name.contains('/') {
221
231
  param_name.split('/').next_back().unwrap_or(&param_name).to_string()
222
232
  } else {
@@ -268,12 +278,15 @@ impl SchemaValidator {
268
278
  }
269
279
 
270
280
  /// Validate and parse JSON bytes
281
+ ///
282
+ /// # Errors
283
+ /// Returns a validation error if the JSON is invalid or fails validation against the schema.
271
284
  pub fn validate_json(&self, json_bytes: &[u8]) -> Result<Value, ValidationError> {
272
285
  let value: Value = serde_json::from_slice(json_bytes).map_err(|e| ValidationError {
273
286
  errors: vec![ValidationErrorDetail {
274
287
  error_type: "json_parse_error".to_string(),
275
288
  loc: vec!["body".to_string()],
276
- msg: format!("Invalid JSON: {}", e),
289
+ msg: format!("Invalid JSON: {e}"),
277
290
  input: Value::Null,
278
291
  ctx: None,
279
292
  }],
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-http"
3
- version = "0.8.1"
3
+ version = "0.8.3"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
@@ -55,6 +55,13 @@ prost = "0.14"
55
55
  prost-types = "0.14"
56
56
  h2 = "0.4"
57
57
 
58
+ [lints.rust]
59
+ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
60
+
61
+ [lints.clippy]
62
+ all = { level = "deny", priority = 0 }
63
+ pedantic = { level = "deny", priority = 0 }
64
+ nursery = { level = "deny", priority = 0 }
58
65
 
59
66
  [features]
60
67
  default = []
@@ -48,7 +48,7 @@ pub mod streaming;
48
48
 
49
49
  // Re-export main types
50
50
  pub use handler::{GrpcHandler, GrpcHandlerResult, GrpcRequestData, GrpcResponseData};
51
- pub use service::{copy_metadata, GenericGrpcService, is_grpc_request, parse_grpc_path};
51
+ pub use service::{GenericGrpcService, copy_metadata, is_grpc_request, parse_grpc_path};
52
52
  pub use streaming::{MessageStream, StreamingRequest, StreamingResponse};
53
53
 
54
54
  use serde::{Deserialize, Serialize};
@@ -200,7 +200,9 @@ mod tests {
200
200
  let service = GenericGrpcService::new(handler);
201
201
 
202
202
  let request = Request::new(Bytes::from("test payload"));
203
- let result = service.handle_unary("test.TestService".to_string(), "TestMethod".to_string(), request).await;
203
+ let result = service
204
+ .handle_unary("test.TestService".to_string(), "TestMethod".to_string(), request)
205
+ .await;
204
206
 
205
207
  assert!(result.is_ok());
206
208
  let response = result.unwrap();
@@ -217,7 +219,9 @@ mod tests {
217
219
  .metadata_mut()
218
220
  .insert("custom-header", "custom-value".parse().unwrap());
219
221
 
220
- let result = service.handle_unary("test.TestService".to_string(), "TestMethod".to_string(), request).await;
222
+ let result = service
223
+ .handle_unary("test.TestService".to_string(), "TestMethod".to_string(), request)
224
+ .await;
221
225
 
222
226
  assert!(result.is_ok());
223
227
  }
@@ -266,10 +270,7 @@ mod tests {
266
270
  #[test]
267
271
  fn test_is_grpc_request_valid() {
268
272
  let mut headers = axum::http::HeaderMap::new();
269
- headers.insert(
270
- axum::http::header::CONTENT_TYPE,
271
- "application/grpc".parse().unwrap(),
272
- );
273
+ headers.insert(axum::http::header::CONTENT_TYPE, "application/grpc".parse().unwrap());
273
274
  assert!(is_grpc_request(&headers));
274
275
  }
275
276
 
@@ -286,10 +287,7 @@ mod tests {
286
287
  #[test]
287
288
  fn test_is_grpc_request_not_grpc() {
288
289
  let mut headers = axum::http::HeaderMap::new();
289
- headers.insert(
290
- axum::http::header::CONTENT_TYPE,
291
- "application/json".parse().unwrap(),
292
- );
290
+ headers.insert(axum::http::header::CONTENT_TYPE, "application/json".parse().unwrap());
293
291
  assert!(!is_grpc_request(&headers));
294
292
  }
295
293
 
@@ -382,7 +380,9 @@ mod tests {
382
380
  let service = GenericGrpcService::new(handler);
383
381
 
384
382
  let request = Request::new(Bytes::new());
385
- let result = service.handle_unary("test.ErrorService".to_string(), "ErrorMethod".to_string(), request).await;
383
+ let result = service
384
+ .handle_unary("test.ErrorService".to_string(), "ErrorMethod".to_string(), request)
385
+ .await;
386
386
 
387
387
  assert!(result.is_err());
388
388
  let status = result.unwrap_err();
@@ -193,7 +193,11 @@ mod tests {
193
193
 
194
194
  #[tokio::test]
195
195
  async fn test_from_tonic_stream() {
196
- let messages = vec![Ok(Bytes::from("a")), Ok(Bytes::from("b")), Err(Status::cancelled("done"))];
196
+ let messages = vec![
197
+ Ok(Bytes::from("a")),
198
+ Ok(Bytes::from("b")),
199
+ Err(Status::cancelled("done")),
200
+ ];
197
201
 
198
202
  let tonic_stream = futures_util::stream::iter(messages);
199
203
  let mut stream = from_tonic_stream(tonic_stream);
@@ -69,10 +69,7 @@ pub async fn route_grpc_request(
69
69
  let handler = match registry.get(&service_name) {
70
70
  Some(h) => h,
71
71
  None => {
72
- return Err((
73
- StatusCode::NOT_FOUND,
74
- format!("Service not found: {}", service_name),
75
- ));
72
+ return Err((StatusCode::NOT_FOUND, format!("Service not found: {}", service_name)));
76
73
  }
77
74
  };
78
75
 
@@ -94,7 +91,10 @@ pub async fn route_grpc_request(
94
91
  // Try to parse as ASCII metadata
95
92
  if let Ok(metadata_value) = value_str.parse::<tonic::metadata::MetadataValue<tonic::metadata::Ascii>>() {
96
93
  // Use key.as_str() directly instead of creating String
97
- if let Ok(metadata_key) = key.as_str().parse::<tonic::metadata::MetadataKey<tonic::metadata::Ascii>>() {
94
+ if let Ok(metadata_key) = key
95
+ .as_str()
96
+ .parse::<tonic::metadata::MetadataKey<tonic::metadata::Ascii>>()
97
+ {
98
98
  tonic_request.metadata_mut().insert(metadata_key, metadata_value);
99
99
  }
100
100
  }
@@ -132,9 +132,12 @@ pub async fn route_grpc_request(
132
132
  response = response.header("grpc-status", "0");
133
133
 
134
134
  // Convert bytes::Bytes to Body
135
- let response = response
136
- .body(Body::from(payload))
137
- .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to build response: {}", e)))?;
135
+ let response = response.body(Body::from(payload)).map_err(|e| {
136
+ (
137
+ StatusCode::INTERNAL_SERVER_ERROR,
138
+ format!("Failed to build response: {}", e),
139
+ )
140
+ })?;
138
141
 
139
142
  Ok(response)
140
143
  }
@@ -268,21 +271,57 @@ mod tests {
268
271
  fn test_grpc_status_to_http_mappings() {
269
272
  // Test all gRPC status codes map correctly
270
273
  assert_eq!(grpc_status_to_http(tonic::Code::Ok), StatusCode::OK);
271
- assert_eq!(grpc_status_to_http(tonic::Code::Cancelled), StatusCode::from_u16(499).unwrap());
272
- assert_eq!(grpc_status_to_http(tonic::Code::Unknown), StatusCode::INTERNAL_SERVER_ERROR);
273
- assert_eq!(grpc_status_to_http(tonic::Code::InvalidArgument), StatusCode::BAD_REQUEST);
274
- assert_eq!(grpc_status_to_http(tonic::Code::DeadlineExceeded), StatusCode::GATEWAY_TIMEOUT);
274
+ assert_eq!(
275
+ grpc_status_to_http(tonic::Code::Cancelled),
276
+ StatusCode::from_u16(499).unwrap()
277
+ );
278
+ assert_eq!(
279
+ grpc_status_to_http(tonic::Code::Unknown),
280
+ StatusCode::INTERNAL_SERVER_ERROR
281
+ );
282
+ assert_eq!(
283
+ grpc_status_to_http(tonic::Code::InvalidArgument),
284
+ StatusCode::BAD_REQUEST
285
+ );
286
+ assert_eq!(
287
+ grpc_status_to_http(tonic::Code::DeadlineExceeded),
288
+ StatusCode::GATEWAY_TIMEOUT
289
+ );
275
290
  assert_eq!(grpc_status_to_http(tonic::Code::NotFound), StatusCode::NOT_FOUND);
276
291
  assert_eq!(grpc_status_to_http(tonic::Code::AlreadyExists), StatusCode::CONFLICT);
277
- assert_eq!(grpc_status_to_http(tonic::Code::PermissionDenied), StatusCode::FORBIDDEN);
278
- assert_eq!(grpc_status_to_http(tonic::Code::ResourceExhausted), StatusCode::TOO_MANY_REQUESTS);
279
- assert_eq!(grpc_status_to_http(tonic::Code::FailedPrecondition), StatusCode::BAD_REQUEST);
292
+ assert_eq!(
293
+ grpc_status_to_http(tonic::Code::PermissionDenied),
294
+ StatusCode::FORBIDDEN
295
+ );
296
+ assert_eq!(
297
+ grpc_status_to_http(tonic::Code::ResourceExhausted),
298
+ StatusCode::TOO_MANY_REQUESTS
299
+ );
300
+ assert_eq!(
301
+ grpc_status_to_http(tonic::Code::FailedPrecondition),
302
+ StatusCode::BAD_REQUEST
303
+ );
280
304
  assert_eq!(grpc_status_to_http(tonic::Code::Aborted), StatusCode::CONFLICT);
281
305
  assert_eq!(grpc_status_to_http(tonic::Code::OutOfRange), StatusCode::BAD_REQUEST);
282
- assert_eq!(grpc_status_to_http(tonic::Code::Unimplemented), StatusCode::NOT_IMPLEMENTED);
283
- assert_eq!(grpc_status_to_http(tonic::Code::Internal), StatusCode::INTERNAL_SERVER_ERROR);
284
- assert_eq!(grpc_status_to_http(tonic::Code::Unavailable), StatusCode::SERVICE_UNAVAILABLE);
285
- assert_eq!(grpc_status_to_http(tonic::Code::DataLoss), StatusCode::INTERNAL_SERVER_ERROR);
286
- assert_eq!(grpc_status_to_http(tonic::Code::Unauthenticated), StatusCode::UNAUTHORIZED);
306
+ assert_eq!(
307
+ grpc_status_to_http(tonic::Code::Unimplemented),
308
+ StatusCode::NOT_IMPLEMENTED
309
+ );
310
+ assert_eq!(
311
+ grpc_status_to_http(tonic::Code::Internal),
312
+ StatusCode::INTERNAL_SERVER_ERROR
313
+ );
314
+ assert_eq!(
315
+ grpc_status_to_http(tonic::Code::Unavailable),
316
+ StatusCode::SERVICE_UNAVAILABLE
317
+ );
318
+ assert_eq!(
319
+ grpc_status_to_http(tonic::Code::DataLoss),
320
+ StatusCode::INTERNAL_SERVER_ERROR
321
+ );
322
+ assert_eq!(
323
+ grpc_status_to_http(tonic::Code::Unauthenticated),
324
+ StatusCode::UNAUTHORIZED
325
+ );
287
326
  }
288
327
  }