serde_nixos/lib.rs
1//! # serde-nixos
2//!
3//! Generate NixOS type definitions from your Rust structures.
4//!
5//! This crate provides a derive macro that automatically generates NixOS module
6//! definitions from your Rust configuration structures, ensuring type safety
7//! across both Rust and Nix.
8//!
9//! ## Quick Start
10//!
11//! ```rust
12//! use serde::{Serialize, Deserialize};
13//! use serde_nixos::NixosType;
14//!
15//! #[derive(Serialize, Deserialize, NixosType)]
16//! struct ServerConfig {
17//! #[nixos(description = "The server port to listen on")]
18//! port: u16,
19//!
20//! #[nixos(default = "\"localhost\"", description = "The hostname to bind to")]
21//! host: String,
22//!
23//! #[nixos(description = "Maximum number of concurrent connections")]
24//! max_connections: Option<u32>,
25//!
26//! #[nixos(description = "Database configuration")]
27//! database: DatabaseConfig,
28//! }
29//!
30//! #[derive(Serialize, Deserialize, NixosType)]
31//! struct DatabaseConfig {
32//! #[nixos(description = "Database connection string")]
33//! url: String,
34//!
35//! #[nixos(default = "5", description = "Connection pool size")]
36//! pool_size: u32,
37//! }
38//! ```
39//!
40//! Then generate the NixOS module:
41//!
42//! ```rust
43//! # use serde::{Serialize, Deserialize};
44//! # use serde_nixos::NixosType;
45//! #
46//! # #[derive(Serialize, Deserialize, NixosType)]
47//! # struct ServerConfig {
48//! # enable: bool,
49//! # }
50//! #
51//! let nixos_module = ServerConfig::nixos_type_definition();
52//! println!("{}", nixos_module);
53//! ```
54//!
55//! This will output a NixOS module definition that can be used in your NixOS configuration.
56//!
57//! ## Multi-Type Module Generation
58//!
59//! For generating `.nix` files that compose multiple types with proper
60//! `let` bindings and cross-references, use [`NixosModuleGenerator`]:
61//!
62//! ```rust
63//! use serde::{Serialize, Deserialize};
64//! use serde_nixos::{NixosType, type_registration};
65//! use serde_nixos::generator::NixosModuleGenerator;
66//!
67//! #[derive(Serialize, Deserialize, NixosType)]
68//! struct Inner { value: String }
69//!
70//! #[derive(Serialize, Deserialize, NixosType)]
71//! struct Outer { child: Inner }
72//!
73//! let nix = NixosModuleGenerator::new()
74//! .register(type_registration!(Inner))
75//! .register(type_registration!(Outer))
76//! .export_all_types()
77//! .generate();
78//! ```
79
80pub use serde_nixos_macros::{nixos_module, NixosType};
81
82/// Re-export commonly used serde traits for convenience
83pub use serde::{Deserialize, Serialize};
84
85/// Generator utilities for building NixOS modules
86pub mod generator;
87
88// Re-export key generator types for convenience
89pub use generator::{NixosModuleGenerator, TypeRegistration};
90
91/// Create a [`TypeRegistration`] from a type that derives [`NixosType`].
92///
93/// This macro captures the output of the derive-generated inherent methods
94/// (`nixos_type_name()`, `nixos_options()`, `nixos_options_named()`,
95/// `nixos_type()`) into a [`TypeRegistration`] struct suitable for
96/// [`NixosModuleGenerator::register`].
97///
98/// # Example
99///
100/// ```rust
101/// use serde::{Serialize, Deserialize};
102/// use serde_nixos::{NixosType, type_registration};
103///
104/// #[derive(Serialize, Deserialize, NixosType)]
105/// struct MyConfig {
106/// port: u16,
107/// host: String,
108/// }
109///
110/// let reg = type_registration!(MyConfig);
111/// assert_eq!(reg.type_name, "myConfigType");
112/// ```
113#[macro_export]
114macro_rules! type_registration {
115 ($ty:ty) => {
116 $crate::generator::TypeRegistration {
117 type_name: <$ty>::nixos_type_name(),
118 options: <$ty>::nixos_options(),
119 options_named: <$ty>::nixos_options_named(),
120 type_expr: <$ty>::nixos_type(),
121 }
122 };
123}
124
125/// Helper trait for types that can generate NixOS definitions
126pub trait NixosTypeGenerator {
127 /// Generate a complete NixOS module definition
128 fn nixos_type_definition() -> String;
129
130 /// Generate just the options portion of the module
131 fn nixos_options() -> String;
132
133 /// Get the NixOS type expression for this type
134 fn nixos_type() -> String;
135}
136
137/// Utility functions for working with NixOS types
138pub mod utils {
139
140 /// Format a Rust value as a Nix expression
141 pub fn format_nix_value(value: &serde_json::Value) -> String {
142 match value {
143 serde_json::Value::Null => "null".to_string(),
144 serde_json::Value::Bool(b) => b.to_string(),
145 serde_json::Value::Number(n) => n.to_string(),
146 serde_json::Value::String(s) => format!("\"{}\"", escape_nix_string(s)),
147 serde_json::Value::Array(arr) => {
148 let items: Vec<String> = arr.iter().map(format_nix_value).collect();
149 format!("[ {} ]", items.join(" "))
150 }
151 serde_json::Value::Object(obj) => {
152 let attrs: Vec<String> = obj
153 .iter()
154 .map(|(k, v)| format!("{} = {};", k, format_nix_value(v)))
155 .collect();
156 format!("{{ {} }}", attrs.join(" "))
157 }
158 }
159 }
160
161 /// Escape a string for use in Nix expressions
162 pub fn escape_nix_string(s: &str) -> String {
163 s.replace('\\', "\\\\")
164 .replace('"', "\\\"")
165 .replace('\n', "\\n")
166 .replace('\r', "\\r")
167 .replace('\t', "\\t")
168 }
169
170 /// Generate a NixOS module file with proper formatting
171 pub fn generate_module_file(module_name: &str, options: &str, config: Option<&str>) -> String {
172 let mut result = String::new();
173
174 result.push_str("{ config, lib, pkgs, ... }:\n\n");
175 result.push_str("with lib;\n\n");
176 result.push_str("{\n");
177
178 // Add options
179 result.push_str(" options.");
180 result.push_str(module_name);
181 result.push_str(" = {\n");
182 result.push_str(options);
183 result.push_str(" };\n\n");
184
185 // Add config if provided
186 if let Some(cfg) = config {
187 result.push_str(" config = mkIf config.");
188 result.push_str(module_name);
189 result.push_str(".enable {\n");
190 result.push_str(cfg);
191 result.push_str(" };\n");
192 }
193
194 result.push_str("}\n");
195 result
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_nix_value_formatting() {
205 assert_eq!(utils::format_nix_value(&serde_json::json!(null)), "null");
206 assert_eq!(utils::format_nix_value(&serde_json::json!(true)), "true");
207 assert_eq!(utils::format_nix_value(&serde_json::json!(42)), "42");
208 assert_eq!(
209 utils::format_nix_value(&serde_json::json!("hello")),
210 "\"hello\""
211 );
212 assert_eq!(
213 utils::format_nix_value(&serde_json::json!([1, 2, 3])),
214 "[ 1 2 3 ]"
215 );
216 }
217
218 #[test]
219 fn test_escape_nix_string() {
220 assert_eq!(utils::escape_nix_string("hello"), "hello");
221 assert_eq!(utils::escape_nix_string("hello\"world"), "hello\\\"world");
222 assert_eq!(utils::escape_nix_string("line1\nline2"), "line1\\nline2");
223 }
224}