use std::io;
pub mod ascii;
pub mod dot;
pub mod html;
pub mod impact;
pub mod json;
pub mod layout;
pub mod list;
pub mod mermaid;
pub mod plain;
pub mod summary;
pub mod svg;
pub(crate) fn handle_stdout_result(result: io::Result<()>) {
if let Err(e) = result
&& e.kind() != io::ErrorKind::BrokenPipe
{
eprintln!("error writing output: {}", e);
}
}
pub(crate) fn capitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_uppercase().to_string() + chars.as_str(),
}
}
pub(crate) const NO_DIRECTORY_LABEL: &str = "(other)";
pub(crate) fn directory_label(node: &crate::graph::types::NodeData) -> String {
match &node.file_path {
Some(path) => path
.parent()
.filter(|p| !p.as_os_str().is_empty())
.map(|p| {
p.components()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.collect::<Vec<String>>()
.join("/")
})
.unwrap_or_else(|| NO_DIRECTORY_LABEL.to_string()),
None => NO_DIRECTORY_LABEL.to_string(),
}
}
pub(crate) fn sanitize_id(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '_' {
c
} else {
'_'
}
})
.collect()
}
pub(crate) fn serde_io_error(e: serde_json::Error) -> io::Error {
match e.io_error_kind() {
Some(kind) => io::Error::new(kind, e),
None => io::Error::other(e),
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
use crate::graph::types::*;
pub fn make_node(unique_id: &str, label: &str, node_type: NodeType) -> NodeData {
NodeData {
unique_id: unique_id.into(),
label: label.into(),
node_type,
file_path: None,
description: None,
materialization: None,
tags: vec![],
columns: vec![],
exposure: None,
}
}
pub fn make_node_with_columns(
unique_id: &str,
label: &str,
node_type: NodeType,
columns: &[&str],
) -> NodeData {
NodeData {
unique_id: unique_id.into(),
label: label.into(),
node_type,
file_path: None,
description: None,
materialization: None,
tags: vec![],
columns: columns.iter().map(|s| s.to_string()).collect(),
exposure: None,
}
}
pub fn make_node_with_path(
unique_id: &str,
label: &str,
node_type: NodeType,
path: &str,
) -> NodeData {
NodeData {
unique_id: unique_id.into(),
label: label.into(),
node_type,
file_path: Some(path.into()),
description: None,
materialization: None,
tags: vec![],
columns: vec![],
exposure: None,
}
}
pub fn make_sample_lineage_graph() -> LineageGraph {
let mut graph = LineageGraph::new();
let src = graph.add_node(make_node(
"source.raw.orders",
"raw.orders",
NodeType::Source,
));
let stg = graph.add_node(make_node("model.stg_orders", "stg_orders", NodeType::Model));
let mart = graph.add_node(make_node("model.orders", "orders", NodeType::Model));
let t = graph.add_node(make_node(
"test.orders_positive",
"orders_positive",
NodeType::Test,
));
let exp = graph.add_node(make_node(
"exposure.dashboard",
"dashboard",
NodeType::Exposure,
));
graph.add_edge(src, stg, EdgeData::direct(EdgeType::Source));
graph.add_edge(stg, mart, EdgeData::direct(EdgeType::Ref));
graph.add_edge(mart, t, EdgeData::direct(EdgeType::Test));
graph.add_edge(mart, exp, EdgeData::direct(EdgeType::Exposure));
graph
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_id_directory_with_hyphens() {
assert_eq!(sanitize_id("models/my-project"), "models_my_project");
}
#[test]
fn test_sanitize_id_parentheses() {
assert_eq!(sanitize_id("(other)"), "_other_");
}
#[test]
fn test_sanitize_id_empty() {
assert_eq!(sanitize_id(""), "");
}
#[test]
fn test_sanitize_id_already_valid() {
assert_eq!(sanitize_id("models_staging"), "models_staging");
}
#[test]
fn test_sanitize_id_non_ascii() {
assert_eq!(sanitize_id("モデル/日本語"), "_______");
}
#[test]
fn test_capitalize() {
assert_eq!(capitalize("model"), "Model");
assert_eq!(capitalize(""), "");
}
#[test]
fn test_directory_label_with_path() {
let node = test_helpers::make_node_with_path(
"model.a",
"a",
crate::graph::types::NodeType::Model,
"models/staging/a.sql",
);
assert_eq!(directory_label(&node), "models/staging");
}
#[test]
fn test_directory_label_without_path() {
let node =
test_helpers::make_node("exposure.e", "e", crate::graph::types::NodeType::Exposure);
assert_eq!(directory_label(&node), NO_DIRECTORY_LABEL);
}
#[test]
fn test_directory_label_file_at_root() {
let node = test_helpers::make_node_with_path(
"model.a",
"a",
crate::graph::types::NodeType::Model,
"a.sql",
);
assert_eq!(directory_label(&node), NO_DIRECTORY_LABEL);
}
}