From a93aeaf3f2d30e5ad282c72398ff7dde9b7a7d00 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 2 Aug 2026 23:36:19 +0200 Subject: [PATCH 1/2] feat: imlemented body validation --- src/main.rs | 1 + .../flow_service_client_impl/mod.rs | 3 +- .../module_configuration_client_impl.rs | 4 +- .../flow_id_cache.rs | 4 +- .../test_execution_client_impl/mod.rs | 44 ++- .../response_sender.rs | 6 +- src/server/action_transfer/logon.rs | 10 +- src/server/action_transfer/mod.rs | 6 +- src/server/action_transfer/nats_bridge.rs | 4 +- src/server/dynamic_server.rs | 4 +- .../monitor.rs | 6 +- .../registry.rs | 7 +- src/server/static_server.rs | 5 +- src/startup/dynamic_mode.rs | 5 +- src/validation.rs | 324 ++++++++++++++++++ 15 files changed, 410 insertions(+), 23 deletions(-) create mode 100644 src/validation.rs diff --git a/src/main.rs b/src/main.rs index 817aa0c..ba2fbc3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ pub mod sagittarius; pub mod server; pub mod startup; pub mod telemetry; +pub mod validation; pub mod version; const CONFIG_PATH_ENV: &str = "AQUILA_CONFIG_PATH"; diff --git a/src/sagittarius/flow_service_client_impl/mod.rs b/src/sagittarius/flow_service_client_impl/mod.rs index 8e3997e..57738a8 100644 --- a/src/sagittarius/flow_service_client_impl/mod.rs +++ b/src/sagittarius/flow_service_client_impl/mod.rs @@ -128,7 +128,8 @@ impl SagittariusFlowClient { dev_export::overwrite(&self.flow_export_path, flows.clone()).await; } - let (purged_count, stored_count) = flow_store::replace_all(&self.store, flows).await; + let (purged_count, stored_count) = + flow_store::replace_all(&self.store, flows).await; log::info!( "Finished replacing stored flows received_count={} purged_count={} stored_count={}", received_count, diff --git a/src/sagittarius/module_configuration_client_impl.rs b/src/sagittarius/module_configuration_client_impl.rs index 4b9b59e..23beaef 100644 --- a/src/sagittarius/module_configuration_client_impl.rs +++ b/src/sagittarius/module_configuration_client_impl.rs @@ -109,6 +109,8 @@ impl SagittariusModuleConfigurationClient { } log::warn!("Sagittarius closed the module configuration stream; reconnecting"); - Err(tonic::Status::unavailable("module configuration stream ended")) + Err(tonic::Status::unavailable( + "module configuration stream ended", + )) } } diff --git a/src/sagittarius/test_execution_client_impl/flow_id_cache.rs b/src/sagittarius/test_execution_client_impl/flow_id_cache.rs index 0fc28a1..58c019f 100644 --- a/src/sagittarius/test_execution_client_impl/flow_id_cache.rs +++ b/src/sagittarius/test_execution_client_impl/flow_id_cache.rs @@ -123,7 +123,9 @@ fn prune_expired(entries: &mut HashMap, now: Ins /// Evicts whichever entry is closest to expiring, since that's the best /// approximation of "oldest" without tracking insertion order separately. -fn remove_soonest_to_expire(entries: &mut HashMap) -> Option { +fn remove_soonest_to_expire( + entries: &mut HashMap, +) -> Option { let soonest_execution_id = entries .iter() .min_by_key(|(_, mapping)| mapping.expires_at) diff --git a/src/sagittarius/test_execution_client_impl/mod.rs b/src/sagittarius/test_execution_client_impl/mod.rs index e2175c1..bd88a76 100644 --- a/src/sagittarius/test_execution_client_impl/mod.rs +++ b/src/sagittarius/test_execution_client_impl/mod.rs @@ -28,7 +28,9 @@ use tucana::sagittarius_gateway::execution_service_client::ExecutionServiceClien use tucana::sagittarius_gateway::{ExecutionLogonRequest, Logon}; use tucana::shared::{ExecutionFlow, ValidationFlow}; -use crate::{authorization::authorization::get_authentication_metadata, flow::key_has_flow_id}; +use crate::{ + authorization::authorization::get_authentication_metadata, flow::key_has_flow_id, validation, +}; pub struct SagittariusTestExecutionServiceClient { nats_client: async_nats::Client, @@ -205,8 +207,44 @@ impl SagittariusTestExecutionServiceClient { } }; - // TODO: When the new validator is ready, the body needs to be validated at this - // point. + if validation::is_rest_flow(&validation_flow) { + let input_schema = validation::extract_input_schema(&validation_flow); + if let Err(err) = validation::validate_body_against_schema( + input_schema, + request.body.as_ref(), + ) { + log::warn!( + "Rejecting Sagittarius execution request due to input schema validation failure requested_execution_id={} flow_id={} error={}", + request.execution_identifier, + request.flow_id, + err + ); + + let execution_id = if request.execution_identifier.is_empty() { + uuid::Uuid::new_v4().to_string() + } else { + request.execution_identifier.clone() + }; + + let rejection = validation::rejection_result( + execution_id, + request.flow_id, + &err, + ); + + if let Err(status) = + self.response_sender.send_execution_result(rejection).await + { + log::error!( + "Failed to send input schema validation rejection result flow_id={} error={:?}", + request.flow_id, + status + ); + } + + continue; + } + } let execution_id = if request.execution_identifier.is_empty() { uuid::Uuid::new_v4().to_string() diff --git a/src/sagittarius/test_execution_client_impl/response_sender.rs b/src/sagittarius/test_execution_client_impl/response_sender.rs index b05de7d..ebd2a54 100644 --- a/src/sagittarius/test_execution_client_impl/response_sender.rs +++ b/src/sagittarius/test_execution_client_impl/response_sender.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use tokio::sync::Mutex; use tonic::Status; -use tucana::sagittarius_gateway::execution_logon_request::Data; use tucana::sagittarius_gateway::ExecutionLogonRequest; +use tucana::sagittarius_gateway::execution_logon_request::Data; use tucana::shared::ExecutionResult; use super::flow_id_cache::ExecutionFlowIdCache; @@ -50,7 +50,9 @@ impl SagittariusExecutionResponseSender { } pub(super) async fn remember_execution_flow(&self, execution_id: &str, flow_id: i64) { - self.execution_flow_ids.remember(execution_id, flow_id).await; + self.execution_flow_ids + .remember(execution_id, flow_id) + .await; } pub(super) async fn forget_execution_flow(&self, execution_id: &str) { diff --git a/src/server/action_transfer/logon.rs b/src/server/action_transfer/logon.rs index 2335969..da2a1ce 100644 --- a/src/server/action_transfer/logon.rs +++ b/src/server/action_transfer/logon.rs @@ -46,7 +46,10 @@ pub(super) fn extract_token( /// Whether a broadcasted config update is meant for `action_identifier`, since /// [`spawn_cfg_forwarder`] subscribes to a single broadcast channel shared by /// every connected action. -fn applies_to_action(configs: &tucana::shared::ModuleConfigurations, action_identifier: &str) -> bool { +fn applies_to_action( + configs: &tucana::shared::ModuleConfigurations, + action_identifier: &str, +) -> bool { configs.module_identifier == action_identifier } @@ -54,7 +57,10 @@ fn applies_to_action(configs: &tucana::shared::ModuleConfigurations, action_iden /// logs on with, so downstream consumers can tell it came from this action /// rather than from whatever source the action's module definition was /// authored against. -fn overwrite_module_definition_sources(module: &mut tucana::shared::Module, action_identifier: &str) { +fn overwrite_module_definition_sources( + module: &mut tucana::shared::Module, + action_identifier: &str, +) { let source = format!("action.{}", action_identifier); for flow_type in &mut module.flow_types { diff --git a/src/server/action_transfer/mod.rs b/src/server/action_transfer/mod.rs index d09af65..209ed32 100644 --- a/src/server/action_transfer/mod.rs +++ b/src/server/action_transfer/mod.rs @@ -21,13 +21,13 @@ use tokio_stream::wrappers::ReceiverStream; use tonic::Status; use tracing::Instrument; use tucana::aquila::{ - ActionTransferRequest, ActionTransferResponse, action_transfer_service_server::ActionTransferService, + ActionTransferRequest, ActionTransferResponse, + action_transfer_service_server::ActionTransferService, }; use crate::{ configuration::service::ServiceConfiguration, - sagittarius::module_service_client_impl::SagittariusModuleServiceClient, - telemetry::metrics, + sagittarius::module_service_client_impl::SagittariusModuleServiceClient, telemetry::metrics, }; use logon::{extract_token, handle_logon}; diff --git a/src/server/action_transfer/nats_bridge.rs b/src/server/action_transfer/nats_bridge.rs index 2e26361..e4bd090 100644 --- a/src/server/action_transfer/nats_bridge.rs +++ b/src/server/action_transfer/nats_bridge.rs @@ -6,7 +6,9 @@ use async_nats::{Subject, Subscriber}; use futures::StreamExt; use prost::Message; use tucana::{ - aquila::{ActionEvent, ActionExecutionRequest, ActionExecutionResponse, ActionTransferResponse}, + aquila::{ + ActionEvent, ActionExecutionRequest, ActionExecutionResponse, ActionTransferResponse, + }, shared::{ExecutionFlow, Flows, ValidationFlow, Value}, }; diff --git a/src/server/dynamic_server.rs b/src/server/dynamic_server.rs index 44bcd8b..abd9f03 100644 --- a/src/server/dynamic_server.rs +++ b/src/server/dynamic_server.rs @@ -10,8 +10,8 @@ use crate::{ test_execution_client_impl::SagittariusExecutionResponseSender, }, server::{ - action_transfer::AquilaActionTransferServiceServer, - create_readiness_interceptor, module_service_server_impl::AquilaModuleServiceServer, + action_transfer::AquilaActionTransferServiceServer, create_readiness_interceptor, + module_service_server_impl::AquilaModuleServiceServer, runtime_execution_service_server_impl::AquilaExecutionServiceServer, runtime_status_service_server_impl::AquilaRuntimeStatusServiceServer, }, diff --git a/src/server/runtime_status_service_server_impl/monitor.rs b/src/server/runtime_status_service_server_impl/monitor.rs index ab8fe95..40c9bf1 100644 --- a/src/server/runtime_status_service_server_impl/monitor.rs +++ b/src/server/runtime_status_service_server_impl/monitor.rs @@ -33,7 +33,11 @@ pub(super) fn spawn( interval.tick().await; let timeout_updates = registry - .collect_timeout_updates(Instant::now(), not_responding_after, stopped_after_not_responding) + .collect_timeout_updates( + Instant::now(), + not_responding_after, + stopped_after_not_responding, + ) .await; if timeout_updates.is_empty() { diff --git a/src/server/runtime_status_service_server_impl/registry.rs b/src/server/runtime_status_service_server_impl/registry.rs index 54a63ed..ae1b8fb 100644 --- a/src/server/runtime_status_service_server_impl/registry.rs +++ b/src/server/runtime_status_service_server_impl/registry.rs @@ -133,7 +133,12 @@ impl TrackedRuntimeRegistry { stopped_after_not_responding: Duration, ) -> Vec { let mut tracked = self.tracked.lock().await; - collect_timeout_updates(&mut tracked, now, not_responding_after, stopped_after_not_responding) + collect_timeout_updates( + &mut tracked, + now, + not_responding_after, + stopped_after_not_responding, + ) } } diff --git a/src/server/static_server.rs b/src/server/static_server.rs index 56a88e1..861ecd0 100644 --- a/src/server/static_server.rs +++ b/src/server/static_server.rs @@ -4,10 +4,7 @@ use crate::{ configuration::{config::Config, service::ServiceConfiguration, state::AppReadiness}, - server::{ - action_transfer::AquilaActionTransferServiceServer, - create_readiness_interceptor, - }, + server::{action_transfer::AquilaActionTransferServiceServer, create_readiness_interceptor}, }; use async_nats::jetstream::kv::Store; use log::info; diff --git a/src/startup/dynamic_mode.rs b/src/startup/dynamic_mode.rs index 474fb70..2ed650f 100644 --- a/src/startup/dynamic_mode.rs +++ b/src/startup/dynamic_mode.rs @@ -203,7 +203,10 @@ pub async fn run( action_config_tx_for_module_configuration.clone(), ); - match module_configuration_client.init_configuration_stream().await { + match module_configuration_client + .init_configuration_stream() + .await + { Ok(_) => { log::warn!( "Sagittarius module configuration stream ended normally; reconnecting" diff --git a/src/validation.rs b/src/validation.rs new file mode 100644 index 0000000..0af90a5 --- /dev/null +++ b/src/validation.rs @@ -0,0 +1,324 @@ +//! Validates incoming REST-flow execution requests against the JSON Schema +//! stored in the flow's `input_schema` setting. +//! +//! Mirrors draco's REST adapter (`adapter/rest/src/validation.rs` and +//! `route::extract_flow_setting_as_struct`), but here the outcome of a failed +//! validation isn't an HTTP response - it's a synthesized [`ExecutionResult`] +//! that gets sent straight back to Sagittarius instead of the flow ever being +//! dispatched to a runtime. + +use lupus::data::{Data, Number}; +use std::collections::BTreeMap; +use std::time::{SystemTime, UNIX_EPOCH}; +use tucana::shared::{ + Error, ExecutionResult, Struct, ValidationFlow, Value, execution_result, + helper::value::to_json_value, value::Kind, +}; + +const REST_FLOW_TYPE: &str = "REST"; +const INPUT_SCHEMA_SETTING_ID: &str = "input_schema"; + +/// Whether `flow` is a REST (webhook) flow - the only flow type that carries +/// a request body to validate against `input_schema` before dispatch. +pub fn is_rest_flow(flow: &ValidationFlow) -> bool { + flow.r#type == REST_FLOW_TYPE +} + +/// Reads the `input_schema` flow setting, the same way draco's REST adapter +/// reads `httpMethod`/`httpURL`/`input_schema` off `flow.settings`. +pub fn extract_input_schema(flow: &ValidationFlow) -> Option<&Struct> { + let setting = match flow + .settings + .iter() + .find(|setting| setting.flow_setting_id == INPUT_SCHEMA_SETTING_ID) + { + Some(setting) => setting, + None => { + log::debug!( + "flow setting is missing: flow_id={} flow_setting_id={}", + flow.flow_id, + INPUT_SCHEMA_SETTING_ID + ); + return None; + } + }; + + let value = match setting.value.as_ref() { + Some(value) => value, + None => { + log::debug!( + "flow setting has no value: flow_id={} flow_setting_id={}", + flow.flow_id, + INPUT_SCHEMA_SETTING_ID + ); + return None; + } + }; + + let kind = match value.kind.as_ref() { + Some(kind) => kind, + None => { + log::debug!( + "flow setting has no kind: flow_id={} flow_setting_id={}", + flow.flow_id, + INPUT_SCHEMA_SETTING_ID + ); + return None; + } + }; + + match kind { + Kind::StructValue(value) => Some(value), + _ => { + log::debug!( + "flow setting has non-struct kind: flow_id={} flow_setting_id={} kind={:?}", + flow.flow_id, + INPUT_SCHEMA_SETTING_ID, + kind + ); + None + } + } +} + +#[derive(Debug)] +pub enum BodyValidationError { + InvalidSchema(String), + InvalidBody(String), + Validation(String), +} + +impl std::fmt::Display for BodyValidationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidSchema(msg) => write!(f, "flow input schema is invalid: {}", msg), + Self::InvalidBody(msg) => write!(f, "request body could not be validated: {}", msg), + Self::Validation(msg) => write!(f, "request body failed schema validation: {}", msg), + } + } +} + +impl std::error::Error for BodyValidationError {} + +/// Validates `body` against `input_schema` (a JSON Schema stored as a `shared.Struct` on the +/// flow). A flow without an `input_schema` (or with an empty one) accepts any body unvalidated. +pub fn validate_body_against_schema( + input_schema: Option<&Struct>, + body: Option<&Value>, +) -> Result<(), BodyValidationError> { + let Some(input_schema) = input_schema.filter(|schema| !schema.fields.is_empty()) else { + return Ok(()); + }; + + let schema_json = to_json_value(Value { + kind: Some(Kind::StructValue(input_schema.clone())), + }); + let raw = serde_json::to_string(&schema_json) + .map_err(|err| BodyValidationError::InvalidSchema(err.to_string()))?; + let schema = lupus::JsonSchema { raw }; + + let body_value = body.cloned().unwrap_or(Value { + kind: Some(Kind::NullValue(0)), + }); + let body_json = to_json_value(body_value); + let data = json_value_to_data(body_json)?; + + lupus::validation::validate_json_schema(&data, &schema) + .map_err(|err| BodyValidationError::Validation(err.to_string())) +} + +/// Mirrors lupus's internal JSON-to-`Data` conversion. We can't reuse `lupus::formats::json` +/// directly here because the intermediate `tucana::shared::Value` type in this workspace and the +/// one `lupus` depends on resolve to different (semver-incompatible 0.0.x) versions of the +/// `tucana` crate, so we go through `serde_json::Value` instead, which both sides share. +fn json_value_to_data(value: serde_json::Value) -> Result { + match value { + serde_json::Value::Null => Ok(Data::Null), + serde_json::Value::Bool(value) => Ok(Data::Bool(value)), + serde_json::Value::Number(value) => { + if let Some(value) = value.as_i64() { + Ok(Data::Number(Number::I64(value))) + } else if let Some(value) = value.as_u64() { + Ok(Data::Number(Number::U64(value))) + } else if let Some(value) = value.as_f64() { + Ok(Data::Number(Number::F64(value))) + } else { + Err(BodyValidationError::InvalidBody( + "unsupported JSON number".to_string(), + )) + } + } + serde_json::Value::String(value) => Ok(Data::String(value)), + serde_json::Value::Array(values) => values + .into_iter() + .map(json_value_to_data) + .collect::, _>>() + .map(Data::Array), + serde_json::Value::Object(fields) => fields + .into_iter() + .map(|(key, value)| Ok((key, json_value_to_data(value)?))) + .collect::, _>>() + .map(Data::Object), + } +} + +/// Builds the `ExecutionResult` sent straight back to Sagittarius when a REST flow's body fails +/// `input_schema` validation, so the flow is never dispatched to a runtime for execution. +pub fn rejection_result( + execution_identifier: String, + flow_id: i64, + error: &BodyValidationError, +) -> ExecutionResult { + let now = epoch_millis_now(); + ExecutionResult { + execution_identifier, + flow_id, + started_at: now, + finished_at: now, + result: Some(execution_result::Result::Error(Error { + code: "A-VALIDATION-000001".to_string(), + category: "InvalidArgument".to_string(), + message: error.to_string(), + timestamp: now, + version: crate::version::runtime_version().to_string(), + ..Default::default() + })), + ..Default::default() + } +} + +fn epoch_millis_now() -> i64 { + match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(duration) => duration.as_millis() as i64, + Err(error) => { + log::warn!("System time before UNIX_EPOCH: {:?}", error); + 0 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use tucana::shared::FlowSetting; + + fn schema_struct(raw_schema: serde_json::Value) -> Struct { + let Value { + kind: Some(Kind::StructValue(schema)), + } = tucana::shared::helper::value::from_json_value(raw_schema) + else { + panic!("expected object schema"); + }; + schema + } + + #[test] + fn rest_flow_type_is_detected() { + let flow = ValidationFlow { + r#type: "REST".to_string(), + ..Default::default() + }; + assert!(is_rest_flow(&flow)); + + let flow = ValidationFlow { + r#type: "CRON".to_string(), + ..Default::default() + }; + assert!(!is_rest_flow(&flow)); + } + + #[test] + fn missing_schema_allows_any_body() { + let body = Value { + kind: Some(Kind::StringValue("anything".to_string())), + }; + assert!(validate_body_against_schema(None, Some(&body)).is_ok()); + } + + #[test] + fn empty_schema_allows_any_body() { + let schema = Struct { + fields: HashMap::new(), + }; + let body = Value { + kind: Some(Kind::StringValue("anything".to_string())), + }; + assert!(validate_body_against_schema(Some(&schema), Some(&body)).is_ok()); + } + + #[test] + fn matching_body_passes_validation() { + let schema = schema_struct(serde_json::json!({ + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" } + } + })); + + let body = tucana::shared::helper::value::from_json_value(serde_json::json!({ + "name": "Ada" + })); + + assert!(validate_body_against_schema(Some(&schema), Some(&body)).is_ok()); + } + + #[test] + fn mismatched_body_fails_validation() { + let schema = schema_struct(serde_json::json!({ + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" } + } + })); + + let body = tucana::shared::helper::value::from_json_value(serde_json::json!({ + "age": 42 + })); + + let err = validate_body_against_schema(Some(&schema), Some(&body)).unwrap_err(); + assert!(matches!(err, BodyValidationError::Validation(_))); + } + + #[test] + fn extract_input_schema_reads_struct_value_setting() { + let schema = schema_struct(serde_json::json!({ "type": "object" })); + let flow = ValidationFlow { + flow_id: 1, + settings: vec![FlowSetting { + database_id: None, + flow_setting_id: "input_schema".to_string(), + value: Some(Value { + kind: Some(Kind::StructValue(schema.clone())), + }), + cast: None, + }], + ..Default::default() + }; + + assert_eq!(extract_input_schema(&flow), Some(&schema)); + } + + #[test] + fn extract_input_schema_is_none_when_setting_missing() { + let flow = ValidationFlow::default(); + assert_eq!(extract_input_schema(&flow), None); + } + + #[test] + fn rejection_result_carries_error_message() { + let error = BodyValidationError::Validation("at $/name: expected string".to_string()); + let result = rejection_result("exec-1".to_string(), 42, &error); + + assert_eq!(result.execution_identifier, "exec-1"); + assert_eq!(result.flow_id, 42); + match result.result { + Some(execution_result::Result::Error(err)) => { + assert!(err.message.contains("expected string")); + } + other => panic!("expected error result, got {:?}", other), + } + } +} From feb65f317760f54ca0bc73277be2e8117033bec9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 2 Aug 2026 23:36:23 +0200 Subject: [PATCH 2/2] deps: added lupus --- Cargo.lock | 577 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + 2 files changed, 577 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index dabe10e..5cd00b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,20 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +25,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.102" @@ -27,6 +47,7 @@ dependencies = [ "futures", "futures-core", "log", + "lupus", "opentelemetry", "prost", "serde", @@ -36,7 +57,7 @@ dependencies = [ "tonic", "tonic-health", "tracing", - "tucana", + "tucana 0.0.77", "uuid", ] @@ -105,6 +126,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.8" @@ -160,6 +204,21 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.11.0" @@ -178,12 +237,24 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytes" version = "1.11.1" @@ -200,6 +271,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -229,6 +302,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "code0-flow" version = "0.0.42" @@ -251,6 +333,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "config" version = "0.15.25" @@ -445,6 +537,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ed25519" version = "2.2.3" @@ -473,6 +571,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -509,6 +616,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fancy-regex" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.4.0" @@ -533,6 +651,17 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + [[package]] name = "fnv" version = "1.0.7" @@ -560,6 +689,22 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fraction" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.33" @@ -676,9 +821,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -735,6 +882,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", ] @@ -820,6 +969,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.5.2" @@ -998,6 +1162,65 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.2", + "libc", +] + [[package]] name = "js-sys" version = "0.3.94" @@ -1021,6 +1244,44 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonschema" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a699d3e77675e6aa4bfffe3b907c8b5f7ed3241f9965bffb25475ad4b08d05" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex", + "fraction", + "getrandom 0.3.4", + "idna", + "itoa", + "jsonschema-regex", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "reqwest", + "rustls", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd1086b01b9349fd4ef9a07433965af64c8ce8159abe633a189e4ff817bd13" +dependencies = [ + "regex-syntax", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1051,12 +1312,32 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "lupus" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c732858255ef8034638b39667f7372f129f58ee76f34dd4c862acf71b1d67709" +dependencies = [ + "jsonschema", + "serde_json", + "tucana 0.0.75", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1078,6 +1359,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + [[package]] name = "mime" version = "0.3.17" @@ -1134,12 +1421,81 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1260,6 +1616,35 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "pathdiff" version = "0.2.3" @@ -1407,6 +1792,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1626,6 +2017,52 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "referencing" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbf332a2f81899f6836f22c03da73dae8a664c32e3016b84692c23cddadc95d" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" version = "1.12.3" @@ -1666,17 +2103,25 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -1752,6 +2197,7 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -1781,12 +2227,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1798,6 +2272,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -1807,6 +2290,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "3.7.0" @@ -1978,6 +2467,22 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -2505,6 +3010,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "tucana" +version = "0.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a58d33f2013edc52c4d782b3bdcdccff2b6b7f2233b91fc833e2f69df2277b" +dependencies = [ + "pbjson", + "pbjson-build", + "pbjson-types", + "prost", + "prost-build", + "prost-types", + "serde", + "serde_json", + "tonic", + "tonic-build", + "tonic-prost", + "tonic-prost-build", +] + [[package]] name = "tucana" version = "0.0.77" @@ -2549,6 +3074,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2602,6 +3133,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2614,6 +3155,22 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2756,6 +3313,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -2774,6 +3340,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-link" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index a63a126..5cd9a93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ tonic = "0.14.1" tucana = { version = "0.0.77", features = ["aquila", "sagittarius_gateway"] } code0-flow = { version = "0.0.42", features = ["flow_config", "flow_health", "flow_telemetry"] } serde_json = "1.0.140" +lupus = "0.0.2" async-nats = "0.50.0" tonic-health = "0.14.1" tokio-stream = "0.1.17"