spikard 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/spikard_rb/Cargo.lock +583 -201
- data/ext/spikard_rb/Cargo.toml +1 -1
- data/lib/spikard/grpc.rb +182 -0
- data/lib/spikard/version.rb +1 -1
- data/lib/spikard.rb +1 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +2 -1
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +197 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +2 -0
- data/vendor/crates/spikard-core/Cargo.toml +1 -1
- data/vendor/crates/spikard-http/Cargo.toml +5 -1
- data/vendor/crates/spikard-http/src/grpc/handler.rs +260 -0
- data/vendor/crates/spikard-http/src/grpc/mod.rs +342 -0
- data/vendor/crates/spikard-http/src/grpc/service.rs +392 -0
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +237 -0
- data/vendor/crates/spikard-http/src/lib.rs +14 -0
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +288 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +1 -0
- data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +1023 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +8 -0
- data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +653 -0
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +332 -0
- data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +518 -0
- data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +476 -0
- data/vendor/crates/spikard-rb/Cargo.toml +2 -1
- data/vendor/crates/spikard-rb/src/config/server_config.rs +1 -0
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +352 -0
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +9 -0
- data/vendor/crates/spikard-rb/src/lib.rs +4 -0
- data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
- metadata +15 -1
data/ext/spikard_rb/Cargo.toml
CHANGED
data/lib/spikard/grpc.rb
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spikard
|
|
4
|
+
# gRPC support for Spikard
|
|
5
|
+
#
|
|
6
|
+
# This module provides Ruby bindings for handling gRPC requests through
|
|
7
|
+
# Spikard's Rust-based gRPC runtime. Handlers receive protobuf messages
|
|
8
|
+
# as binary strings and use the google-protobuf gem for serialization.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic gRPC handler
|
|
11
|
+
# require 'spikard/grpc'
|
|
12
|
+
# require 'user_pb' # Generated protobuf
|
|
13
|
+
#
|
|
14
|
+
# class UserServiceHandler < Spikard::Grpc::Handler
|
|
15
|
+
# def handle_request(request)
|
|
16
|
+
# case request.method_name
|
|
17
|
+
# when 'GetUser'
|
|
18
|
+
# # Deserialize request
|
|
19
|
+
# req = Example::GetUserRequest.decode(request.payload)
|
|
20
|
+
#
|
|
21
|
+
# # Process request
|
|
22
|
+
# user = Example::User.new(id: req.id, name: 'John Doe')
|
|
23
|
+
#
|
|
24
|
+
# # Serialize response
|
|
25
|
+
# Spikard::Grpc::Response.new(payload: Example::User.encode(user))
|
|
26
|
+
# else
|
|
27
|
+
# raise "Unknown method: #{request.method_name}"
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
module Grpc
|
|
32
|
+
# gRPC request object
|
|
33
|
+
#
|
|
34
|
+
# Represents an incoming gRPC request with service/method information
|
|
35
|
+
# and a binary protobuf payload.
|
|
36
|
+
#
|
|
37
|
+
# @!attribute [r] service_name
|
|
38
|
+
# @return [String] Fully qualified service name (e.g., "mypackage.MyService")
|
|
39
|
+
# @!attribute [r] method_name
|
|
40
|
+
# @return [String] Method name (e.g., "GetUser")
|
|
41
|
+
# @!attribute [r] payload
|
|
42
|
+
# @return [String] Binary string containing serialized protobuf message
|
|
43
|
+
# @!attribute [r] metadata
|
|
44
|
+
# @return [Hash<String, String>] gRPC metadata (headers)
|
|
45
|
+
class Request
|
|
46
|
+
# These methods are implemented in Rust via Magnus FFI
|
|
47
|
+
# See: crates/spikard-rb/src/grpc/handler.rs
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# gRPC response object
|
|
51
|
+
#
|
|
52
|
+
# Used to return gRPC responses from handlers. The payload should be
|
|
53
|
+
# a binary string containing a serialized protobuf message.
|
|
54
|
+
#
|
|
55
|
+
# @example Creating a response
|
|
56
|
+
# user = Example::User.new(id: 1, name: 'Alice')
|
|
57
|
+
# response = Spikard::Grpc::Response.new(payload: Example::User.encode(user))
|
|
58
|
+
#
|
|
59
|
+
# @example Adding metadata
|
|
60
|
+
# response = Spikard::Grpc::Response.new(payload: encoded_message)
|
|
61
|
+
# response.metadata = { 'x-custom-header' => 'value' }
|
|
62
|
+
class Response
|
|
63
|
+
# @!attribute [w] metadata
|
|
64
|
+
# @return [Hash<String, String>] gRPC metadata to include in response
|
|
65
|
+
|
|
66
|
+
# Create a new gRPC response
|
|
67
|
+
#
|
|
68
|
+
# @param payload [String] Binary string containing serialized protobuf message
|
|
69
|
+
# @raise [ArgumentError] if payload is not a String
|
|
70
|
+
#
|
|
71
|
+
# Note: Implementation in Rust (Magnus FFI)
|
|
72
|
+
# See: crates/spikard-rb/src/grpc/handler.rs
|
|
73
|
+
|
|
74
|
+
# Create an error response
|
|
75
|
+
#
|
|
76
|
+
# @param message [String] Error message
|
|
77
|
+
# @param metadata [Hash<String, String>] Optional gRPC metadata
|
|
78
|
+
# @return [Response] A response with error status
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# response = Spikard::Grpc::Response.error('Method not implemented')
|
|
82
|
+
def self.error(message, metadata = {})
|
|
83
|
+
error_metadata = metadata.merge(
|
|
84
|
+
'grpc-status' => 'INTERNAL',
|
|
85
|
+
'grpc-message' => message
|
|
86
|
+
)
|
|
87
|
+
response = new(payload: '')
|
|
88
|
+
response.metadata = error_metadata
|
|
89
|
+
response
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Base class for gRPC handlers
|
|
94
|
+
#
|
|
95
|
+
# Subclass this to implement gRPC service handlers. Override
|
|
96
|
+
# {#handle_request} to process incoming requests.
|
|
97
|
+
#
|
|
98
|
+
# @example Implementing a handler
|
|
99
|
+
# class MyServiceHandler < Spikard::Grpc::Handler
|
|
100
|
+
# def handle_request(request)
|
|
101
|
+
# case request.method_name
|
|
102
|
+
# when 'MethodOne'
|
|
103
|
+
# # Handle MethodOne
|
|
104
|
+
# req = MyPackage::MethodOneRequest.decode(request.payload)
|
|
105
|
+
# resp = MyPackage::MethodOneResponse.new(...)
|
|
106
|
+
# Spikard::Grpc::Response.new(payload: MyPackage::MethodOneResponse.encode(resp))
|
|
107
|
+
# when 'MethodTwo'
|
|
108
|
+
# # Handle MethodTwo
|
|
109
|
+
# # ...
|
|
110
|
+
# else
|
|
111
|
+
# raise "Unknown method: #{request.method_name}"
|
|
112
|
+
# end
|
|
113
|
+
# end
|
|
114
|
+
# end
|
|
115
|
+
class Handler
|
|
116
|
+
# Handle a gRPC request
|
|
117
|
+
#
|
|
118
|
+
# This method must be overridden by subclasses to implement the
|
|
119
|
+
# actual request handling logic.
|
|
120
|
+
#
|
|
121
|
+
# @param request [Spikard::Grpc::Request] The incoming gRPC request
|
|
122
|
+
# @return [Spikard::Grpc::Response] The gRPC response
|
|
123
|
+
# @raise [NotImplementedError] if not overridden by subclass
|
|
124
|
+
def handle_request(request)
|
|
125
|
+
raise NotImplementedError, "#{self.class}#handle_request must be implemented"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Service registry for gRPC handlers
|
|
130
|
+
#
|
|
131
|
+
# Manages registration and lookup of gRPC service handlers.
|
|
132
|
+
# Handlers are registered by service name and method.
|
|
133
|
+
#
|
|
134
|
+
# @example Registering a handler
|
|
135
|
+
# service = Spikard::Grpc::Service.new
|
|
136
|
+
# handler = UserServiceHandler.new
|
|
137
|
+
# service.register_handler('mypackage.UserService', handler)
|
|
138
|
+
class Service
|
|
139
|
+
def initialize
|
|
140
|
+
@handlers = {}
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Register a gRPC handler for a service
|
|
144
|
+
#
|
|
145
|
+
# @param service_name [String] Fully qualified service name
|
|
146
|
+
# @param handler [Spikard::Grpc::Handler] Handler instance
|
|
147
|
+
# @raise [ArgumentError] if service_name is invalid or handler doesn't respond to handle_request
|
|
148
|
+
def register_handler(service_name, handler)
|
|
149
|
+
raise ArgumentError, 'Service name cannot be empty' if service_name.nil? || service_name.empty?
|
|
150
|
+
|
|
151
|
+
unless handler.respond_to?(:handle_request)
|
|
152
|
+
raise ArgumentError, "Handler must respond to :handle_request"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
@handlers[service_name] = handler
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Get a handler by service name
|
|
159
|
+
#
|
|
160
|
+
# @param service_name [String] Fully qualified service name
|
|
161
|
+
# @return [Spikard::Grpc::Handler, nil] The handler or nil if not found
|
|
162
|
+
def get_handler(service_name)
|
|
163
|
+
@handlers[service_name]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Get all registered service names
|
|
167
|
+
#
|
|
168
|
+
# @return [Array<String>] List of registered service names
|
|
169
|
+
def service_names
|
|
170
|
+
@handlers.keys
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Check if a service is registered
|
|
174
|
+
#
|
|
175
|
+
# @param service_name [String] Fully qualified service name
|
|
176
|
+
# @return [Boolean] true if the service is registered
|
|
177
|
+
def registered?(service_name)
|
|
178
|
+
@handlers.key?(service_name)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
data/lib/spikard/version.rb
CHANGED
data/lib/spikard.rb
CHANGED
|
@@ -17,6 +17,7 @@ require_relative 'spikard/background'
|
|
|
17
17
|
require_relative 'spikard/schema'
|
|
18
18
|
require_relative 'spikard/websocket'
|
|
19
19
|
require_relative 'spikard/sse'
|
|
20
|
+
require_relative 'spikard/grpc'
|
|
20
21
|
require_relative 'spikard/upload_file'
|
|
21
22
|
require_relative 'spikard/converters'
|
|
22
23
|
require_relative 'spikard/provide'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "spikard-bindings-shared"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -18,6 +18,7 @@ spikard-http = { path = "../spikard-http" }
|
|
|
18
18
|
tracing = "0.1"
|
|
19
19
|
http = "1.4"
|
|
20
20
|
http-body-util = "0.1"
|
|
21
|
+
tonic = "0.14"
|
|
21
22
|
|
|
22
23
|
[features]
|
|
23
24
|
default = []
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
//! Shared gRPC metadata utilities
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides common metadata conversion functions used across all
|
|
4
|
+
//! language bindings (Python, Node.js, Ruby, PHP) to avoid code duplication.
|
|
5
|
+
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
use tonic::metadata::{MetadataMap, MetadataKey, MetadataValue};
|
|
8
|
+
|
|
9
|
+
/// Extract metadata from gRPC MetadataMap to a simple HashMap.
|
|
10
|
+
///
|
|
11
|
+
/// This function converts gRPC metadata to a language-agnostic HashMap format
|
|
12
|
+
/// that can be easily passed to language bindings. Only ASCII metadata is
|
|
13
|
+
/// included; binary metadata is skipped with optional logging.
|
|
14
|
+
///
|
|
15
|
+
/// # Arguments
|
|
16
|
+
///
|
|
17
|
+
/// * `metadata` - The gRPC MetadataMap to extract from
|
|
18
|
+
/// * `log_binary_skip` - Whether to log when binary metadata is skipped
|
|
19
|
+
///
|
|
20
|
+
/// # Returns
|
|
21
|
+
///
|
|
22
|
+
/// A HashMap containing all ASCII metadata key-value pairs
|
|
23
|
+
///
|
|
24
|
+
/// # Examples
|
|
25
|
+
///
|
|
26
|
+
/// ```
|
|
27
|
+
/// use tonic::metadata::MetadataMap;
|
|
28
|
+
/// use spikard_bindings_shared::grpc_metadata::extract_metadata_to_hashmap;
|
|
29
|
+
///
|
|
30
|
+
/// let mut metadata = MetadataMap::new();
|
|
31
|
+
/// metadata.insert("authorization", "Bearer token123".parse().unwrap());
|
|
32
|
+
///
|
|
33
|
+
/// let map = extract_metadata_to_hashmap(&metadata, false);
|
|
34
|
+
/// assert_eq!(map.get("authorization"), Some(&"Bearer token123".to_string()));
|
|
35
|
+
/// ```
|
|
36
|
+
pub fn extract_metadata_to_hashmap(metadata: &MetadataMap, log_binary_skip: bool) -> HashMap<String, String> {
|
|
37
|
+
let mut map = HashMap::new();
|
|
38
|
+
|
|
39
|
+
for key_value in metadata.iter() {
|
|
40
|
+
match key_value {
|
|
41
|
+
tonic::metadata::KeyAndValueRef::Ascii(key, value) => {
|
|
42
|
+
let key_str = key.as_str().to_string();
|
|
43
|
+
let value_str = value.to_str().unwrap_or("").to_string();
|
|
44
|
+
map.insert(key_str, value_str);
|
|
45
|
+
}
|
|
46
|
+
tonic::metadata::KeyAndValueRef::Binary(key, _value) => {
|
|
47
|
+
// Binary metadata is skipped as we only support string values
|
|
48
|
+
if log_binary_skip {
|
|
49
|
+
tracing::debug!("Skipping binary metadata key: {}", key.as_str());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
map
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Convert a HashMap to gRPC MetadataMap.
|
|
59
|
+
///
|
|
60
|
+
/// This function converts a language-agnostic HashMap into a gRPC MetadataMap
|
|
61
|
+
/// that can be used in responses. All keys and values are validated and errors
|
|
62
|
+
/// are returned if any are invalid.
|
|
63
|
+
///
|
|
64
|
+
/// # Arguments
|
|
65
|
+
///
|
|
66
|
+
/// * `map` - The HashMap to convert
|
|
67
|
+
///
|
|
68
|
+
/// # Returns
|
|
69
|
+
///
|
|
70
|
+
/// A Result containing the MetadataMap or an error message
|
|
71
|
+
///
|
|
72
|
+
/// # Errors
|
|
73
|
+
///
|
|
74
|
+
/// Returns an error if:
|
|
75
|
+
/// - A metadata key is invalid (contains invalid characters)
|
|
76
|
+
/// - A metadata value is invalid (contains invalid characters)
|
|
77
|
+
///
|
|
78
|
+
/// # Examples
|
|
79
|
+
///
|
|
80
|
+
/// ```
|
|
81
|
+
/// use std::collections::HashMap;
|
|
82
|
+
/// use spikard_bindings_shared::grpc_metadata::hashmap_to_metadata;
|
|
83
|
+
///
|
|
84
|
+
/// let mut map = HashMap::new();
|
|
85
|
+
/// map.insert("content-type".to_string(), "application/grpc".to_string());
|
|
86
|
+
///
|
|
87
|
+
/// let metadata = hashmap_to_metadata(&map).unwrap();
|
|
88
|
+
/// assert!(metadata.contains_key("content-type"));
|
|
89
|
+
/// ```
|
|
90
|
+
pub fn hashmap_to_metadata(map: &HashMap<String, String>) -> Result<MetadataMap, String> {
|
|
91
|
+
let mut metadata = MetadataMap::new();
|
|
92
|
+
|
|
93
|
+
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
|
+
|
|
97
|
+
let metadata_value = MetadataValue::try_from(value)
|
|
98
|
+
.map_err(|err| format!("Invalid metadata value for '{}': {}", key, err))?;
|
|
99
|
+
|
|
100
|
+
metadata.insert(metadata_key, metadata_value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Ok(metadata)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[cfg(test)]
|
|
107
|
+
mod tests {
|
|
108
|
+
use super::*;
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn test_extract_empty_metadata() {
|
|
112
|
+
let metadata = MetadataMap::new();
|
|
113
|
+
let map = extract_metadata_to_hashmap(&metadata, false);
|
|
114
|
+
assert!(map.is_empty());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#[test]
|
|
118
|
+
fn test_extract_single_metadata() {
|
|
119
|
+
let mut metadata = MetadataMap::new();
|
|
120
|
+
metadata.insert("content-type", "application/grpc".parse().unwrap());
|
|
121
|
+
|
|
122
|
+
let map = extract_metadata_to_hashmap(&metadata, false);
|
|
123
|
+
assert_eq!(map.len(), 1);
|
|
124
|
+
assert_eq!(map.get("content-type"), Some(&"application/grpc".to_string()));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[test]
|
|
128
|
+
fn test_extract_multiple_metadata() {
|
|
129
|
+
let mut metadata = MetadataMap::new();
|
|
130
|
+
metadata.insert("content-type", "application/grpc".parse().unwrap());
|
|
131
|
+
metadata.insert("authorization", "Bearer token123".parse().unwrap());
|
|
132
|
+
metadata.insert("x-custom-header", "custom-value".parse().unwrap());
|
|
133
|
+
|
|
134
|
+
let map = extract_metadata_to_hashmap(&metadata, false);
|
|
135
|
+
assert_eq!(map.len(), 3);
|
|
136
|
+
assert_eq!(map.get("content-type"), Some(&"application/grpc".to_string()));
|
|
137
|
+
assert_eq!(map.get("authorization"), Some(&"Bearer token123".to_string()));
|
|
138
|
+
assert_eq!(map.get("x-custom-header"), Some(&"custom-value".to_string()));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn test_hashmap_to_metadata_empty() {
|
|
143
|
+
let map = HashMap::new();
|
|
144
|
+
let metadata = hashmap_to_metadata(&map).unwrap();
|
|
145
|
+
assert_eq!(metadata.len(), 0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn test_hashmap_to_metadata_single() {
|
|
150
|
+
let mut map = HashMap::new();
|
|
151
|
+
map.insert("content-type".to_string(), "application/grpc".to_string());
|
|
152
|
+
|
|
153
|
+
let metadata = hashmap_to_metadata(&map).unwrap();
|
|
154
|
+
assert_eq!(metadata.len(), 1);
|
|
155
|
+
assert!(metadata.contains_key("content-type"));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[test]
|
|
159
|
+
fn test_hashmap_to_metadata_multiple() {
|
|
160
|
+
let mut map = HashMap::new();
|
|
161
|
+
map.insert("content-type".to_string(), "application/grpc".to_string());
|
|
162
|
+
map.insert("authorization".to_string(), "Bearer token".to_string());
|
|
163
|
+
|
|
164
|
+
let metadata = hashmap_to_metadata(&map).unwrap();
|
|
165
|
+
assert_eq!(metadata.len(), 2);
|
|
166
|
+
assert!(metadata.contains_key("content-type"));
|
|
167
|
+
assert!(metadata.contains_key("authorization"));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[test]
|
|
171
|
+
fn test_hashmap_to_metadata_invalid_key() {
|
|
172
|
+
let mut map = HashMap::new();
|
|
173
|
+
map.insert("invalid\nkey".to_string(), "value".to_string());
|
|
174
|
+
|
|
175
|
+
let result = hashmap_to_metadata(&map);
|
|
176
|
+
assert!(result.is_err());
|
|
177
|
+
assert!(result.unwrap_err().contains("Invalid metadata key"));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn test_roundtrip_metadata() {
|
|
182
|
+
let mut original_metadata = MetadataMap::new();
|
|
183
|
+
original_metadata.insert("content-type", "application/grpc".parse().unwrap());
|
|
184
|
+
original_metadata.insert("x-custom", "value".parse().unwrap());
|
|
185
|
+
|
|
186
|
+
// Extract to HashMap
|
|
187
|
+
let map = extract_metadata_to_hashmap(&original_metadata, false);
|
|
188
|
+
|
|
189
|
+
// Convert back to MetadataMap
|
|
190
|
+
let new_metadata = hashmap_to_metadata(&map).unwrap();
|
|
191
|
+
|
|
192
|
+
// Verify both have the same entries
|
|
193
|
+
assert_eq!(new_metadata.len(), original_metadata.len());
|
|
194
|
+
assert!(new_metadata.contains_key("content-type"));
|
|
195
|
+
assert!(new_metadata.contains_key("x-custom"));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -8,6 +8,7 @@ pub mod config_extractor;
|
|
|
8
8
|
pub mod conversion_traits;
|
|
9
9
|
pub mod di_traits;
|
|
10
10
|
pub mod error_response;
|
|
11
|
+
pub mod grpc_metadata;
|
|
11
12
|
pub mod handler_base;
|
|
12
13
|
pub mod lifecycle_base;
|
|
13
14
|
pub mod lifecycle_executor;
|
|
@@ -18,6 +19,7 @@ pub mod validation_helpers;
|
|
|
18
19
|
pub use config_extractor::{ConfigExtractor, ConfigSource};
|
|
19
20
|
pub use di_traits::{FactoryDependencyAdapter, ValueDependencyAdapter};
|
|
20
21
|
pub use error_response::ErrorResponseBuilder;
|
|
22
|
+
pub use grpc_metadata::{extract_metadata_to_hashmap, hashmap_to_metadata};
|
|
21
23
|
pub use handler_base::{HandlerError, HandlerExecutor, LanguageHandler};
|
|
22
24
|
pub use lifecycle_executor::{
|
|
23
25
|
HookResultData, LanguageLifecycleHook, LifecycleExecutor, RequestModifications, extract_body,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "spikard-http"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -50,6 +50,10 @@ cookie = "0.18"
|
|
|
50
50
|
base64 = "0.22.1"
|
|
51
51
|
flate2 = { version = "=1.1.5", default-features = false, features = ["rust_backend"] }
|
|
52
52
|
brotli = "8.0"
|
|
53
|
+
tonic = { version = "0.14", features = ["transport", "codegen", "gzip"] }
|
|
54
|
+
prost = "0.14"
|
|
55
|
+
prost-types = "0.14"
|
|
56
|
+
h2 = "0.4"
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
[features]
|