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}