pub mod artifact;
pub mod buffer;
pub mod builder;
pub mod event;
pub mod failure;
pub mod log_sink;
pub mod shell;
pub mod skip;
pub mod span;
pub mod utf8_stream;
use std::collections::HashMap;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
pub use artifact::ArtifactEntry;
pub use buffer::BufferEvent;
pub use buffer::BufferEventKind;
pub use builder::StructuredLogBuilder;
pub use event::CancelReasonRecord;
pub use event::Event;
pub use event::EventKind;
pub use event::EventSeq;
pub use event::MultiMatchPattern;
pub use failure::CancellationRecord;
pub use failure::FailureRecord;
pub use failure::StackFrame;
pub use shell::ShellRecord;
pub use skip::SkipRecord;
pub use span::FnCallKind;
pub use span::MarkerEvalDecision;
pub use span::MarkerEvalDetail;
pub use span::MarkerEvalKind;
pub use span::MarkerEvalModifier;
pub use span::MatchKind;
pub use span::Span;
pub use span::SpanId;
pub use span::SpanKind;
pub use utf8_stream::Utf8Stream;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
pub struct SourceLocation {
pub file: String,
pub line: usize,
pub start: usize,
pub end: usize,
}
impl std::fmt::Display for SourceLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.file, self.line)
}
}
pub const SCHEMA_VERSION: u32 = 2;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
pub struct StructuredLog {
pub schema_version: u32,
pub info: TestInfo,
pub outcome: TestOutcome,
pub env: EnvInfo,
pub shells: HashMap<String, ShellRecord>,
#[ts(as = "HashMap<String, Span>")]
pub spans: HashMap<SpanId, Span>,
pub events: Vec<Event>,
pub buffer_events: Vec<BufferEvent>,
pub sources: HashMap<String, String>,
pub artifacts: Vec<ArtifactEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
pub struct TestInfo {
pub name: String,
pub path: String,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum TestOutcome {
Pass,
Fail(FailureRecord),
Cancelled(CancellationRecord),
Skip(SkipRecord),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum EnvSourceRecord {
Base,
DotEnv { path: String },
ReluxInternal,
EffectOverlay { mnemonic: String },
}
impl From<&relux_core::pure::LayeredEnvSource> for EnvSourceRecord {
fn from(s: &relux_core::pure::LayeredEnvSource) -> Self {
use relux_core::pure::LayeredEnvSource as S;
match s {
S::Base => Self::Base,
S::DotEnv(p) => Self::DotEnv {
path: p.to_string_lossy().into_owned(),
},
S::ReluxInternal => Self::ReluxInternal,
S::EffectOverlay(m) => Self::EffectOverlay {
mnemonic: m.clone(),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
pub struct EnvValue {
pub key: String,
pub value: String,
pub source: EnvSourceRecord,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
pub struct EnvInfo {
pub bootstrap: Vec<EnvValue>,
}
pub(crate) mod ts_duration_ms {
use std::time::Duration;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serializer;
pub fn serialize<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
s.serialize_f64(d.as_secs_f64() * 1000.0)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Duration, D::Error> {
let ms = f64::deserialize(d)?;
Ok(Duration::from_secs_f64(ms / 1000.0))
}
}
pub(crate) mod ts_duration_ms_opt {
use std::time::Duration;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serializer;
pub fn serialize<S: Serializer>(d: &Option<Duration>, s: S) -> Result<S::Ok, S::Error> {
match d {
Some(d) => s.serialize_some(&(d.as_secs_f64() * 1000.0)),
None => s.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Duration>, D::Error> {
let opt = Option::<f64>::deserialize(d)?;
Ok(opt.map(|ms| Duration::from_secs_f64(ms / 1000.0)))
}
}
#[cfg(test)]
mod env_provenance_tests {
use super::*;
#[test]
fn env_source_record_serialises_dot_env() {
let r = EnvSourceRecord::DotEnv {
path: "/p/.env".into(),
};
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v, serde_json::json!({"kind": "dot-env", "path": "/p/.env"}));
}
#[test]
fn env_source_record_variants_serialise() {
assert_eq!(
serde_json::to_value(EnvSourceRecord::Base).unwrap(),
serde_json::json!({"kind": "base"})
);
assert_eq!(
serde_json::to_value(EnvSourceRecord::ReluxInternal).unwrap(),
serde_json::json!({"kind": "relux-internal"})
);
assert_eq!(
serde_json::to_value(EnvSourceRecord::EffectOverlay {
mnemonic: "brave-yak-0001".into()
})
.unwrap(),
serde_json::json!({"kind": "effect-overlay", "mnemonic": "brave-yak-0001"})
);
}
#[test]
fn env_source_record_from_core() {
use relux_core::pure::LayeredEnvSource;
let r: EnvSourceRecord = (&LayeredEnvSource::ReluxInternal).into();
assert_eq!(r, EnvSourceRecord::ReluxInternal);
let r: EnvSourceRecord = (&LayeredEnvSource::DotEnv("/a/.env".into())).into();
assert_eq!(
r,
EnvSourceRecord::DotEnv {
path: "/a/.env".into()
}
);
let r: EnvSourceRecord = (&LayeredEnvSource::EffectOverlay("m".into())).into();
assert_eq!(
r,
EnvSourceRecord::EffectOverlay {
mnemonic: "m".into()
}
);
}
#[test]
fn env_value_serialises() {
let ev = EnvValue {
key: "PORT".into(),
value: "5432".into(),
source: EnvSourceRecord::Base,
};
let v = serde_json::to_value(&ev).unwrap();
assert_eq!(v["key"], "PORT");
assert_eq!(v["value"], "5432");
assert_eq!(v["source"]["kind"], "base");
}
#[test]
fn schema_version_is_two() {
assert_eq!(SCHEMA_VERSION, 2);
}
}