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 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}