use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ContextWriter {
Framework,
User,
}
pub const APCORE_KEY_PREFIX: &str = "_apcore.";
pub const EXT_KEY_PREFIX: &str = "ext.";
pub mod namespace_keys {
pub const LOGGING_START_TIME: &str = "_apcore.mw.logging.start_time";
pub const TRACING_SPAN_ID: &str = "_apcore.mw.tracing.span_id";
pub const CIRCUIT_STATE: &str = "_apcore.mw.circuit.state";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NamespaceCheck {
pub valid: bool,
pub warning: bool,
}
#[must_use]
pub fn validate_context_key(writer: ContextWriter, key: &str) -> NamespaceCheck {
let in_apcore = key.starts_with(APCORE_KEY_PREFIX);
let in_ext = key.starts_with(EXT_KEY_PREFIX);
let valid = match writer {
ContextWriter::Framework => !in_ext,
ContextWriter::User => !in_apcore,
};
NamespaceCheck {
valid,
warning: !valid,
}
}
pub fn enforce_context_key(writer: ContextWriter, key: &str) -> NamespaceCheck {
let check = validate_context_key(writer, key);
if check.warning {
match writer {
ContextWriter::User => tracing::warn!(
key = key,
"User middleware wrote to reserved '_apcore.*' namespace; \
framework-owned keys must not be set by user code"
),
ContextWriter::Framework => tracing::warn!(
key = key,
"Framework middleware wrote to user 'ext.*' namespace; \
user-extension keys must not be set by framework code"
),
}
}
check
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn framework_apcore_prefix_is_valid() {
let check = validate_context_key(ContextWriter::Framework, "_apcore.mw.logging.start_time");
assert!(check.valid);
assert!(!check.warning);
}
#[test]
fn user_ext_prefix_is_valid() {
let check = validate_context_key(ContextWriter::User, "ext.my_company.request_id");
assert!(check.valid);
assert!(!check.warning);
}
#[test]
fn user_writing_apcore_prefix_is_violation() {
let check = validate_context_key(ContextWriter::User, "_apcore.mw.tracing.span_id");
assert!(!check.valid);
assert!(check.warning);
}
#[test]
fn framework_writing_ext_prefix_is_violation() {
let check = validate_context_key(ContextWriter::Framework, "ext.user_payload");
assert!(!check.valid);
assert!(check.warning);
}
#[test]
fn unprefixed_keys_are_tolerated_for_backward_compat() {
let f = validate_context_key(ContextWriter::Framework, "legacy_key");
let u = validate_context_key(ContextWriter::User, "legacy_key");
assert!(f.valid && !f.warning);
assert!(u.valid && !u.warning);
}
}