openlineage_client/
context.rs1use serde_json::{Map, Value};
11use uuid::Uuid;
12
13use crate::config::OpenLineageConfig;
14use crate::facets::{BaseFacet, ParentJob, ParentRun, ParentRunFacet};
15
16#[derive(Debug, Default, Clone)]
21pub struct LineageContext {
22 pub run_id: Option<Uuid>,
24 pub job_namespace: Option<String>,
26 pub job_name: Option<String>,
28 pub parent_run: Option<ParentRunFacet>,
30 pub run_facets: Map<String, Value>,
32 pub job_facets: Map<String, Value>,
34 pub sql: Option<String>,
38}
39
40impl LineageContext {
41 pub fn from_env(config: &OpenLineageConfig) -> Self {
48 let parent_run = parent_from_env(config);
49 LineageContext {
50 parent_run,
51 ..Default::default()
52 }
53 }
54}
55
56fn parent_from_env(config: &OpenLineageConfig) -> Option<ParentRunFacet> {
57 let (namespace, name, run_id) = if let Ok(parent_id) = std::env::var("OPENLINEAGE_PARENT_ID") {
58 let parts: Vec<&str> = parent_id.splitn(3, '/').collect();
60 match parts.as_slice() {
61 [ns, n, rid] => (ns.to_string(), n.to_string(), rid.to_string()),
62 _ => return None,
63 }
64 } else {
65 let ns = std::env::var("OPENLINEAGE_PARENT_JOB_NAMESPACE").ok()?;
66 let n = std::env::var("OPENLINEAGE_PARENT_JOB_NAME").ok()?;
67 let rid = std::env::var("OPENLINEAGE_PARENT_RUN_ID").ok()?;
68 (ns, n, rid)
69 };
70
71 Some(ParentRunFacet {
72 base: BaseFacet::new(&config.producer, "1-1-0/ParentRunFacet.json"),
73 run: ParentRun { run_id },
74 job: ParentJob { namespace, name },
75 root: None,
76 })
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 const PARENT_VARS: [&str; 4] = [
84 "OPENLINEAGE_PARENT_ID",
85 "OPENLINEAGE_PARENT_JOB_NAMESPACE",
86 "OPENLINEAGE_PARENT_JOB_NAME",
87 "OPENLINEAGE_PARENT_RUN_ID",
88 ];
89
90 fn clear_parent_vars() {
91 for v in PARENT_VARS {
92 unsafe { std::env::remove_var(v) };
93 }
94 }
95
96 #[test]
100 fn parent_from_env_covers_all_forms() {
101 let config = OpenLineageConfig::default();
102
103 clear_parent_vars();
105 assert!(parent_from_env(&config).is_none(), "unset env yields None");
106
107 clear_parent_vars();
109 unsafe { std::env::set_var("OPENLINEAGE_PARENT_ID", "airflow/dag.task/run-7") };
110 let p = parent_from_env(&config).expect("slash form parses");
111 assert_eq!(p.job.namespace, "airflow");
112 assert_eq!(p.job.name, "dag.task");
113 assert_eq!(p.run.run_id, "run-7");
114
115 clear_parent_vars();
117 unsafe { std::env::set_var("OPENLINEAGE_PARENT_ID", "only-one-part") };
118 assert!(
119 parent_from_env(&config).is_none(),
120 "a non-triple PARENT_ID is rejected"
121 );
122
123 clear_parent_vars();
125 unsafe {
126 std::env::set_var("OPENLINEAGE_PARENT_JOB_NAMESPACE", "dagster");
127 std::env::set_var("OPENLINEAGE_PARENT_JOB_NAME", "asset.build");
128 std::env::set_var("OPENLINEAGE_PARENT_RUN_ID", "run-9");
129 }
130 let p = parent_from_env(&config).expect("discrete vars parse");
131 assert_eq!(p.job.namespace, "dagster");
132 assert_eq!(p.job.name, "asset.build");
133 assert_eq!(p.run.run_id, "run-9");
134
135 clear_parent_vars();
137 unsafe {
138 std::env::set_var("OPENLINEAGE_PARENT_JOB_NAMESPACE", "dagster");
139 std::env::set_var("OPENLINEAGE_PARENT_JOB_NAME", "asset.build");
140 }
141 assert!(
142 parent_from_env(&config).is_none(),
143 "missing run id falls through to None"
144 );
145
146 clear_parent_vars();
148 unsafe { std::env::set_var("OPENLINEAGE_PARENT_ID", "ns/job/run") };
149 let ctx = LineageContext::from_env(&config);
150 assert!(ctx.parent_run.is_some());
151 assert!(ctx.run_id.is_none() && ctx.sql.is_none());
152
153 clear_parent_vars();
154 }
155}