use std::time::{Instant, SystemTime, UNIX_EPOCH};
use saddle_admission::RequestMemory;
use super::{
FileStream,
codec::{Field, JsonlRecord, Scalar},
fixed_core::{FixedEncodingLease, FixedFileSink},
};
use crate::file::{CompletionResult, FixedCompletion, FixedFailure, SubmitError};
pub const CORRELATION_SCHEMA_VERSION: u16 = 1;
pub const ALPHA1_TERMINAL_SCHEMA_VERSION: u16 = 1;
pub const MAX_ALPHA1_ID_BYTES: usize = 128;
pub const MAX_ALPHA1_COMPONENT_BYTES: usize = 128;
const CORRELATION_SINK_IDENTITY: [u8; 32] = [0xc8; 32];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Alpha1TerminalKind {
Completed,
TechnicalFailure,
}
impl Alpha1TerminalKind {
const fn value(self) -> &'static str {
match self {
Self::Completed => "completed",
Self::TechnicalFailure => "technical_failure",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Alpha1TechnicalCode {
FunctionNotFound,
FunctionRequestInvalid,
CapacityRejected,
DeadlineExceeded,
DependencyUnavailable,
ContractResultInvalid,
InternalFailure,
TransportFailure,
}
impl Alpha1TechnicalCode {
const fn value(self) -> &'static str {
match self {
Self::FunctionNotFound => "function_not_found",
Self::FunctionRequestInvalid => "function_request_invalid",
Self::CapacityRejected => "capacity_rejected",
Self::DeadlineExceeded => "deadline_exceeded",
Self::DependencyUnavailable => "dependency_unavailable",
Self::ContractResultInvalid => "contract_result_invalid",
Self::InternalFailure => "internal_failure",
Self::TransportFailure => "transport_failure",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Alpha1ExecutionCertainty {
NotExecuted,
Executed,
MayHaveExecuted,
}
impl Alpha1ExecutionCertainty {
const fn value(self) -> &'static str {
match self {
Self::NotExecuted => "not_executed",
Self::Executed => "executed",
Self::MayHaveExecuted => "may_have_executed",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Alpha1ResourceState {
Zero,
NonZero,
Unknown,
}
impl Alpha1ResourceState {
const fn value(self) -> &'static str {
match self {
Self::Zero => "zero",
Self::NonZero => "nonzero",
Self::Unknown => "unknown",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Alpha1TerminalRecordError {
EmptyField,
FieldTooLong,
ControlCharacter,
InvalidCompletedOutcome,
MissingTechnicalCode,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Alpha1CallIdentity<'a> {
request_id: &'a str,
call_id: &'a str,
business_unit: &'a str,
function: &'a str,
}
impl<'a> Alpha1CallIdentity<'a> {
pub fn new(
request_id: &'a str,
call_id: &'a str,
business_unit: &'a str,
function: &'a str,
) -> Result<Self, Alpha1TerminalRecordError> {
validate_alpha1_field(request_id, MAX_ALPHA1_ID_BYTES)?;
validate_alpha1_field(call_id, MAX_ALPHA1_ID_BYTES)?;
validate_alpha1_field(business_unit, MAX_ALPHA1_COMPONENT_BYTES)?;
validate_alpha1_field(function, MAX_ALPHA1_COMPONENT_BYTES)?;
Ok(Self {
request_id,
call_id,
business_unit,
function,
})
}
#[doc(hidden)]
pub const fn into_parts(self) -> (&'a str, &'a str, &'a str, &'a str) {
(
self.request_id,
self.call_id,
self.business_unit,
self.function,
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Alpha1TechnicalFailure {
code: Alpha1TechnicalCode,
certainty: Alpha1ExecutionCertainty,
}
impl Alpha1TechnicalFailure {
pub const fn new(code: Alpha1TechnicalCode, certainty: Alpha1ExecutionCertainty) -> Self {
Self { code, certainty }
}
#[doc(hidden)]
pub const fn into_parts(self) -> (Alpha1TechnicalCode, Alpha1ExecutionCertainty) {
(self.code, self.certainty)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Alpha1TerminalRecord<'a> {
request_id: &'a str,
call_id: &'a str,
business_unit: &'a str,
function: &'a str,
terminal_kind: Alpha1TerminalKind,
technical_code: Option<Alpha1TechnicalCode>,
certainty: Alpha1ExecutionCertainty,
resource_state: Alpha1ResourceState,
}
#[doc(hidden)]
pub struct Alpha1TerminalReservation<
'a,
const BLOCKS: usize,
const BYTES: usize,
const COMMANDS: usize,
> {
lease: FixedEncodingLease<BLOCKS, BYTES, COMMANDS>,
identity: Alpha1CallIdentity<'a>,
sink_identity: CorrelationSinkIdentity,
}
impl<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize>
Alpha1TerminalReservation<'_, BLOCKS, BYTES, COMMANDS>
{
#[doc(hidden)]
pub fn commit(
self,
terminal_kind: Alpha1TerminalKind,
technical_code: Option<Alpha1TechnicalCode>,
certainty: Alpha1ExecutionCertainty,
resource_state: Alpha1ResourceState,
) -> Result<CorrelationSinkIdentity, CorrelationSubmitError> {
let (request_id, call_id, business_unit, function) = self.identity.into_parts();
let record = Alpha1TerminalRecord::new(
request_id,
call_id,
business_unit,
function,
terminal_kind,
technical_code,
certainty,
resource_state,
)
.map_err(|_| CorrelationSubmitError::Encoding)?;
encode_alpha1_terminal(self.lease, record)?;
Ok(self.sink_identity)
}
}
impl<'a> Alpha1TerminalRecord<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
request_id: &'a str,
call_id: &'a str,
business_unit: &'a str,
function: &'a str,
terminal_kind: Alpha1TerminalKind,
technical_code: Option<Alpha1TechnicalCode>,
certainty: Alpha1ExecutionCertainty,
resource_state: Alpha1ResourceState,
) -> Result<Self, Alpha1TerminalRecordError> {
let identity = Alpha1CallIdentity::new(request_id, call_id, business_unit, function)?;
match (terminal_kind, technical_code, certainty) {
(Alpha1TerminalKind::Completed, None, Alpha1ExecutionCertainty::Executed) => {}
(Alpha1TerminalKind::Completed, _, _) => {
return Err(Alpha1TerminalRecordError::InvalidCompletedOutcome);
}
(Alpha1TerminalKind::TechnicalFailure, None, _) => {
return Err(Alpha1TerminalRecordError::MissingTechnicalCode);
}
(Alpha1TerminalKind::TechnicalFailure, Some(_), _) => {}
}
let (request_id, call_id, business_unit, function) = identity.into_parts();
Ok(Self {
request_id,
call_id,
business_unit,
function,
terminal_kind,
technical_code,
certainty,
resource_state,
})
}
}
fn validate_alpha1_field(value: &str, maximum: usize) -> Result<(), Alpha1TerminalRecordError> {
if value.is_empty() {
return Err(Alpha1TerminalRecordError::EmptyField);
}
if value.len() > maximum {
return Err(Alpha1TerminalRecordError::FieldTooLong);
}
if value.chars().any(char::is_control) {
return Err(Alpha1TerminalRecordError::ControlCharacter);
}
Ok(())
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CorrelationCallKind {
ExternalRoute,
Service,
Database,
Transaction,
}
impl CorrelationCallKind {
const fn value(self) -> &'static str {
match self {
Self::ExternalRoute => "external_route",
Self::Service => "service",
Self::Database => "database",
Self::Transaction => "transaction",
}
}
const fn stream(self) -> FileStream {
match self {
Self::ExternalRoute => FileStream::Access,
Self::Service | Self::Database | Self::Transaction => FileStream::Trace,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CorrelationRecordPhase {
Started,
Finished,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CorrelationCallOutcome {
Success,
Failure,
Cancelled,
Abandoned,
}
impl CorrelationCallOutcome {
const fn value(self) -> &'static str {
match self {
Self::Success => "success",
Self::Failure => "failure",
Self::Cancelled => "cancelled",
Self::Abandoned => "abandoned",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CorrelationRecord<'a> {
kind: CorrelationCallKind,
phase: CorrelationRecordPhase,
trace_id: &'a str,
span_id: &'a str,
parent_span_id: Option<&'a str>,
route: &'a str,
service: &'a str,
operation: &'a str,
outcome: Option<CorrelationCallOutcome>,
elapsed_us: Option<u64>,
}
impl<'a> CorrelationRecord<'a> {
#[doc(hidden)]
pub const fn started(
kind: CorrelationCallKind,
trace_id: &'a str,
span_id: &'a str,
parent_span_id: Option<&'a str>,
route: &'a str,
service: &'a str,
operation: &'a str,
) -> Self {
Self {
kind,
phase: CorrelationRecordPhase::Started,
trace_id,
span_id,
parent_span_id,
route,
service,
operation,
outcome: None,
elapsed_us: None,
}
}
pub const fn phase(&self) -> CorrelationRecordPhase {
self.phase
}
pub const fn kind(&self) -> CorrelationCallKind {
self.kind
}
fn finished(self, outcome: CorrelationCallOutcome, elapsed_us: u64) -> Self {
Self {
phase: CorrelationRecordPhase::Finished,
outcome: Some(outcome),
elapsed_us: Some(elapsed_us),
..self
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CorrelationSinkIdentity {
schema_version: u16,
sink_identity: [u8; 32],
}
impl CorrelationSinkIdentity {
pub const fn schema_version(self) -> u16 {
self.schema_version
}
pub const fn sink_identity(self) -> [u8; 32] {
self.sink_identity
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CorrelationHealth {
Accepting,
ShuttingDown,
Failed(FixedFailure),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CorrelationSubmitError {
Backpressured,
Encoding,
ShuttingDown,
Unhealthy(FixedFailure),
Admission,
}
impl From<SubmitError> for CorrelationSubmitError {
fn from(error: SubmitError) -> Self {
match error {
SubmitError::Backpressured | SubmitError::CompletionBusy => Self::Backpressured,
SubmitError::Encoding => Self::Encoding,
SubmitError::ShuttingDown | SubmitError::OutstandingEncoding => Self::ShuttingDown,
SubmitError::Unhealthy(failure) => Self::Unhealthy(failure),
SubmitError::Admission(_) => Self::Admission,
}
}
}
#[doc(hidden)]
pub struct CorrelationSinkOwner<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize> {
sink: FixedFileSink<BLOCKS, BYTES, COMMANDS>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VerifiedCorrelationSink {
identity: CorrelationSinkIdentity,
health: CorrelationHealth,
}
#[doc(hidden)]
pub struct CorrelationBarrier<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize> {
identity: CorrelationSinkIdentity,
pub(super) completion: FixedCompletion<BLOCKS, BYTES, COMMANDS>,
}
impl<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize>
CorrelationBarrier<BLOCKS, BYTES, COMMANDS>
{
pub const fn identity(&self) -> CorrelationSinkIdentity {
self.identity
}
pub fn result(&self) -> CompletionResult<FixedFailure> {
self.completion.result()
}
pub fn recycle(self) -> Result<(), CorrelationSubmitError> {
self.completion.recycle().map_err(Into::into)
}
pub fn cancel(self) -> Result<CompletionResult<FixedFailure>, CorrelationSubmitError> {
self.completion.cancel().map_err(Into::into)
}
#[doc(hidden)]
pub fn into_completion(self) -> FixedCompletion<BLOCKS, BYTES, COMMANDS> {
self.completion
}
}
impl VerifiedCorrelationSink {
pub const fn identity(self) -> CorrelationSinkIdentity {
self.identity
}
pub const fn health(self) -> CorrelationHealth {
self.health
}
}
impl<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize>
CorrelationSinkOwner<BLOCKS, BYTES, COMMANDS>
{
#[doc(hidden)]
pub(crate) fn from_fixed_sink(sink: FixedFileSink<BLOCKS, BYTES, COMMANDS>) -> Self {
Self { sink }
}
pub const fn identity(&self) -> CorrelationSinkIdentity {
CorrelationSinkIdentity {
schema_version: CORRELATION_SCHEMA_VERSION,
sink_identity: CORRELATION_SINK_IDENTITY,
}
}
pub fn verify(&self) -> VerifiedCorrelationSink {
let health = match self.sink.health() {
Ok(true) => CorrelationHealth::Accepting,
Ok(false) => CorrelationHealth::ShuttingDown,
Err(failure) => CorrelationHealth::Failed(failure),
};
VerifiedCorrelationSink {
identity: self.identity(),
health,
}
}
#[doc(hidden)]
pub fn begin_call<'a>(
&self,
memory: &RequestMemory,
started: CorrelationRecord<'a>,
) -> Result<ActiveCorrelationCall<'a, BLOCKS, BYTES, COMMANDS>, CorrelationSubmitError> {
if started.phase != CorrelationRecordPhase::Started {
return Err(CorrelationSubmitError::Encoding);
}
let start = self.sink.begin_record(memory)?;
let finish = self.sink.begin_record(memory)?;
encode(start, started)?;
Ok(ActiveCorrelationCall {
finish: Some(finish),
started,
clock: Instant::now(),
})
}
#[doc(hidden)]
pub fn reserve_alpha1_terminal<'a>(
&self,
memory: &RequestMemory,
identity: Alpha1CallIdentity<'a>,
) -> Result<Alpha1TerminalReservation<'a, BLOCKS, BYTES, COMMANDS>, CorrelationSubmitError>
{
let lease = self.sink.begin_record(memory)?;
Ok(Alpha1TerminalReservation {
lease,
identity,
sink_identity: self.identity(),
})
}
#[doc(hidden)]
pub fn try_flush(
&self,
) -> Result<CorrelationBarrier<BLOCKS, BYTES, COMMANDS>, CorrelationSubmitError> {
Ok(CorrelationBarrier {
identity: self.identity(),
completion: self.sink.try_flush()?,
})
}
#[doc(hidden)]
pub fn try_shutdown(
&self,
) -> Result<CorrelationBarrier<BLOCKS, BYTES, COMMANDS>, CorrelationSubmitError> {
Ok(CorrelationBarrier {
identity: self.identity(),
completion: self.sink.try_shutdown()?,
})
}
}
fn encode_alpha1_terminal<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize>(
lease: FixedEncodingLease<BLOCKS, BYTES, COMMANDS>,
record: Alpha1TerminalRecord<'_>,
) -> Result<(), CorrelationSubmitError> {
let fields = alpha1_terminal_fields(record);
lease
.encode_and_commit(
FileStream::Access,
JsonlRecord {
timestamp_unix_ms: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis(),
level: "info",
event: "framework.call.terminal",
trace_id: None,
span_id: None,
parent_span_id: None,
fields: &fields,
dropped_events: 0,
},
)
.map_err(Into::into)
}
fn alpha1_terminal_fields(record: Alpha1TerminalRecord<'_>) -> [Field<'_>; 9] {
[
Field {
name: "schema_version",
value: Scalar::Unsigned(u64::from(ALPHA1_TERMINAL_SCHEMA_VERSION)),
},
Field {
name: "request_id",
value: Scalar::String(record.request_id),
},
Field {
name: "call_id",
value: Scalar::String(record.call_id),
},
Field {
name: "business_unit",
value: Scalar::String(record.business_unit),
},
Field {
name: "function",
value: Scalar::String(record.function),
},
Field {
name: "terminal_kind",
value: Scalar::String(record.terminal_kind.value()),
},
Field {
name: "technical_code",
value: record
.technical_code
.map_or(Scalar::Null, |code| Scalar::String(code.value())),
},
Field {
name: "certainty",
value: Scalar::String(record.certainty.value()),
},
Field {
name: "resource_state",
value: Scalar::String(record.resource_state.value()),
},
]
}
#[doc(hidden)]
pub struct ActiveCorrelationCall<'a, const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize>
{
finish: Option<FixedEncodingLease<BLOCKS, BYTES, COMMANDS>>,
started: CorrelationRecord<'a>,
clock: Instant,
}
impl<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize>
ActiveCorrelationCall<'_, BLOCKS, BYTES, COMMANDS>
{
pub fn finish(mut self, outcome: CorrelationCallOutcome) -> Result<(), CorrelationSubmitError> {
self.commit_finish(outcome)
}
fn commit_finish(
&mut self,
outcome: CorrelationCallOutcome,
) -> Result<(), CorrelationSubmitError> {
let elapsed = u64::try_from(self.clock.elapsed().as_micros()).unwrap_or(u64::MAX);
let lease = self.finish.take().ok_or(CorrelationSubmitError::Encoding)?;
encode(lease, self.started.finished(outcome, elapsed))
}
}
impl<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize> Drop
for ActiveCorrelationCall<'_, BLOCKS, BYTES, COMMANDS>
{
fn drop(&mut self) {
if self.finish.is_some() {
let _ = self.commit_finish(CorrelationCallOutcome::Abandoned);
}
}
}
fn encode<const BLOCKS: usize, const BYTES: usize, const COMMANDS: usize>(
lease: FixedEncodingLease<BLOCKS, BYTES, COMMANDS>,
record: CorrelationRecord<'_>,
) -> Result<(), CorrelationSubmitError> {
let schema = u64::from(CORRELATION_SCHEMA_VERSION);
let fields = [
Field {
name: "schema_version",
value: Scalar::Unsigned(schema),
},
Field {
name: "call_kind",
value: Scalar::String(record.kind.value()),
},
Field {
name: "phase",
value: Scalar::String(match record.phase {
CorrelationRecordPhase::Started => "started",
CorrelationRecordPhase::Finished => "finished",
}),
},
Field {
name: "route",
value: Scalar::String(record.route),
},
Field {
name: "service",
value: Scalar::String(record.service),
},
Field {
name: "operation",
value: Scalar::String(record.operation),
},
Field {
name: "outcome",
value: record
.outcome
.map_or(Scalar::Null, |value| Scalar::String(value.value())),
},
Field {
name: "elapsed_us",
value: record.elapsed_us.map_or(Scalar::Null, Scalar::Unsigned),
},
];
lease
.encode_and_commit(
record.kind.stream(),
JsonlRecord {
timestamp_unix_ms: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis(),
level: "info",
event: match record.phase {
CorrelationRecordPhase::Started => "framework.call.started",
CorrelationRecordPhase::Finished => "framework.call.finished",
},
trace_id: Some(record.trace_id),
span_id: Some(record.span_id),
parent_span_id: record.parent_span_id,
fields: &fields,
dropped_events: 0,
},
)
.map_err(Into::into)
}
#[cfg(test)]
mod alpha1_tests {
use super::*;
fn completed(request_id: &str) -> Result<Alpha1TerminalRecord<'_>, Alpha1TerminalRecordError> {
Alpha1TerminalRecord::new(
request_id,
"call-1",
"payments",
"authorize",
Alpha1TerminalKind::Completed,
None,
Alpha1ExecutionCertainty::Executed,
Alpha1ResourceState::Zero,
)
}
#[test]
fn alpha1_safe_projection_accepts_only_bounded_non_control_identifiers() {
assert!(completed("request-1").is_ok());
assert_eq!(completed(""), Err(Alpha1TerminalRecordError::EmptyField));
assert_eq!(
completed("request\nforged"),
Err(Alpha1TerminalRecordError::ControlCharacter)
);
let oversized = "x".repeat(MAX_ALPHA1_ID_BYTES + 1);
assert_eq!(
completed(&oversized),
Err(Alpha1TerminalRecordError::FieldTooLong)
);
}
#[test]
fn alpha1_completed_and_failure_shapes_are_unambiguous() {
assert_eq!(
Alpha1TerminalRecord::new(
"request-1",
"call-1",
"payments",
"authorize",
Alpha1TerminalKind::Completed,
Some(Alpha1TechnicalCode::InternalFailure),
Alpha1ExecutionCertainty::Executed,
Alpha1ResourceState::Unknown,
),
Err(Alpha1TerminalRecordError::InvalidCompletedOutcome)
);
assert_eq!(
Alpha1TerminalRecord::new(
"request-1",
"call-1",
"payments",
"authorize",
Alpha1TerminalKind::TechnicalFailure,
None,
Alpha1ExecutionCertainty::MayHaveExecuted,
Alpha1ResourceState::Unknown,
),
Err(Alpha1TerminalRecordError::MissingTechnicalCode)
);
assert!(
Alpha1TerminalRecord::new(
"request-1",
"call-1",
"payments",
"authorize",
Alpha1TerminalKind::TechnicalFailure,
Some(Alpha1TechnicalCode::DeadlineExceeded),
Alpha1ExecutionCertainty::MayHaveExecuted,
Alpha1ResourceState::NonZero,
)
.is_ok()
);
}
#[test]
fn alpha1_encoded_field_set_is_exact_and_contains_no_sensitive_body_fields() {
let record = completed("request-1").unwrap();
let fields = alpha1_terminal_fields(record);
let names = fields.map(|field| field.name);
assert_eq!(
names,
[
"schema_version",
"request_id",
"call_id",
"business_unit",
"function",
"terminal_kind",
"technical_code",
"certainty",
"resource_state",
]
);
assert!(!names.contains(&"user_id"));
assert!(!names.contains(&"payload"));
assert!(!names.contains(&"result"));
}
}