use std::collections::BTreeMap; use std::path::Path; use serde::Deserialize; use serde::Serialize; use crate::Result; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub(crate) struct FlakeLock { pub(crate) root: String, pub(crate) version: u8, pub(crate) nodes: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub(crate) struct FlakeLockNode { #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) locked: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) original: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) inputs: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) flake: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub(crate) struct FlakeLockNodeLocked { #[serde(rename = "lastModified")] pub(crate) last_modified: i64, #[serde(rename = "narHash")] pub(crate) nar_hash: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) url: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) owner: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) repo: Option, #[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")] pub(crate) git_ref: Option, pub(crate) rev: String, #[serde(rename = "revCount", default, skip_serializing_if = "Option::is_none")] pub(crate) rev_count: Option, #[serde(rename = "type")] pub(crate) repo_type: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "lowercase", deny_unknown_fields)] pub(crate) enum FlakeLockNodeOriginal { Github { owner: String, repo: String, #[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")] git_ref: Option, #[serde(default, skip_serializing_if = "Option::is_none")] rev: Option, }, Git { url: String, }, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] pub(crate) enum FlakeLockInputValue { String(String), List(Vec), } pub(crate) async fn parse_flake_lock(flake_path: F) -> Result where F: AsRef, { let lock_file_path = flake_path.as_ref().join("flake.lock"); let contents = tokio::fs::read_to_string(&lock_file_path).await?; let parsed_flake_lock = serde_json::from_str(&contents)?; Ok(parsed_flake_lock) } impl FlakeLock { pub(crate) fn get_root_node(&self) -> Result<&FlakeLockNode> { let root_node = self.nodes.get(&self.root).ok_or("Root node not found.")?; Ok(root_node) } pub(crate) fn get_inputs<'lock>( &'lock self, source_node: &'lock FlakeLockNode, ) -> Result> { let direct_inputs = match &source_node.inputs { Some(inputs) => inputs, None => { return Ok(BTreeMap::new()); } }; let mut inputs = BTreeMap::new(); for (input_name, input_id_or_path) in direct_inputs.iter() { inputs.insert(input_name, self.resolve_path(input_id_or_path)?); } Ok(inputs) } fn resolve_path<'lock>( &'lock self, input_id_or_path: &'lock FlakeLockInputValue, ) -> Result<&'lock FlakeLockNode> { match input_id_or_path { FlakeLockInputValue::String(input_id) => { // If the input value is a plain string, then it refers to a node. let node = self.nodes.get(input_id).ok_or("Input not found.")?; Ok(node) } FlakeLockInputValue::List(items) => { // If the input value is a list, then it refers to a path to an input. let mut current_node = self.get_root_node()?; for step in items { let child_input_id_or_path = current_node .inputs .as_ref() .ok_or("Node without inputs while walking input tree.")? .get(step) .ok_or("Step not found in inputs while walking input tree")?; current_node = self.resolve_path(child_input_id_or_path)?; } Ok(current_node) } } } }