use serde_json::{Map, Value};
use uuid::Uuid;
use crate::config::OpenLineageConfig;
use crate::facets::{BaseFacet, ParentJob, ParentRun, ParentRunFacet};
#[derive(Debug, Default, Clone)]
pub struct LineageContext {
pub run_id: Option<Uuid>,
pub job_namespace: Option<String>,
pub job_name: Option<String>,
pub parent_run: Option<ParentRunFacet>,
pub run_facets: Map<String, Value>,
pub job_facets: Map<String, Value>,
pub sql: Option<String>,
}
impl LineageContext {
pub fn from_env(config: &OpenLineageConfig) -> Self {
let parent_run = parent_from_env(config);
LineageContext {
parent_run,
..Default::default()
}
}
}
fn parent_from_env(config: &OpenLineageConfig) -> Option<ParentRunFacet> {
let (namespace, name, run_id) = if let Ok(parent_id) = std::env::var("OPENLINEAGE_PARENT_ID") {
let parts: Vec<&str> = parent_id.splitn(3, '/').collect();
match parts.as_slice() {
[ns, n, rid] => (ns.to_string(), n.to_string(), rid.to_string()),
_ => return None,
}
} else {
let ns = std::env::var("OPENLINEAGE_PARENT_JOB_NAMESPACE").ok()?;
let n = std::env::var("OPENLINEAGE_PARENT_JOB_NAME").ok()?;
let rid = std::env::var("OPENLINEAGE_PARENT_RUN_ID").ok()?;
(ns, n, rid)
};
Some(ParentRunFacet {
base: BaseFacet::new(&config.producer, "1-1-0/ParentRunFacet.json"),
run: ParentRun { run_id },
job: ParentJob { namespace, name },
root: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
const PARENT_VARS: [&str; 4] = [
"OPENLINEAGE_PARENT_ID",
"OPENLINEAGE_PARENT_JOB_NAMESPACE",
"OPENLINEAGE_PARENT_JOB_NAME",
"OPENLINEAGE_PARENT_RUN_ID",
];
fn clear_parent_vars() {
for v in PARENT_VARS {
unsafe { std::env::remove_var(v) };
}
}
#[test]
fn parent_from_env_covers_all_forms() {
let config = OpenLineageConfig::default();
clear_parent_vars();
assert!(parent_from_env(&config).is_none(), "unset env yields None");
clear_parent_vars();
unsafe { std::env::set_var("OPENLINEAGE_PARENT_ID", "airflow/dag.task/run-7") };
let p = parent_from_env(&config).expect("slash form parses");
assert_eq!(p.job.namespace, "airflow");
assert_eq!(p.job.name, "dag.task");
assert_eq!(p.run.run_id, "run-7");
clear_parent_vars();
unsafe { std::env::set_var("OPENLINEAGE_PARENT_ID", "only-one-part") };
assert!(
parent_from_env(&config).is_none(),
"a non-triple PARENT_ID is rejected"
);
clear_parent_vars();
unsafe {
std::env::set_var("OPENLINEAGE_PARENT_JOB_NAMESPACE", "dagster");
std::env::set_var("OPENLINEAGE_PARENT_JOB_NAME", "asset.build");
std::env::set_var("OPENLINEAGE_PARENT_RUN_ID", "run-9");
}
let p = parent_from_env(&config).expect("discrete vars parse");
assert_eq!(p.job.namespace, "dagster");
assert_eq!(p.job.name, "asset.build");
assert_eq!(p.run.run_id, "run-9");
clear_parent_vars();
unsafe {
std::env::set_var("OPENLINEAGE_PARENT_JOB_NAMESPACE", "dagster");
std::env::set_var("OPENLINEAGE_PARENT_JOB_NAME", "asset.build");
}
assert!(
parent_from_env(&config).is_none(),
"missing run id falls through to None"
);
clear_parent_vars();
unsafe { std::env::set_var("OPENLINEAGE_PARENT_ID", "ns/job/run") };
let ctx = LineageContext::from_env(&config);
assert!(ctx.parent_run.is_some());
assert!(ctx.run_id.is_none() && ctx.sql.is_none());
clear_parent_vars();
}
}