Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions hitbox-configuration/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<PolicyConfig>` ([#253](https://github.com/hit-box/hitbox/pull/253))
Expand Down
119 changes: 119 additions & 0 deletions hitbox-configuration/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ impl From<PolicyConfig> 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<String>,
#[serde(default)]
pub request: MaybeUndefined<Request>,
#[serde(default)]
Expand Down Expand Up @@ -164,10 +172,121 @@ impl ConfigEndpoint {
) as RequestPredicate<ReqBody>,
});
Ok(Endpoint {
name: self.name,
extractors,
request_predicates,
response_predicates,
policy: Arc::new(self.policy.into()),
})
}
}

// =============================================================================
// 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<ConfigEndpoint>,
}

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<ReqBody, ResBody>(
self,
) -> Result<Vec<Endpoint<ReqBody, ResBody>>, 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<ConfigEndpoint>;

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<ConfigEndpoint> for ConfigEndpoints {
fn from_iter<T: IntoIterator<Item = ConfigEndpoint>>(iter: T) -> Self {
Self {
endpoints: iter.into_iter().collect(),
}
}
}
20 changes: 20 additions & 0 deletions hitbox-configuration/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
pub request_predicates: ArcRequestPredicate<ReqBody>,
pub response_predicates: ArcResponsePredicate<ResBody>,
pub extractors: ArcRequestExtractor<ReqBody>,
Expand All @@ -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", &"...")
Expand All @@ -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),
Expand Down Expand Up @@ -142,6 +151,7 @@ where
ReqBody: hyper::body::Body,
ResBody: hyper::body::Body,
{
name: Option<String>,
request_predicates: Option<ArcRequestPredicate<ReqBody>>,
response_predicates: Option<ArcResponsePredicate<ResBody>>,
extractors: Option<ArcRequestExtractor<ReqBody>>,
Expand All @@ -156,13 +166,22 @@ where
/// Create a new builder with default values.
pub fn new() -> Self {
Self {
name: None,
request_predicates: None,
response_predicates: None,
extractors: None,
policy: Arc::new(PolicyConfig::default()),
}
}

/// Set the endpoint name for debugging and tracing.
pub fn name(self, name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
..self
}
}

/// Set the request predicates.
pub fn request_predicate<P>(self, predicate: P) -> Self
where
Expand Down Expand Up @@ -216,6 +235,7 @@ where
{
let default = Endpoint::<ReqBody, ResBody>::default();
Endpoint {
name: self.name,
request_predicates: self
.request_predicates
.unwrap_or(default.request_predicates),
Expand Down
11 changes: 11 additions & 0 deletions hitbox-configuration/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
#[source]
source: Box<ConfigError>,
},
}

impl From<http::method::InvalidMethod> for ConfigError {
Expand Down
2 changes: 1 addition & 1 deletion hitbox-configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
Loading
Loading