diff --git a/hitbox-configuration/CHANGELOG.md b/hitbox-configuration/CHANGELOG.md index f07dc8eb..83ceaaa5 100644 --- a/hitbox-configuration/CHANGELOG.md +++ b/hitbox-configuration/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release - `Endpoint` now implements `CacheConfigs` for use with `SelectiveCacheFuture` ([#253](https://github.com/hit-box/hitbox/pull/253)) +- `ConfigEndpoints` wrapper type for parsing a list of endpoints from YAML, and optional `name` field on `ConfigEndpoint` / `Endpoint` / `EndpointBuilder` for identifying endpoints in multi-endpoint configurations ([#276](https://github.com/hit-box/hitbox/pull/276)) +- `ConfigError::EndpointAt { index, name, source }` variant carrying positional context when `ConfigEndpoints::into_endpoints` fails on a specific entry ([#276](https://github.com/hit-box/hitbox/pull/276)) ### Changed - `Endpoint` now stores `policy` as `Arc` ([#253](https://github.com/hit-box/hitbox/pull/253)) diff --git a/hitbox-configuration/src/config.rs b/hitbox-configuration/src/config.rs index e3b85211..7dc61f04 100644 --- a/hitbox-configuration/src/config.rs +++ b/hitbox-configuration/src/config.rs @@ -104,6 +104,14 @@ impl From for policy::PolicyConfig { #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)] pub struct ConfigEndpoint { + /// Optional identifier for this endpoint. + /// + /// Surfaced in `Debug` output and error messages produced by + /// [`ConfigEndpoints::into_endpoints`]. Reserved for future use in + /// tracing spans and metrics labels; not currently consumed by + /// runtime routing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, #[serde(default)] pub request: MaybeUndefined, #[serde(default)] @@ -164,6 +172,7 @@ impl ConfigEndpoint { ) as RequestPredicate, }); Ok(Endpoint { + name: self.name, extractors, request_predicates, response_predicates, @@ -171,3 +180,113 @@ impl ConfigEndpoint { }) } } + +// ============================================================================= +// ConfigEndpoints +// ============================================================================= + +/// A list of endpoint configurations for multi-endpoint cache routing. +/// +/// Deserializes transparently from a YAML list of endpoint definitions. +/// Each endpoint is evaluated in order; first match wins. +/// +/// # Example +/// +/// The value parses as a bare YAML list — embed it under whatever key the +/// surrounding configuration uses (for example, `endpoints:` on a larger +/// config struct): +/// +/// ```yaml +/// - name: "user-by-id" +/// request: +/// - Method: GET +/// - Path: "/api/users/{id}" +/// policy: +/// Enabled: +/// ttl: 300s +/// - request: +/// - Method: GET +/// policy: +/// Enabled: +/// ttl: 30s +/// ``` +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)] +#[serde(transparent)] +pub struct ConfigEndpoints { + pub endpoints: Vec, +} + +impl ConfigEndpoints { + /// Number of endpoint configurations in the list. + pub fn len(&self) -> usize { + self.endpoints.len() + } + + /// Whether the list contains no endpoints. + pub fn is_empty(&self) -> bool { + self.endpoints.is_empty() + } + + /// Iterate over the endpoint configurations by reference. + pub fn iter(&self) -> std::slice::Iter<'_, ConfigEndpoint> { + self.endpoints.iter() + } + + /// Convert all endpoint configurations into runtime [`Endpoint`] instances. + /// + /// On failure, the returned error is wrapped in + /// [`ConfigError::EndpointAt`] carrying the zero-based index of the + /// failing entry and its `name` when present, making it straightforward + /// to locate a bad entry in a long list. + pub fn into_endpoints( + self, + ) -> Result>, ConfigError> + where + ReqBody: hyper::body::Body + Send + Unpin + Debug + 'static, + ReqBody::Error: Debug + Send, + ReqBody::Data: Send, + ResBody: hyper::body::Body + Send + Unpin + 'static, + ResBody::Error: Debug + Send, + ResBody::Data: Send, + { + self.endpoints + .into_iter() + .enumerate() + .map(|(index, ce)| { + let name = ce.name.clone(); + ce.into_endpoint() + .map_err(|source| ConfigError::EndpointAt { + index, + name, + source: Box::new(source), + }) + }) + .collect() + } +} + +impl IntoIterator for ConfigEndpoints { + type Item = ConfigEndpoint; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.endpoints.into_iter() + } +} + +impl<'a> IntoIterator for &'a ConfigEndpoints { + type Item = &'a ConfigEndpoint; + type IntoIter = std::slice::Iter<'a, ConfigEndpoint>; + + fn into_iter(self) -> Self::IntoIter { + self.endpoints.iter() + } +} + +impl FromIterator for ConfigEndpoints { + fn from_iter>(iter: T) -> Self { + Self { + endpoints: iter.into_iter().collect(), + } + } +} diff --git a/hitbox-configuration/src/endpoint.rs b/hitbox-configuration/src/endpoint.rs index 85f0b752..ca33d1f5 100644 --- a/hitbox-configuration/src/endpoint.rs +++ b/hitbox-configuration/src/endpoint.rs @@ -25,6 +25,13 @@ where ReqBody: hyper::body::Body, ResBody: hyper::body::Body, { + /// Optional identifier for this endpoint. + /// + /// Propagated from [`crate::ConfigEndpoint::name`] and surfaced in + /// `Debug` output and routing errors. Reserved for future use in + /// tracing spans and metrics labels; not currently consumed by + /// runtime routing. + pub name: Option, pub request_predicates: ArcRequestPredicate, pub response_predicates: ArcResponsePredicate, pub extractors: ArcRequestExtractor, @@ -38,6 +45,7 @@ where { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Endpoint") + .field("name", &self.name) .field("request_predicates", &"...") .field("response_predicates", &"...") .field("extractors", &"...") @@ -53,6 +61,7 @@ where { fn clone(&self) -> Self { Self { + name: self.name.clone(), request_predicates: Arc::clone(&self.request_predicates), response_predicates: Arc::clone(&self.response_predicates), extractors: Arc::clone(&self.extractors), @@ -142,6 +151,7 @@ where ReqBody: hyper::body::Body, ResBody: hyper::body::Body, { + name: Option, request_predicates: Option>, response_predicates: Option>, extractors: Option>, @@ -156,6 +166,7 @@ where /// Create a new builder with default values. pub fn new() -> Self { Self { + name: None, request_predicates: None, response_predicates: None, extractors: None, @@ -163,6 +174,14 @@ where } } + /// Set the endpoint name for debugging and tracing. + pub fn name(self, name: impl Into) -> Self { + Self { + name: Some(name.into()), + ..self + } + } + /// Set the request predicates. pub fn request_predicate

(self, predicate: P) -> Self where @@ -216,6 +235,7 @@ where { let default = Endpoint::::default(); Endpoint { + name: self.name, request_predicates: self .request_predicates .unwrap_or(default.request_predicates), diff --git a/hitbox-configuration/src/error.rs b/hitbox-configuration/src/error.rs index 6a242a54..909bb164 100644 --- a/hitbox-configuration/src/error.rs +++ b/hitbox-configuration/src/error.rs @@ -46,6 +46,17 @@ pub enum ConfigError { /// Empty path list in 'in' operation #[error("Path 'in' operation requires at least one pattern")] EmptyPathList, + + /// Error originating from a specific endpoint in a multi-endpoint configuration + #[error("endpoint #{index}: {source}")] + EndpointAt { + /// Zero-based position of the failing endpoint in the list + index: usize, + /// Name of the failing endpoint, when one was provided + name: Option, + #[source] + source: Box, + }, } impl From for ConfigError { diff --git a/hitbox-configuration/src/lib.rs b/hitbox-configuration/src/lib.rs index c2b454b9..b5125d35 100644 --- a/hitbox-configuration/src/lib.rs +++ b/hitbox-configuration/src/lib.rs @@ -11,7 +11,7 @@ pub mod predicates; pub mod types; pub use backend::Backend; -pub use config::ConfigEndpoint; +pub use config::{ConfigEndpoint, ConfigEndpoints}; pub use endpoint::{ Endpoint, EndpointBuilder, RequestExtractor, RequestPredicate, ResponsePredicate, }; diff --git a/hitbox-configuration/tests/test_config_endpoints.rs b/hitbox-configuration/tests/test_config_endpoints.rs new file mode 100644 index 00000000..05a13eb2 --- /dev/null +++ b/hitbox-configuration/tests/test_config_endpoints.rs @@ -0,0 +1,204 @@ +use bytes::Bytes; +use hitbox_configuration::{ConfigEndpoint, ConfigEndpoints, ConfigError, Endpoint}; +use http_body_util::Empty; + +type ReqBody = Empty; +type ResBody = Empty; + +#[test] +fn test_single_endpoint_with_name() { + let yaml_input = r" +name: my-endpoint +request: +- Method: GET +policy: + Enabled: + ttl: 60s +"; + let config: ConfigEndpoint = serde_saphyr::from_str(yaml_input).unwrap(); + assert_eq!(config.name, Some("my-endpoint".to_string())); +} + +#[test] +fn test_single_endpoint_without_name() { + let yaml_input = r" +request: +- Method: GET +policy: + Enabled: + ttl: 60s +"; + let config: ConfigEndpoint = serde_saphyr::from_str(yaml_input).unwrap(); + assert_eq!(config.name, None); +} + +#[test] +fn test_parse_multi_endpoint_list() { + let yaml_input = r" +- name: users + request: + - Method: GET + - Path: /api/users/{id} + response: + - Status: 200 + extractors: + - Method: ~ + - Path: '/api/users/{id}' + policy: + Enabled: + ttl: 300s + +- name: orders + request: + - Method: POST + - Path: /api/orders + policy: + Enabled: + ttl: 60s + +- request: + - Method: GET + policy: + Enabled: + ttl: 30s +"; + let config: ConfigEndpoints = serde_saphyr::from_str(yaml_input).unwrap(); + assert_eq!(config.len(), 3); + assert_eq!(config.endpoints[0].name, Some("users".to_string())); + assert_eq!(config.endpoints[1].name, Some("orders".to_string())); + assert_eq!(config.endpoints[2].name, None); +} + +#[test] +fn test_into_endpoints_count() { + let yaml_input = r" +- request: + - Method: GET + policy: + Enabled: + ttl: 60s +- request: + - Method: POST + policy: + Enabled: + ttl: 120s +"; + let config: ConfigEndpoints = serde_saphyr::from_str(yaml_input).unwrap(); + let endpoints = config.into_endpoints::().unwrap(); + assert_eq!(endpoints.len(), 2); +} + +#[test] +fn test_into_endpoints_order_preservation() { + let yaml_input = r" +- policy: + Enabled: + ttl: 10s +- policy: + Enabled: + ttl: 20s +- policy: + Enabled: + ttl: 30s +"; + let config: ConfigEndpoints = serde_saphyr::from_str(yaml_input).unwrap(); + let endpoints = config.into_endpoints::().unwrap(); + assert_eq!(endpoints.len(), 3); + + // Verify order matches YAML order via policy TTL + use hitbox::policy::PolicyConfig; + use std::time::Duration; + for (i, expected_secs) in [10, 20, 30].iter().enumerate() { + match endpoints[i].policy.as_ref() { + PolicyConfig::Enabled(cfg) => { + assert_eq!(cfg.ttl, Some(Duration::from_secs(*expected_secs))); + } + PolicyConfig::Disabled => panic!("Expected Enabled policy for endpoint {}", i), + } + } +} + +#[test] +fn test_single_element_list() { + let yaml_input = r" +- request: + - Method: GET + policy: + Enabled: + ttl: 60s +"; + let config: ConfigEndpoints = serde_saphyr::from_str(yaml_input).unwrap(); + let endpoints = config.into_endpoints::().unwrap(); + assert_eq!(endpoints.len(), 1); +} + +#[test] +fn test_name_propagates_to_endpoint() { + let yaml_input = r" +- name: my-cached-endpoint + request: + - Method: GET + policy: + Enabled: + ttl: 60s +"; + let config: ConfigEndpoints = serde_saphyr::from_str(yaml_input).unwrap(); + let endpoints = config.into_endpoints::().unwrap(); + assert_eq!(endpoints[0].name, Some("my-cached-endpoint".to_string())); +} + +#[test] +fn test_empty_list() { + let yaml_input = "[]"; + let config: ConfigEndpoints = serde_saphyr::from_str(yaml_input).unwrap(); + let endpoints = config.into_endpoints::().unwrap(); + assert!(endpoints.is_empty()); +} + +#[test] +fn test_endpoint_builder_sets_name() { + let endpoint: Endpoint = Endpoint::builder().name("custom-name").build(); + assert_eq!(endpoint.name, Some("custom-name".to_string())); +} + +#[test] +fn test_endpoint_builder_without_name_defaults_to_none() { + let endpoint: Endpoint = Endpoint::builder().build(); + assert_eq!(endpoint.name, None); +} + +#[test] +fn test_into_endpoints_error_includes_index_and_name() { + // Second entry has a malformed HTTP method that fails during conversion. + let yaml_input = r#" +- name: good-endpoint + request: + - Method: GET + policy: + Enabled: + ttl: 60s +- name: broken-endpoint + request: + - Method: "INVALID METHOD" + policy: + Enabled: + ttl: 60s +"#; + let config: ConfigEndpoints = serde_saphyr::from_str(yaml_input).unwrap(); + let err = config + .into_endpoints::() + .expect_err("conversion should fail on invalid HTTP method"); + + match err { + ConfigError::EndpointAt { + index, + name, + source, + } => { + assert_eq!(index, 1); + assert_eq!(name, Some("broken-endpoint".to_string())); + assert!(matches!(*source, ConfigError::InvalidMethod(..))); + } + other => panic!("expected ConfigError::EndpointAt, got: {other:?}"), + } +} diff --git a/hitbox-test/benches/cache_future_reference.rs b/hitbox-test/benches/cache_future_reference.rs index 258703b0..eef900da 100644 --- a/hitbox-test/benches/cache_future_reference.rs +++ b/hitbox-test/benches/cache_future_reference.rs @@ -27,7 +27,7 @@ use hitbox_backend::composition::policy::{ }; use hitbox_backend::format::BincodeFormat; use hitbox_backend::{CacheBackend, CompositionBackend, PassthroughCompressor}; -use hitbox_configuration::{Backend as ConfigBackend, ConfigEndpoint}; +use hitbox_configuration::{Backend as ConfigBackend, ConfigEndpoints, Endpoint}; use hitbox_core::DisabledOffload; use hitbox_core::Upstream; use hitbox_http::extractors::MethodConfig; @@ -630,7 +630,7 @@ fn bench_compare_composition_write(c: &mut Criterion) { #[derive(Debug, serde::Deserialize)] struct BenchConfig { backend: ConfigBackend, - endpoint: ConfigEndpoint, + endpoints: ConfigEndpoints, } /// Load configuration from YAML @@ -643,6 +643,16 @@ fn load_body_config() -> BenchConfig { serde_saphyr::from_str(REFERENCE_CONFIG_BODY).expect("Failed to parse body reference config") } +/// Extract the first endpoint from a list of endpoint configurations. +fn first_endpoint(endpoints: ConfigEndpoints) -> Endpoint { + endpoints + .into_iter() + .next() + .expect("endpoints list must not be empty") + .into_endpoint() + .expect("Failed to create endpoint") +} + // Type alias for dynamic backend (must match the blanket impl in hitbox_backend) // Arc implements CacheBackend, so CacheFuture can use it as B. // However, CacheFuture::new takes Arc, so we need Arc>. @@ -662,10 +672,7 @@ fn bench_compare_request_predicates(c: &mut Criterion) { // Dynamic dispatch predicates (from config) let config = load_config(); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dynamic_predicates = endpoint.request_predicates; group.bench_function("static", |b| { @@ -696,10 +703,7 @@ fn bench_compare_response_predicates(c: &mut Criterion) { // Dynamic dispatch predicates (from config) let config = load_config(); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dynamic_predicates = endpoint.response_predicates; group.bench_function("static", |b| { @@ -732,10 +736,7 @@ fn bench_compare_extractors(c: &mut Criterion) { // Dynamic dispatch extractors (from config) let config = load_config(); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dynamic_extractors = endpoint.extractors; group.bench_function("static", |b| { @@ -810,10 +811,7 @@ fn bench_compare_cache_future_hit(c: &mut Criterion) { .backend .into_backend() .expect("Failed to create backend"); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dyn_policy = Arc::clone(&endpoint.policy); // Pre-populate dynamic cache @@ -942,10 +940,7 @@ fn bench_compare_cache_future_miss(c: &mut Criterion) { .backend .into_backend() .expect("Failed to create backend"); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dyn_policy = Arc::clone(&endpoint.policy); let dyn_backend_arc: Arc = Arc::new(dyn_backend); @@ -1045,10 +1040,7 @@ fn bench_compare_body_request_predicates(c: &mut Criterion) { // Dynamic dispatch predicates (from body config) let config = load_body_config(); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dynamic_predicates = endpoint.request_predicates; group.bench_function("static", |b| { @@ -1079,10 +1071,7 @@ fn bench_compare_body_response_predicates(c: &mut Criterion) { // Dynamic dispatch predicates (from body config) let config = load_body_config(); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dynamic_predicates = endpoint.response_predicates; group.bench_function("static", |b| { @@ -1115,10 +1104,7 @@ fn bench_compare_body_extractors(c: &mut Criterion) { // Dynamic dispatch extractors (from body config) let config = load_body_config(); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dynamic_extractors = endpoint.extractors; group.bench_function("static", |b| { @@ -1193,10 +1179,7 @@ fn bench_compare_body_cache_future_hit(c: &mut Criterion) { .backend .into_backend() .expect("Failed to create backend"); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dyn_policy = Arc::clone(&endpoint.policy); // Pre-populate dynamic cache @@ -1324,10 +1307,7 @@ fn bench_compare_body_cache_future_miss(c: &mut Criterion) { .backend .into_backend() .expect("Failed to create backend"); - let endpoint = config - .endpoint - .into_endpoint::() - .expect("Failed to create endpoint"); + let endpoint = first_endpoint(config.endpoints); let dyn_policy = Arc::clone(&endpoint.policy); let dyn_backend_arc: Arc = Arc::new(dyn_backend); diff --git a/hitbox-test/benches/fixtures/load_test_config.yaml b/hitbox-test/benches/fixtures/load_test_config.yaml index cae3a44b..c2feb2fc 100644 --- a/hitbox-test/benches/fixtures/load_test_config.yaml +++ b/hitbox-test/benches/fixtures/load_test_config.yaml @@ -13,30 +13,31 @@ backend: type: Disabled # Endpoint configuration -endpoint: - request: - - Method: POST - - Path: "/api/orders" - - Header: - x-tenant-id: - exist: true - - Header: - content-type: - exist: true +endpoints: + - name: "load-test-endpoint" + request: + - Method: POST + - Path: "/api/orders" + - Header: + x-tenant-id: + exist: true + - Header: + content-type: + exist: true - response: - - Status: 200 - - Header: - content-type: - contains: application/json + response: + - Status: 200 + - Header: + content-type: + contains: application/json - extractors: - - Method: - - Path: "/api/orders" - - Header: x-tenant-id - - Header: content-type - - Query: include + extractors: + - Method: + - Path: "/api/orders" + - Header: x-tenant-id + - Header: content-type + - Query: include - policy: - Enabled: - ttl: 300s + policy: + Enabled: + ttl: 300s diff --git a/hitbox-test/benches/fixtures/reference_config.yaml b/hitbox-test/benches/fixtures/reference_config.yaml index 28dac313..22c66f13 100644 --- a/hitbox-test/benches/fixtures/reference_config.yaml +++ b/hitbox-test/benches/fixtures/reference_config.yaml @@ -11,42 +11,43 @@ backend: compression: type: Disabled -endpoint: - request: - - Method: POST - - Path: "/v1/users/{user_id}/orders" - - Header: - authorization: - exist: true - - Header: - x-tenant-id: - exist: true - - Header: - x-idempotency-key: - exist: true +endpoints: + - name: "bench-endpoint" + request: + - Method: POST + - Path: "/v1/users/{user_id}/orders" + - Header: + authorization: + exist: true + - Header: + x-tenant-id: + exist: true + - Header: + x-idempotency-key: + exist: true - response: - - Status: - class: Success - - Header: - content-type: - exist: true - - Header: - cache-control: - exist: true + response: + - Status: + class: Success + - Header: + content-type: + exist: true + - Header: + cache-control: + exist: true - extractors: - - Method: - - Path: "/v1/users/{user_id}/orders" - - Query: include - - Query: fields - - Header: x-tenant-id - - Header: - name: authorization - transform: - - Hash - - Header: x-idempotency-key + extractors: + - Method: + - Path: "/v1/users/{user_id}/orders" + - Query: include + - Query: fields + - Header: x-tenant-id + - Header: + name: authorization + transform: + - Hash + - Header: x-idempotency-key - policy: - Enabled: - ttl: 300s + policy: + Enabled: + ttl: 300s diff --git a/hitbox-test/benches/fixtures/reference_config_body.yaml b/hitbox-test/benches/fixtures/reference_config_body.yaml index 23629ff7..924a2a74 100644 --- a/hitbox-test/benches/fixtures/reference_config_body.yaml +++ b/hitbox-test/benches/fixtures/reference_config_body.yaml @@ -11,52 +11,53 @@ backend: compression: type: Disabled -endpoint: - request: - - Method: POST - - Path: "/v1/users/{user_id}/orders" - - Header: - authorization: - exist: true - - Header: - x-tenant-id: - exist: true - - Header: - x-idempotency-key: - exist: true - - Body: - jq: - expression: ".order.customer_id" - exist: true +endpoints: + - name: "bench-endpoint-body" + request: + - Method: POST + - Path: "/v1/users/{user_id}/orders" + - Header: + authorization: + exist: true + - Header: + x-tenant-id: + exist: true + - Header: + x-idempotency-key: + exist: true + - Body: + jq: + expression: ".order.customer_id" + exist: true - response: - - Status: - class: Success - - Header: - content-type: - exist: true - - Header: - cache-control: - exist: true - - Body: - jq: - expression: ".data | type" - eq: "array" + response: + - Status: + class: Success + - Header: + content-type: + exist: true + - Header: + cache-control: + exist: true + - Body: + jq: + expression: ".data | type" + eq: "array" - extractors: - - Method: - - Path: "/v1/users/{user_id}/orders" - - Query: include - - Query: fields - - Header: x-tenant-id - - Header: - name: authorization - transform: - - Hash - - Header: x-idempotency-key - - Body: - jq: "{customer_id: .order.customer_id, shipping: .order.shipping_method}" + extractors: + - Method: + - Path: "/v1/users/{user_id}/orders" + - Query: include + - Query: fields + - Header: x-tenant-id + - Header: + name: authorization + transform: + - Hash + - Header: x-idempotency-key + - Body: + jq: "{customer_id: .order.customer_id, shipping: .order.shipping_method}" - policy: - Enabled: - ttl: 300s + policy: + Enabled: + ttl: 300s diff --git a/hitbox-test/benches/load_test.rs b/hitbox-test/benches/load_test.rs index 9a49d3fb..629471b7 100644 --- a/hitbox-test/benches/load_test.rs +++ b/hitbox-test/benches/load_test.rs @@ -26,7 +26,7 @@ use std::time::Duration; use axum::routing::post; use axum::{Json, Router}; -use hitbox_configuration::{Backend, ConfigEndpoint}; +use hitbox_configuration::{Backend, ConfigEndpoints}; use hitbox_tower::Cache; use serde::{Deserialize, Serialize}; use tokio::net::TcpListener; @@ -131,7 +131,7 @@ impl Args { #[derive(Debug, Deserialize)] struct LoadTestConfig { backend: Backend, - endpoint: ConfigEndpoint, + endpoints: ConfigEndpoints, } // ============================================================================ @@ -496,8 +496,13 @@ async fn start_server_with_cache( // Create backend from config let backend = config.backend.into_backend()?; - // Create endpoint config - let endpoint_config = config.endpoint.into_endpoint()?; + // Create endpoint config (use first endpoint from the list) + let endpoint_config = config + .endpoints + .into_iter() + .next() + .expect("endpoints list must not be empty") + .into_endpoint()?; // Create cache layer let cache_layer = Cache::builder()