mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-15 13:22:19 +01:00
fix(nixgen): make the generated output order the same on every run
The generator kept its definitions, reference counts and type bindings in HashMap and HashSet, whose iteration order changes from run to run. The topological sort walked the keys in that order, so which shared types were bound before their dependents, and so how some types were written out, changed between runs. Running the same schema eight times gave eight different files. Use BTreeMap and BTreeSet in generator.rs and prepare.rs so every walk is in sorted key order. The final let block was already sorted by identifier and is unchanged. Verified: cargo test passes (48 tests). Running the release binary on the KDE schema.json eight times gave one identical output; the same eight runs before the change gave eight different outputs.
This commit is contained in:
1 parent
a26728ab19
commit
e28a8b7956
2 files changed
+21
-21
No files matched your search
+18
-18
@@ -22,8 +22,8 @@ use crate::prepare::resolve_ref_name;
|
||||
use crate::types::NixOption;
|
||||
use crate::types::NixType;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// Configuration options for the generator
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -56,13 +56,13 @@ pub enum GeneratorError {
|
||||
|
||||
/// The main Nix code generator
|
||||
pub struct Generator {
|
||||
definitions: HashMap<String, Value>,
|
||||
definitions: BTreeMap<String, Value>,
|
||||
/// Cache for resolved type definitions to avoid infinite recursion
|
||||
resolving: Vec<String>,
|
||||
/// Count of how many times each $def is referenced
|
||||
ref_counts: HashMap<String, usize>,
|
||||
ref_counts: BTreeMap<String, usize>,
|
||||
/// Pre-generated types for frequently used definitions
|
||||
type_bindings: HashMap<String, NixType>,
|
||||
type_bindings: BTreeMap<String, NixType>,
|
||||
/// Whether we're in the second pass (using type refs)
|
||||
use_type_refs: bool,
|
||||
}
|
||||
@@ -73,8 +73,8 @@ impl Generator {
|
||||
Self {
|
||||
definitions: collect_definitions(schema),
|
||||
resolving: Vec::new(),
|
||||
ref_counts: HashMap::new(),
|
||||
type_bindings: HashMap::new(),
|
||||
ref_counts: BTreeMap::new(),
|
||||
type_bindings: BTreeMap::new(),
|
||||
use_type_refs: false,
|
||||
}
|
||||
}
|
||||
@@ -149,9 +149,9 @@ impl Generator {
|
||||
}
|
||||
|
||||
/// Build a dependency graph of $defs
|
||||
/// Returns HashMap<def_name, Vec<dependencies>>
|
||||
fn build_dependency_graph(&self) -> HashMap<String, Vec<String>> {
|
||||
let mut graph: HashMap<String, Vec<String>> = HashMap::new();
|
||||
/// Returns BTreeMap<def_name, Vec<dependencies>>
|
||||
fn build_dependency_graph(&self) -> BTreeMap<String, Vec<String>> {
|
||||
let mut graph: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
||||
|
||||
for (name, def) in &self.definitions {
|
||||
let deps = Self::collect_refs_in_schema(def);
|
||||
@@ -233,10 +233,10 @@ impl Generator {
|
||||
}
|
||||
|
||||
/// Topologically sort definitions so dependencies come before dependents
|
||||
fn topological_sort(graph: &HashMap<String, Vec<String>>) -> Vec<String> {
|
||||
fn topological_sort(graph: &BTreeMap<String, Vec<String>>) -> Vec<String> {
|
||||
let mut result = Vec::new();
|
||||
let mut visited = HashSet::new();
|
||||
let mut temp_visited = HashSet::new(); // For cycle detection
|
||||
let mut visited = BTreeSet::new();
|
||||
let mut temp_visited = BTreeSet::new(); // For cycle detection
|
||||
|
||||
for name in graph.keys() {
|
||||
Self::topo_visit(name, graph, &mut visited, &mut temp_visited, &mut result);
|
||||
@@ -248,9 +248,9 @@ impl Generator {
|
||||
/// DFS visit for topological sort
|
||||
fn topo_visit(
|
||||
name: &str,
|
||||
graph: &HashMap<String, Vec<String>>,
|
||||
visited: &mut HashSet<String>,
|
||||
temp_visited: &mut HashSet<String>,
|
||||
graph: &BTreeMap<String, Vec<String>>,
|
||||
visited: &mut BTreeSet<String>,
|
||||
temp_visited: &mut BTreeSet<String>,
|
||||
result: &mut Vec<String>,
|
||||
) {
|
||||
if visited.contains(name) {
|
||||
@@ -286,7 +286,7 @@ impl Generator {
|
||||
let sorted_defs = Self::topological_sort(&graph);
|
||||
|
||||
// Only generate bindings for refs used more than once
|
||||
let frequent_refs: HashSet<String> = self
|
||||
let frequent_refs: BTreeSet<String> = self
|
||||
.ref_counts
|
||||
.iter()
|
||||
.filter(|&(_, &count)| count > 1)
|
||||
@@ -480,7 +480,7 @@ impl Generator {
|
||||
|
||||
// Collect all other properties from all variants (excluding the discriminator)
|
||||
// Track property types to merge them if they differ across variants
|
||||
let mut prop_types: HashMap<String, Vec<NixType>> = HashMap::new();
|
||||
let mut prop_types: BTreeMap<String, Vec<NixType>> = BTreeMap::new();
|
||||
|
||||
for (_, variant_schema) in &variants {
|
||||
if let Some(props) = variant_schema.get("properties").and_then(|p| p.as_object()) {
|
||||
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
//! Schema preparation utilities for normalizing JSON Schema before conversion
|
||||
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Result type for mixed enum + object union detection
|
||||
/// Contains (enum_variants, object_variants) where object_variants are (tag_name, inner_schema) tuples
|
||||
pub type MixedUnionResult = (Vec<String>, Vec<(String, Value)>);
|
||||
|
||||
/// Extract all definitions from the schema's $defs section
|
||||
pub fn collect_definitions(schema: &Value) -> HashMap<String, Value> {
|
||||
let mut defs = HashMap::new();
|
||||
pub fn collect_definitions(schema: &Value) -> BTreeMap<String, Value> {
|
||||
let mut defs = BTreeMap::new();
|
||||
|
||||
if let Some(definitions) = schema.get("$defs").and_then(|d| d.as_object()) {
|
||||
for (name, def) in definitions {
|
||||
|
||||
Reference in new issue
Block a user