Files
nix_builder/src/nix_util/lock_parser.rs
2026-07-28 10:19:23 -04:00

151 lines
4.6 KiB
Rust

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<String, FlakeLockNode>,
}
#[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<FlakeLockNodeLocked>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) original: Option<FlakeLockNodeOriginal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) inputs: Option<BTreeMap<String, FlakeLockInputValue>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) flake: Option<bool>,
}
#[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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) owner: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) repo: Option<String>,
#[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
pub(crate) git_ref: Option<String>,
pub(crate) rev: String,
#[serde(rename = "revCount", default, skip_serializing_if = "Option::is_none")]
pub(crate) rev_count: Option<i64>,
#[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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
rev: Option<String>,
},
Git {
url: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
pub(crate) enum FlakeLockInputValue {
String(String),
List(Vec<String>),
}
pub(crate) async fn parse_flake_lock<F>(flake_path: F) -> Result<FlakeLock>
where
F: AsRef<Path>,
{
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<BTreeMap<&'lock String, &'lock FlakeLockNode>> {
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)
}
}
}
}