Skip to main content

seer_core/output/
json.rs

1use super::OutputFormatter;
2use crate::dns::{DnsRecord, FollowIteration, FollowResult, PropagationResult};
3use crate::lookup::LookupResult;
4use crate::rdap::RdapResponse;
5use crate::status::StatusResponse;
6use crate::whois::WhoisResponse;
7
8pub struct JsonFormatter {
9    pretty: bool,
10}
11
12impl Default for JsonFormatter {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17
18impl JsonFormatter {
19    pub fn new() -> Self {
20        Self { pretty: true }
21    }
22
23    pub fn compact(mut self) -> Self {
24        self.pretty = false;
25        self
26    }
27
28    fn to_json<T: serde::Serialize + ?Sized>(&self, value: &T) -> String {
29        if self.pretty {
30            serde_json::to_string_pretty(value)
31                .unwrap_or_else(|e| format!("{{\"error\": \"{}\"}}", e))
32        } else {
33            serde_json::to_string(value).unwrap_or_else(|e| format!("{{\"error\": \"{}\"}}", e))
34        }
35    }
36}
37
38impl OutputFormatter for JsonFormatter {
39    fn format_whois(&self, response: &WhoisResponse) -> String {
40        self.to_json(response)
41    }
42
43    fn format_rdap(&self, response: &RdapResponse) -> String {
44        self.to_json(response)
45    }
46
47    fn format_dns(&self, records: &[DnsRecord]) -> String {
48        self.to_json(records)
49    }
50
51    fn format_propagation(&self, result: &PropagationResult) -> String {
52        self.to_json(result)
53    }
54
55    fn format_lookup(&self, result: &LookupResult) -> String {
56        self.to_json(result)
57    }
58
59    fn format_status(&self, response: &StatusResponse) -> String {
60        self.to_json(response)
61    }
62
63    fn format_follow_iteration(&self, iteration: &FollowIteration) -> String {
64        self.to_json(iteration)
65    }
66
67    fn format_follow(&self, result: &FollowResult) -> String {
68        self.to_json(result)
69    }
70
71    fn format_availability(&self, result: &crate::availability::AvailabilityResult) -> String {
72        self.to_json(result)
73    }
74
75    fn format_dnssec(&self, report: &crate::dns::DnssecReport) -> String {
76        self.to_json(report)
77    }
78
79    fn format_delegation(&self, report: &crate::dns::DelegationReport) -> String {
80        self.to_json(report)
81    }
82
83    fn format_tld(&self, info: &crate::tld::TldInfo) -> String {
84        self.to_json(info)
85    }
86
87    fn format_dns_comparison(&self, comparison: &crate::dns::DnsComparison) -> String {
88        self.to_json(comparison)
89    }
90
91    fn format_subdomains(&self, result: &crate::subdomains::SubdomainResult) -> String {
92        self.to_json(result)
93    }
94
95    fn format_diff(&self, diff: &crate::diff::DomainDiff) -> String {
96        self.to_json(diff)
97    }
98
99    fn format_ssl(&self, report: &crate::ssl::SslReport) -> String {
100        self.to_json(report)
101    }
102
103    fn format_watch(&self, report: &crate::watchlist::WatchReport) -> String {
104        self.to_json(report)
105    }
106
107    fn format_domain_info(&self, info: &crate::domain_info::DomainInfo) -> String {
108        self.to_json(info)
109    }
110    fn format_drift(&self, report: &crate::drift::DriftReport) -> String {
111        self.to_json(report)
112    }
113    fn format_posture(&self, posture: &crate::posture::EmailPosture) -> String {
114        self.to_json(posture)
115    }
116    fn format_caa(&self, policy: &crate::caa::CaaPolicy) -> String {
117        self.to_json(policy)
118    }
119    fn format_confusables(&self, report: &crate::confusables::ConfusableReport) -> String {
120        self.to_json(report)
121    }
122    fn format_subdomain_classification(
123        &self,
124        result: &crate::subdomains::SubdomainClassification,
125    ) -> String {
126        self.to_json(result)
127    }
128
129    fn format_subdomain_baseline_diff(
130        &self,
131        report: &crate::subdomains::SubdomainBaselineDiff,
132    ) -> String {
133        self.to_json(report)
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::dns::RecordType;
141    use crate::status::StatusResponse;
142
143    #[test]
144    fn test_json_format_status() {
145        let response = StatusResponse::new("example.com".to_string());
146        let formatter = JsonFormatter::new();
147        let output = formatter.format_status(&response);
148        assert!(output.contains("example.com"));
149        assert!(output.contains("domain"));
150    }
151
152    #[test]
153    fn test_json_compact() {
154        let response = StatusResponse::new("example.com".to_string());
155        let formatter = JsonFormatter::new().compact();
156        let output = formatter.format_status(&response);
157        // Compact format should not have newlines within the JSON
158        assert!(!output.contains("\n  "));
159    }
160
161    #[test]
162    fn test_json_format_dns_records() {
163        let records = vec![DnsRecord {
164            name: "example.com".to_string(),
165            record_type: RecordType::A,
166            ttl: 300,
167            data: crate::dns::RecordData::A {
168                address: "93.184.216.34".to_string(),
169            },
170        }];
171        let formatter = JsonFormatter::new();
172        let output = formatter.format_dns(&records);
173        assert!(output.contains("93.184.216.34"));
174        assert!(output.contains("\"A\""));
175    }
176}