use crate::plm::uses::revision_key_in;
use serde_json::Value;
const MAX_DEPTH: usize = 64;
pub fn is_superseded(lifecycle: &str) -> bool {
matches!(lifecycle, "superseded" | "obsolete")
}
pub fn pinned_keys(document: &Value) -> Vec<String> {
fn walk(document: &Value, depth: usize, out: &mut Vec<String>) {
let Some(entries) = document["partsLibrary"].as_object() else { return };
if depth > MAX_DEPTH {
return;
}
for entry in entries.values() {
if let Some(key) = entry["sourceKey"].as_str().and_then(revision_key_in) {
if !out.contains(&key) {
out.push(key);
}
}
walk(&entry["document"], depth + 1, out);
}
}
let mut out = Vec::new();
walk(document, 0, &mut out);
out
}
#[derive(Debug, Clone, PartialEq)]
pub struct Placement {
pub path: String,
pub owners: Vec<String>,
pub part: String,
}
impl Placement {
pub fn name(&self) -> String {
self.owners.iter().chain(std::iter::once(&self.part)).cloned().collect::<Vec<_>>().join("/")
}
}
pub fn occurrences_of(document: &Value, key: &str) -> Vec<Placement> {
fn walk(document: &Value, key: &str, prefix: &str, owners: &[String], depth: usize, out: &mut Vec<Placement>) {
if depth > MAX_DEPTH {
return;
}
let library = &document["partsLibrary"];
for feature in document["features"].as_array().into_iter().flatten() {
if !matches!(feature["type"].as_str(), Some("ACOMP" | "ASSEMBLY COMPONENT")) {
continue;
}
let (Some(id), Some(part)) = (feature["inputParams"]["id"].as_str(), feature["inputParams"]["partName"].as_str()) else {
continue;
};
let path = if prefix.is_empty() { id.to_string() } else { format!("{prefix}:{id}") };
let entry = &library[part];
if entry["sourceKey"].as_str().and_then(revision_key_in).as_deref() == Some(key) {
out.push(Placement { path, owners: owners.to_vec(), part: part.to_string() });
} else if entry["document"].is_object() {
let mut chain = owners.to_vec();
chain.push(part.to_string());
walk(&entry["document"], key, &path, &chain, depth + 1, out);
}
}
}
let mut out = Vec::new();
walk(document, key, "", &[], 0, &mut out);
out
}
pub fn current_release(part: &Value) -> Option<(String, String)> {
part["revision_views"]
.as_array()?
.iter()
.filter(|r| r["lifecycle"] == "released")
.next_back()
.and_then(|r| Some((r["id"].as_str()?.to_string(), r["label"].as_str()?.to_string())))
}