spikard 0.8.1 → 0.8.2

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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/ext/spikard_rb/Cargo.toml +1 -1
  3. data/lib/spikard/grpc.rb +5 -5
  4. data/lib/spikard/version.rb +1 -1
  5. data/vendor/crates/spikard-bindings-shared/Cargo.toml +1 -1
  6. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +3 -3
  7. data/vendor/crates/spikard-core/Cargo.toml +1 -1
  8. data/vendor/crates/spikard-core/src/metadata.rs +3 -14
  9. data/vendor/crates/spikard-http/Cargo.toml +1 -1
  10. data/vendor/crates/spikard-http/src/grpc/mod.rs +1 -1
  11. data/vendor/crates/spikard-http/src/grpc/service.rs +11 -11
  12. data/vendor/crates/spikard-http/src/grpc/streaming.rs +5 -1
  13. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +59 -20
  14. data/vendor/crates/spikard-http/src/server/routing_factory.rs +179 -201
  15. data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +49 -60
  16. data/vendor/crates/spikard-http/tests/common/handlers.rs +5 -5
  17. data/vendor/crates/spikard-http/tests/common/mod.rs +7 -8
  18. data/vendor/crates/spikard-http/tests/common/test_builders.rs +14 -19
  19. data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +68 -69
  20. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +1 -3
  21. data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +98 -84
  22. data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +76 -57
  23. data/vendor/crates/spikard-rb/Cargo.toml +1 -1
  24. data/vendor/crates/spikard-rb/src/grpc/handler.rs +30 -25
  25. data/vendor/crates/spikard-rb/src/lib.rs +1 -2
  26. data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
  27. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e00c3157cc5b1a3971cf534263f4a554953b29aba028d681a4641cc6eea6e7f
4
- data.tar.gz: 78f3c47e532447c265ae0bee25cc01317000549c7b811685ba9e648229541b4b
3
+ metadata.gz: 812ea62c2af7b3d443a8485aa055c197cdb1b65b04a3cc916ffd7bd6b0dfc81a
4
+ data.tar.gz: 825b7329c8ce47f3d74ebcc97fdbfa5ebbd5cbc16294b2721362bc1bd62fc7c6
5
5
  SHA512:
6
- metadata.gz: 4e998981f0096716f4f3248773d9f06ab87c3c6fef18cb66a12e30c47893e48775d98000480361be14b6f46c098edca727c30e324a84951585a0f0b9119ee107
7
- data.tar.gz: eb400d4510cfa5a9c7d489feba501e9acbea43f09b5c412cea001c1ed710d149801b6d8caf014f8f2ed2675f2b525d6eafbb3a785729e6510b87e692ded6a11c
6
+ metadata.gz: 8c27c069e2a10c5dc6b43f721f5baf2c56f5361f3fde3362b7ee3242b0deda62d9e4f198853cbba3e3f44c5f7aa0028495623961299fa99cc9bc9518d6f726a6
7
+ data.tar.gz: 6179700d82a1aff3aa93b87ac1152be28722eda2559a8becd8946d3e462cd32acc73328154c4906b79fab39e0188f0e7d84fefaa0e2aa4cc18358bb0fec6a84e
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-rb-ext"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  edition = "2024"
5
5
  license = "MIT"
6
6
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
data/lib/spikard/grpc.rb CHANGED
@@ -42,10 +42,12 @@ module Spikard
42
42
  # @return [String] Binary string containing serialized protobuf message
43
43
  # @!attribute [r] metadata
44
44
  # @return [Hash<String, String>] gRPC metadata (headers)
45
+ # rubocop:disable Lint/EmptyClass -- Implementation in Rust via FFI
45
46
  class Request
46
- # These methods are implemented in Rust via Magnus FFI
47
- # See: crates/spikard-rb/src/grpc/handler.rs
47
+ # These methods are implemented in Rust via Magnus FFI.
48
+ # See: crates/spikard-rb/src/grpc/handler.rs for implementation details.
48
49
  end
50
+ # rubocop:enable Lint/EmptyClass
49
51
 
50
52
  # gRPC response object
51
53
  #
@@ -148,9 +150,7 @@ module Spikard
148
150
  def register_handler(service_name, handler)
149
151
  raise ArgumentError, 'Service name cannot be empty' if service_name.nil? || service_name.empty?
150
152
 
151
- unless handler.respond_to?(:handle_request)
152
- raise ArgumentError, "Handler must respond to :handle_request"
153
- end
153
+ raise ArgumentError, 'Handler must respond to :handle_request' unless handler.respond_to?(:handle_request)
154
154
 
155
155
  @handlers[service_name] = handler
156
156
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spikard
4
- VERSION = '0.8.1'
4
+ VERSION = '0.8.2'
5
5
  end
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-bindings-shared"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
@@ -4,7 +4,7 @@
4
4
  //! language bindings (Python, Node.js, Ruby, PHP) to avoid code duplication.
5
5
 
6
6
  use std::collections::HashMap;
7
- use tonic::metadata::{MetadataMap, MetadataKey, MetadataValue};
7
+ use tonic::metadata::{MetadataKey, MetadataMap, MetadataValue};
8
8
 
9
9
  /// Extract metadata from gRPC MetadataMap to a simple HashMap.
10
10
  ///
@@ -94,8 +94,8 @@ pub fn hashmap_to_metadata(map: &HashMap<String, String>) -> Result<MetadataMap,
94
94
  let metadata_key = MetadataKey::from_bytes(key.as_bytes())
95
95
  .map_err(|err| format!("Invalid metadata key '{}': {}", key, err))?;
96
96
 
97
- let metadata_value = MetadataValue::try_from(value)
98
- .map_err(|err| format!("Invalid metadata value for '{}': {}", key, err))?;
97
+ let metadata_value =
98
+ MetadataValue::try_from(value).map_err(|err| format!("Invalid metadata value for '{}': {}", key, err))?;
99
99
 
100
100
  metadata.insert(metadata_key, metadata_value);
101
101
  }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-core"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
@@ -181,11 +181,7 @@ pub fn parse_parameter_schema(schema: &Value) -> Result<Vec<ParameterMetadata>,
181
181
  let required: Vec<String> = schema
182
182
  .get("required")
183
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
- })
184
+ .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
189
185
  .unwrap_or_default();
190
186
 
191
187
  for (param_name, param_schema) in props {
@@ -197,10 +193,7 @@ pub fn parse_parameter_schema(schema: &Value) -> Result<Vec<ParameterMetadata>,
197
193
  .and_then(|s| s.parse().ok())
198
194
  .unwrap_or(ParameterSource::Query);
199
195
 
200
- let schema_type = param_schema
201
- .get("type")
202
- .and_then(|t| t.as_str())
203
- .map(String::from);
196
+ let schema_type = param_schema.get("type").and_then(|t| t.as_str()).map(String::from);
204
197
 
205
198
  params.push(ParameterMetadata {
206
199
  name: param_name.clone(),
@@ -242,11 +235,7 @@ pub fn validate_metadata(metadata: &ExtractedRouteMetadata) -> Result<(), Vec<St
242
235
  }
243
236
  }
244
237
 
245
- if errors.is_empty() {
246
- Ok(())
247
- } else {
248
- Err(errors)
249
- }
238
+ if errors.is_empty() { Ok(()) } else { Err(errors) }
250
239
  }
251
240
 
252
241
  /// Merge path parameters with parameter schema
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-http"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
@@ -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
  }