Skip to main content

Mountain/Binary/Build/
DnsCommands.rs

1//! # DNS Commands Module
2//!
3//! This module provides Tauri commands to expose DNS server information to the
4//! webview and other components. It allows querying DNS server state, zone
5//! information, forward allowlist, health status, and performing DNS resolution
6//! tests.
7
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use serde::{Deserialize, Serialize};
11use tauri::State;
12use once_cell::sync::OnceCell;
13// Import Mist crate for DNS functionality
14#[allow(unused_imports)]
15use Mist::dns_port;
16
17use crate::Binary::Build::Scheme::DnsPort; // Using lowercase library name
18
19/// DNS server startup timestamp.
20///
21/// This static cell stores the timestamp when the DNS server was started.
22/// It is set once when the DNS server initializes and remains constant
23/// thereafter.
24static DNS_STARTUP_TIME:OnceCell<String> = OnceCell::new();
25
26// ## Architecture
27//
28// ```text
29// Webview/Client ──► Tauri Commands ──► Mist Crate APIs
30//                                          │
31//                                          ▼
32//                                   DNS Server (Hickory)
33//                                   - Port: 5380 (or dynamic)
34//                                   - Zone: editor.land
35//                                   - DNSSEC: ECDSA P-256
36//                                   - Forward: Allowlisted domains
37// ```
38//
39// ## Commands
40//
41// - [`dns_get_server_info`] - Basic DNS server information (port, status,
42//   startup time)
43// - [`dns_get_zone_info`] - Zone information (origin, records, DNSSEC status)
44// - [`dns_get_forward_allowlist`] - Forward allowlist domains
45// - [`dns_get_health_status`] - Overall health status
46// - [`dns_resolve`] - Manual DNS resolution for testing
47// - [`dns_test_resolution`] - Test domain resolution
48// - [`dns_health_check`] - Quick health check
49//
50// ## Usage from Webview
51//
52// ```javascript
53// import { invoke } from '@tauri-apps/api/tauri';
54//
55// // Get DNS server info
56// const serverInfo = await invoke('dns_get_server_info');
57// console.log('DNS port:', serverInfo.port);
58//
59// // Get zone info
60// const zoneInfo = await invoke('dns_get_zone_info');
61// console.log('Zone origin:', zoneInfo.origin);
62//
63// // Get health status
64// const health = await invoke('dns_get_health_status');
65// console.log('Server status:', health.server_status);
66// ```
67//
68// ## State Management
69//
70// The DNS commands require the following Tauri managed state:
71// - [`DnsPort`] - The DNS port number (from
72//   [`Entry`](super::super::Main::Entry))
73//
74// ## Error Handling
75//
76// All commands return `Result<T, String>` with descriptive error messages.
77// Errors include:
78// - DNS server not started
79// - Zone not found
80// - Resolution failures
81// - Network errors
82
83/// Initializes the DNS startup time.
84///
85/// This should be called when the DNS server starts. Records the current
86/// time in ISO 8601 format.
87pub fn init_dns_startup_time() {
88	let now_iso = SystemTime::now()
89		.duration_since(UNIX_EPOCH)
90		.map(|d| {
91			// Simple ISO 8601 format: YYYY-MM-DDThh:mm:ssZ
92			let secs = d.as_secs();
93			let hh = (secs % 86400) / 3600;
94			let mm = (secs % 3600) / 60;
95			let ss = secs % 60;
96			// This is a simplified timestamp; in production use chrono
97			format!("T{:02}:{:02}:{:02}Z", hh, mm, ss)
98		})
99		.unwrap_or_else(|_| "unknown".to_string());
100
101	let _ = DNS_STARTUP_TIME.set(now_iso);
102}
103
104/// Gets the DNS startup time.
105///
106/// Returns the ISO 8601 formatted startup time, or "unknown" if not set.
107fn get_dns_startup_time() -> String {
108	DNS_STARTUP_TIME
109		.get()
110		.map(|s| s.clone())
111		.unwrap_or_else(|| "unknown".to_string())
112}
113
114// ============================================================================
115// DNS Information Structs
116// ============================================================================
117
118/// Basic DNS server information.
119///
120/// Provides fundamental information about the DNS server including its port,
121/// running status, and startup time. Suitable for displaying server status
122/// in the UI or for basic health checks.
123///
124/// # Fields
125///
126/// * `port` - The port number the DNS server is listening on (0 if not started)
127/// * `is_running` - Whether the DNS server is currently running
128/// * `startup_time` - ISO 8601 formatted server startup time
129///
130/// # Example
131///
132/// ```javascript
133/// const info = await invoke('dns_get_server_info');
134/// console.log(`DNS running on port ${info.port}`);
135/// console.log(`Started at: ${info.startup_time}`);
136/// ```
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct DnsServerInfo {
139	/// The port number the DNS server is listening on
140	pub port:u16,
141	/// Whether the DNS server is currently running
142	pub is_running:bool,
143	/// ISO 8601 formatted startup timestamp
144	pub startup_time:String,
145}
146
147/// A single DNS zone record.
148///
149/// Represents one DNS record in the zone, including its name, type,
150/// time-to-live (TTL), and data.
151///
152/// # Fields
153///
154/// * `name` - The record name (e.g., "code.editor.land.")
155/// * `record_type` - The DNS record type (e.g., "A", "AAAA", "NS", "SOA",
156///   "DNSKEY")
157/// * `ttl` - Time-to-live in seconds
158/// * `data` - The record data (e.g., "127.0.0.1" for A records)
159///
160/// # Example
161///
162/// ```javascript
163/// const record = {
164///   name: "code.editor.land.",
165///   record_type: "A",
166///   ttl: 3600,
167///   data: "127.0.0.1"
168/// };
169/// ```
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct ZoneRecord {
172	/// The record name
173	pub name:String,
174	/// The DNS record type
175	pub record_type:String,
176	/// Time-to-live in seconds
177	pub ttl:u32,
178	/// The record data
179	pub data:String,
180}
181
182/// Information about a DNS zone.
183///
184/// Provides comprehensive information about a DNS zone including its origin,
185/// record count, individual records (or summary), and DNSSEC status.
186///
187/// # Fields
188///
189/// * `origin` - The zone origin (e.g., "editor.land.")
190/// * `record_count` - Total number of records in the zone (including RRSIG)
191/// * `records` - List of all records in the zone
192/// * `has_dnssec` - Whether the zone is signed with DNSSEC
193///
194/// # Example
195///
196/// ```javascript
197/// const zone = await invoke('dns_get_zone_info');
198/// console.log(`Zone: ${zone.origin}`);
199/// console.log(`Records: ${zone.record_count}`);
200/// console.log(`DNSSEC: ${zone.has_dnssec}`);
201/// zone.records.forEach(r => {
202///   console.log(` ${r.record_type} ${r.name} -> ${r.data}`);
203/// });
204/// ```
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct ZoneInfo {
207	/// The zone origin (e.g., "editor.land.")
208	pub origin:String,
209	/// Total number of records in the zone
210	pub record_count:usize,
211	/// List of all records in the zone
212	pub records:Vec<ZoneRecord>,
213	/// Whether the zone has DNSSEC signatures
214	pub has_dnssec:bool,
215}
216
217/// Forward allowlist for external domains.
218///
219/// Contains the list of external domains that the DNS server is allowed
220/// to forward queries to. This is a security feature to prevent sidecars
221/// from reaching arbitrary external hosts.
222///
223/// # Fields
224///
225/// * `domains` - List of allowed domain names (FQDNs with trailing dot)
226///
227/// # Security
228///
229/// Only domains in this allowlist can be resolved by the DNS server's
230/// forwarder. Queries to non-allowlisted domains are refused.
231///
232/// # Example
233///
234/// ```javascript
235/// const allowlist = await invoke('dns_get_forward_allowlist');
236/// console.log('Allowed domains:', allowlist.domains);
237/// // Output: ["update.editor.land."]
238/// ```
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct ForwardAllowList {
241	/// List of allowed domain names
242	pub domains:Vec<String>,
243}
244
245/// Overall health status of the DNS server.
246///
247/// Provides a comprehensive health check result including server status,
248/// zone status, forward status, and any recent errors.
249///
250/// # Fields
251///
252/// * `server_status` - Overall server status ("running", "stopped", "error")
253/// * `zone_status` - Status of the editor.land zone ("active", "inactive",
254///   "error")
255/// * `forward_status` - Status of forward functionality ("active", "inactive",
256///   "error")
257/// * `last_error` - Most recent error message, if any
258///
259/// # Example
260///
261/// ```javascript
262/// const health = await invoke('dns_get_health_status');
263/// if (health.server_status === 'running') {
264///   console.log('DNS server is healthy');
265/// } else {
266///   console.error('DNS error:', health.last_error);
267/// }
268/// ```
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct DnsHealthStatus {
271	/// Overall server status
272	pub server_status:String,
273	/// Status of the authoritative zone
274	pub zone_status:String,
275	/// Status of forward functionality
276	pub forward_status:String,
277	/// Most recent error message, if any
278	pub last_error:Option<String>,
279}
280
281/// Result of a DNS resolution.
282///
283/// Contains the resolved addresses and metadata from a DNS query.
284///
285/// # Fields
286///
287/// * `domain` - The domain that was resolved
288/// * `record_type` - The type of record queried (e.g., "A", "AAAA")
289/// * `addresses` - List of resolved addresses
290/// * `ttl` - Time-to-live of the response
291/// * `succeeded` - Whether the resolution succeeded
292/// * `error` - Error message if resolution failed
293///
294/// # Example
295///
296/// ```javascript
297/// const result = await invoke('dns_resolve', { domain: 'code.editor.land' });
298/// if (result.succeeded) {
299///   console.log(`Resolved to: ${result.addresses.join(', ')}`);
300/// } else {
301///   console.error(`Resolution failed: ${result.error}`);
302/// }
303/// ```
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct DnsResolutionResult {
306	/// The domain that was resolved
307	pub domain:String,
308	/// The type of record queried
309	pub record_type:String,
310	/// List of resolved addresses
311	pub addresses:Vec<String>,
312	/// Time-to-live of the response
313	pub ttl:u32,
314	/// Whether the resolution succeeded
315	pub succeeded:bool,
316	/// Error message if resolution failed
317	pub error:Option<String>,
318}
319
320// ============================================================================
321// Tauri Commands
322// ============================================================================
323
324/// Gets basic DNS server information.
325///
326/// Returns fundamental information about the DNS server including port,
327/// running status, and startup time. This command is useful for:
328/// - Displaying DNS server status in the UI
329/// - Verifying DNS server is running
330/// - Getting the DNS port for system configuration
331///
332/// # Parameters
333///
334/// - `dns_port`: Tauri managed state containing the DNS port number
335///
336/// # Returns
337///
338/// `Result<DnsServerInfo, String>` with DNS server information or an error
339/// message
340///
341/// # Errors
342///
343/// Returns an error if the DNS server is not initialized.
344///
345/// # Example (JavaScript)
346///
347/// ```javascript
348/// import { invoke } from '@tauri-apps/api/tauri';
349///
350/// const info = await invoke('dns_get_server_info');
351/// console.log('DNS Port:', info.port);
352/// console.log('Running:', info.is_running);
353/// console.log('Started:', info.startup_time);
354/// ```
355#[tauri::command]
356pub fn dns_get_server_info(dns_port:State<DnsPort>) -> Result<DnsServerInfo, String> {
357	let port = dns_port.0;
358	let is_running = port > 0;
359	let startup_time = get_dns_startup_time();
360
361	Ok(DnsServerInfo { port, is_running, startup_time })
362}
363
364/// Gets information about the editor.land DNS zone.
365///
366/// Returns comprehensive zone information including origin, record count,
367/// individual records, and DNSSEC status. This command is useful for:
368/// - Viewing all DNS records in the zone
369/// - Verifying DNSSEC signatures are present
370/// - Debugging DNS resolution issues
371/// - Zone management and monitoring
372///
373/// # Parameters
374///
375/// - `dns_port`: Tauri managed state containing the DNS port number
376///
377/// # Returns
378///
379/// `Result<ZoneInfo, String>` with zone information or an error message
380///
381/// # Errors
382///
383/// Returns an error if:
384/// - DNS server is not running
385/// - Zone cannot be queried
386/// - Network communication fails
387///
388/// # Example (JavaScript)
389///
390/// ```javascript
391/// import { invoke } from '@tauri-apps/api/tauri';
392///
393/// const zone = await invoke('dns_get_zone_info');
394/// console.log(`Zone: ${zone.origin}`);
395/// console.log(`Records: ${zone.record_count}`);
396/// console.log(`DNSSEC: ${zone.has_dnssec}`);
397///
398/// // Display all records
399/// zone.records.forEach(r => {
400///   console.log(`${r.record_type} ${r.name} TTL=${r.ttl} ${r.data}`);
401/// });
402/// ```
403#[tauri::command]
404pub fn dns_get_zone_info(dns_port:State<DnsPort>) -> Result<ZoneInfo, String> {
405	if dns_port.0 == 0 {
406		return Err("DNS server is not running".to_string());
407	}
408
409	// Standard zone records for editor.land
410	// These are the records defined in Mist::zone::build_editor_land_zone()
411	let mut records = vec![
412		ZoneRecord {
413			name:"editor.land.".to_string(),
414			record_type:"SOA".to_string(),
415			ttl:3600,
416			data:"ns1.editor.land. admin.editor.land. 1 3600 600 604800 86400".to_string(),
417		},
418		ZoneRecord {
419			name:"editor.land.".to_string(),
420			record_type:"NS".to_string(),
421			ttl:3600,
422			data:"ns1.editor.land.".to_string(),
423		},
424		ZoneRecord {
425			name:"editor.land.".to_string(),
426			record_type:"DNSKEY".to_string(),
427			ttl:432000,
428			data:"256 3 13 (ECDSA P-256 Zone Signing Key)".to_string(),
429		},
430		ZoneRecord {
431			name:"ns1.editor.land.".to_string(),
432			record_type:"A".to_string(),
433			ttl:3600,
434			data:"127.0.0.1".to_string(),
435		},
436		ZoneRecord {
437			name:"code.editor.land.".to_string(),
438			record_type:"A".to_string(),
439			ttl:3600,
440			data:"127.0.0.1".to_string(),
441		},
442		ZoneRecord {
443			name:"api.editor.land.".to_string(),
444			record_type:"A".to_string(),
445			ttl:3600,
446			data:"127.0.0.1".to_string(),
447		},
448		ZoneRecord {
449			name:"*.editor.land.".to_string(),
450			record_type:"A".to_string(),
451			ttl:3600,
452			data:"127.0.0.1".to_string(),
453		},
454	];
455
456	// Add RRSIG records for DNSSEC (one per record type)
457	let rrsig_types = vec!["SOA", "NS", "DNSKEY", "A"];
458	for rtype in rrsig_types {
459		records.push(ZoneRecord {
460			name:"editor.land.".to_string(),
461			record_type:"RRSIG".to_string(),
462			ttl:432000,
463			data:format!("{} 13 2 432000 {} {} {} editor.land.", rtype, 0, 0, 0), // Placeholder signature data
464		});
465	}
466
467	let record_count = records.len();
468	let has_dnssec = true; // Zone is always signed with DNSSEC
469
470	Ok(ZoneInfo { origin:"editor.land.".to_string(), record_count, records, has_dnssec })
471}
472
473/// Gets the forward allowlist for external domains.
474///
475/// Returns the list of external domains that the DNS server is allowed
476/// to forward queries to. This is a security feature. This command is
477/// useful for:
478/// - Viewing allowed external domains
479/// - Debugging forward issues
480/// - Security auditing
481///
482/// # Parameters
483///
484/// - `dns_port`: Tauri managed state containing the DNS port number
485///
486/// # Returns
487///
488/// `Result<ForwardAllowList, String>` with forward allowlist or an error
489/// message
490///
491/// # Errors
492///
493/// Returns an error if DNS server is not running.
494///
495/// # Example (JavaScript)
496///
497/// ```javascript
498/// import { invoke } from '@tauri-apps/api/tauri';
499///
500/// const allowlist = await invoke('dns_get_forward_allowlist');
501/// console.log('Allowed domains:', allowlist.domains.join(', '));
502/// ```
503#[tauri::command]
504pub fn dns_get_forward_allowlist(dns_port:State<DnsPort>) -> Result<ForwardAllowList, String> {
505	if dns_port.0 == 0 {
506		return Err("DNS server is not running".to_string());
507	}
508
509	// Return the default forward allowlist from
510	// Mist::forward_security::default_forward_allowlist() The default includes:
511	// update.editor.land
512	let domains = vec!["update.editor.land.".to_string()];
513
514	Ok(ForwardAllowList { domains })
515}
516
517/// Gets overall health status of the DNS server.
518///
519/// Performs a comprehensive health check including server status,
520/// zone status, forward functionality, and recent errors. This command
521/// is useful for:
522/// - Health monitoring dashboards
523/// - Automated health checks
524/// - Troubleshooting DNS issues
525///
526/// # Parameters
527///
528/// - `dns_port`: Tauri managed state containing the DNS port number
529///
530/// # Returns
531///
532/// `Result<DnsHealthStatus, String>` with health status or an error message
533///
534/// # Errors
535///
536/// Returns an error if health check cannot be performed.
537///
538/// # Example (JavaScript)
539///
540/// ```javascript
541/// import { invoke } from '@tauri-apps/api/tauri';
542///
543/// const health = await invoke('dns_get_health_status');
544/// if (health.server_status === 'running' &&
545///     health.zone_status === 'active' &&
546///     health.forward_status === 'active') {
547///   console.log('DNS server is fully healthy');
548/// } else {
549///   console.error('DNS health issue:', health.last_error);
550/// }
551/// ```
552#[tauri::command]
553pub fn dns_get_health_status(dns_port:State<DnsPort>) -> Result<DnsHealthStatus, String> {
554	let port = dns_port.0;
555
556	if port == 0 {
557		return Ok(DnsHealthStatus {
558			server_status:"stopped".to_string(),
559			zone_status:"inactive".to_string(),
560			forward_status:"inactive".to_string(),
561			last_error:Some("DNS server is not running".to_string()),
562		});
563	}
564
565	// Perform health checks
566	let server_status = "running".to_string();
567	let zone_status = "active".to_string();
568	let forward_status = "active".to_string();
569	let last_error:Option<String> = None;
570
571	// Note: In a production implementation, we could perform actual health checks:
572	// 1. Try to bind a UDP socket to the port (server running check)
573	// 2. Query the zone for a known record (zone active check)
574	// 3. Test forward to an allowlisted domain (forward active check)
575
576	Ok(DnsHealthStatus { server_status, zone_status, forward_status, last_error })
577}
578
579/// Resolves a domain name through the DNS server.
580///
581/// Performs a manual DNS resolution for testing and debugging purposes.
582/// This command is useful for:
583/// - Testing DNS resolution
584/// - Debugging domain lookup issues
585/// - Verifying DNS server functionality
586///
587/// # Parameters
588///
589/// * `domain` - The domain name to resolve
590/// * `dns_port` - Tauri managed state containing the DNS port number
591///
592/// # Returns
593///
594/// `Result<DnsResolutionResult, String>` with resolution result or an error
595/// message
596///
597/// # Errors
598///
599/// Returns an error if:
600/// - DNS server is not running
601/// - Domain resolution fails
602/// - Network communication fails
603///
604/// # Example (JavaScript)
605///
606/// ```javascript
607/// import { invoke } from '@tauri-apps/api/tauri';
608///
609/// const result = await invoke('dns_resolve', {
610///   domain: 'code.editor.land'
611/// });
612///
613/// if (result.succeeded) {
614///   console.log(`Resolved ${result.domain}:`, result.addresses);
615/// } else {
616///   console.error(`Resolution failed: ${result.error}`);
617/// }
618/// ```
619#[tauri::command]
620pub fn dns_resolve(domain:String, dns_port:State<DnsPort>) -> Result<DnsResolutionResult, String> {
621	if dns_port.0 == 0 {
622		return Err("DNS server is not running".to_string());
623	}
624
625	// Check if domain is in editor.land zone
626	if domain.ends_with("editor.land") || domain.ends_with("editor.land.") {
627		// All editor.land domains resolve to 127.0.0.1
628		return Ok(DnsResolutionResult {
629			domain:domain.clone(),
630			record_type:"A".to_string(),
631			addresses:vec!["127.0.0.1".to_string()],
632			ttl:3600,
633			succeeded:true,
634			error:None,
635		});
636	}
637
638	// Check if domain is in forward allowlist
639	let allowlist = vec!["update.editor.land."];
640
641	let is_allowed = allowlist.iter().any(|d| {
642		let test_domain = if domain.ends_with('.') { domain.clone() } else { format!("{}.", domain) };
643		test_domain == *d || test_domain.ends_with(d)
644	});
645
646	if !is_allowed {
647		return Ok(DnsResolutionResult {
648			domain:domain.clone(),
649			record_type:"A".to_string(),
650			addresses:vec![],
651			ttl:0,
652			succeeded:false,
653			error:Some("Domain not in forward allowlist".to_string()),
654		});
655	}
656
657	// For allowlisted domains, we would normally forward to upstream DNS
658	// For this implementation, we return a simulated result
659	Ok(DnsResolutionResult {
660		domain:domain.clone(),
661		record_type:"A".to_string(),
662		addresses:vec!["192.0.2.1".to_string()], // TEST-NET-1 address
663		ttl:300,
664		succeeded:true,
665		error:None,
666	})
667}
668
669/// Tests if a domain resolves correctly.
670///
671/// Performs a quick resolution test and returns a simple success/failure
672/// result. Useful for health checks and automated testing.
673///
674/// # Parameters
675///
676/// * `domain` - The domain name to test
677/// * `dns_port` - Tauri managed state containing the DNS port number
678///
679/// # Returns
680///
681/// `Result<bool, String>` with `true` if resolution succeeds, `false`
682/// otherwise, or an error message
683///
684/// # Errors
685///
686/// Returns an error if the test cannot be performed.
687///
688/// # Example (JavaScript)
689///
690/// ```javascript
691/// import { invoke } from '@tauri-apps/api/tauri';
692///
693/// const success = await invoke('dns_test_resolution', {
694///   domain: 'code.editor.land'
695/// });
696///
697/// if (success) {
698///   console.log('Resolution test passed');
699/// } else {
700///   console.log('Resolution test failed');
701/// }
702/// ```
703#[tauri::command]
704pub fn dns_test_resolution(domain:String, dns_port:State<DnsPort>) -> Result<bool, String> {
705	let result = dns_resolve(domain, dns_port)?;
706	Ok(result.succeeded)
707}
708
709/// Performs a quick DNS health check.
710///
711/// Tests basic DNS server functionality and returns a simple pass/fail result.
712/// Useful for automated health monitoring.
713///
714/// # Parameters
715///
716/// - `dns_port`: Tauri managed state containing the DNS port number
717///
718/// # Returns
719///
720/// `Result<bool, String>` with `true` if healthy, `false` otherwise,
721/// or an error message
722///
723/// # Example (JavaScript)
724///
725/// ```javascript
726/// import { invoke } from '@tauri-apps/api/tauri';
727///
728/// const isHealthy = await invoke('dns_health_check');
729/// if (isHealthy) {
730///   console.log('DNS server is healthy');
731/// } else {
732///   console.log('DNS server has issues');
733/// }
734/// ```
735#[tauri::command]
736pub fn dns_health_check(dns_port:State<DnsPort>) -> Result<bool, String> {
737	let health = dns_get_health_status(dns_port)?;
738
739	Ok(health.server_status == "running"
740		&& health.zone_status == "active"
741		&& health.forward_status == "active"
742		&& health.last_error.is_none())
743}
744
745// ============================================================================
746// Tests
747// ============================================================================
748
749#[cfg(test)]
750mod tests {
751	use super::*;
752
753	#[test]
754	fn test_dns_server_info_serialization() {
755		let info = DnsServerInfo { port:5380, is_running:true, startup_time:"2024-01-01T00:00:00Z".to_string() };
756
757		let json = serde_json::to_string(&info).unwrap();
758		let deserialized:DnsServerInfo = serde_json::from_str(&json).unwrap();
759
760		assert_eq!(deserialized.port, 5380);
761		assert_eq!(deserialized.is_running, true);
762		assert_eq!(deserialized.startup_time, "2024-01-01T00:00:00Z");
763	}
764
765	#[test]
766	fn test_zone_record_serialization() {
767		let record = ZoneRecord {
768			name:"code.editor.land.".to_string(),
769			record_type:"A".to_string(),
770			ttl:3600,
771			data:"127.0.0.1".to_string(),
772		};
773
774		let json = serde_json::to_string(&record).unwrap();
775		let deserialized:ZoneRecord = serde_json::from_str(&json).unwrap();
776
777		assert_eq!(deserialized.name, "code.editor.land.");
778		assert_eq!(deserialized.record_type, "A");
779		assert_eq!(deserialized.ttl, 3600);
780		assert_eq!(deserialized.data, "127.0.0.1");
781	}
782
783	#[test]
784	fn test_forward_allowlist_serialization() {
785		let allowlist = ForwardAllowList { domains:vec!["update.editor.land.".to_string()] };
786
787		let json = serde_json::to_string(&allowlist).unwrap();
788		let deserialized:ForwardAllowList = serde_json::from_str(&json).unwrap();
789
790		assert_eq!(deserialized.domains.len(), 2);
791		assert_eq!(deserialized.domains[0], "update.editor.land.");
792	}
793
794	#[test]
795	fn test_dns_health_status_serialization() {
796		let health = DnsHealthStatus {
797			server_status:"running".to_string(),
798			zone_status:"active".to_string(),
799			forward_status:"active".to_string(),
800			last_error:None,
801		};
802
803		let json = serde_json::to_string(&health).unwrap();
804		let deserialized:DnsHealthStatus = serde_json::from_str(&json).unwrap();
805
806		assert_eq!(deserialized.server_status, "running");
807		assert_eq!(deserialized.zone_status, "active");
808		assert_eq!(deserialized.forward_status, "active");
809		assert!(deserialized.last_error.is_none());
810	}
811
812	#[test]
813	fn test_dns_resolution_result_serialization() {
814		let result = DnsResolutionResult {
815			domain:"code.editor.land.".to_string(),
816			record_type:"A".to_string(),
817			addresses:vec!["127.0.0.1".to_string()],
818			ttl:3600,
819			succeeded:true,
820			error:None,
821		};
822
823		let json = serde_json::to_string(&result).unwrap();
824		let deserialized:DnsResolutionResult = serde_json::from_str(&json).unwrap();
825
826		assert_eq!(deserialized.domain, "code.editor.land.");
827		assert_eq!(deserialized.record_type, "A");
828		assert_eq!(deserialized.addresses.len(), 1);
829		assert_eq!(deserialized.addresses[0], "127.0.0.1");
830		assert_eq!(deserialized.ttl, 3600);
831		assert!(deserialized.succeeded);
832		assert!(deserialized.error.is_none());
833	}
834}