1use crate::{
13 CountryCode,
14 country::{country_code_uk, country_code_us},
15 error::CoreError,
16};
17use serde::{Deserialize, Serialize};
18use serde_with::{DeserializeFromStr, SerializeDisplay};
19use std::{fmt::Display, str::FromStr};
20
21#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
30pub struct Agency {
31 name: String,
32 abbreviation: Option<String>,
33 kind: AgencyKind,
34 jurisdiction: Option<Jurisdiction>,
35 url: Option<String>,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, DeserializeFromStr, SerializeDisplay)]
39pub enum AgencyKind {
40 StandardsSetting,
41 Regulatory,
42 Maintaining,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
46#[serde(untagged)]
47pub enum Jurisdiction {
48 International,
49 Just(CountryCode),
50 All(Vec<CountryCode>),
51}
52
53pub fn agency_itu() -> Agency {
58 Agency::new(
59 "The International Telecommunication Union",
60 AgencyKind::StandardsSetting,
61 )
62 .with_abbreviation("ITU")
63 .with_jurisdiction(Jurisdiction::International)
64 .with_url("https://www.itu.int")
65}
66
67pub fn agency_iaru() -> Agency {
68 Agency::new(
69 "The International Amateur Radio Union",
70 AgencyKind::Maintaining,
71 )
72 .with_abbreviation("IARU")
73 .with_jurisdiction(Jurisdiction::International)
74 .with_url("https://www.iaru.org")
75}
76
77pub fn agency_arrl() -> Agency {
78 Agency::new("The American Radio Relay League", AgencyKind::Maintaining)
79 .with_abbreviation("ARRL")
80 .with_jurisdiction(Jurisdiction::International)
81 .with_url("http://www.arrl.org")
82}
83
84pub fn agency_fcc() -> Agency {
85 Agency::new("Federal Communications Commission", AgencyKind::Regulatory)
86 .with_abbreviation("FCC")
87 .with_jurisdiction(Jurisdiction::Just(country_code_us()))
88 .with_url("https://www.fcc.gov")
89}
90
91pub fn agency_ofcom() -> Agency {
92 Agency::new("Ofcom", AgencyKind::Regulatory)
93 .with_jurisdiction(Jurisdiction::Just(country_code_uk()))
94 .with_url("https://www.ofcom.org.uk")
95}
96
97pub fn agency_rsgb() -> Agency {
98 Agency::new("Radio Society of Great Britain", AgencyKind::Maintaining)
99 .with_abbreviation("RSGB")
100 .with_jurisdiction(Jurisdiction::Just(country_code_uk()))
101 .with_url("https://www.rsgb.org")
102}
103
104impl Display for Agency {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 if f.alternate() {
119 write!(
120 f,
121 "{}{}, {:#}{}",
122 self.name,
123 if let Some(abbreviation) = self.abbreviation.as_ref() {
124 format!(" ({})", abbreviation)
125 } else {
126 String::default()
127 },
128 self.kind,
129 match &self.jurisdiction {
130 Some(Jurisdiction::International) => "international".to_string(),
131 Some(Jurisdiction::Just(cc)) => cc.to_string(),
132 Some(Jurisdiction::All(ccs)) => ccs
133 .iter()
134 .map(|c| c.as_str())
135 .collect::<Vec<_>>()
136 .join(", "),
137 None => String::default(),
138 }
139 )
140 } else {
141 write!(
142 f,
143 "{}{}",
144 self.name,
145 if let Some(abbreviation) = self.abbreviation.as_ref() {
146 format!(" ({})", abbreviation)
147 } else {
148 String::default()
149 }
150 )
151 }
152 }
153}
154
155impl Agency {
156 pub fn new(name: &str, kind: AgencyKind) -> Self {
157 Self {
158 name: name.to_string(),
159 abbreviation: None,
160 kind,
161 jurisdiction: None,
162 url: None,
163 }
164 }
165
166 pub fn with_abbreviation(mut self, abbreviation: &str) -> Self {
167 self.abbreviation = Some(abbreviation.to_string());
168 self
169 }
170
171 pub fn with_jurisdiction(mut self, jurisdiction: Jurisdiction) -> Self {
172 self.jurisdiction = Some(jurisdiction);
173 self
174 }
175
176 pub fn with_url(mut self, url: &str) -> Self {
177 self.url = Some(url.to_string());
178 self
179 }
180
181 pub fn within_jurisdiction(&self, country: &CountryCode) -> Option<bool> {
182 self.jurisdiction.as_ref().map(|v| v.contains(country))
183 }
184
185 pub fn name(&self) -> &String {
186 &self.name
187 }
188
189 pub fn abbreviation(&self) -> Option<&String> {
190 self.abbreviation.as_ref()
191 }
192
193 pub fn kind(&self) -> AgencyKind {
194 self.kind
195 }
196
197 pub fn jurisdiction(&self) -> Option<&Jurisdiction> {
198 self.jurisdiction.as_ref()
199 }
200
201 pub fn url(&self) -> Option<&String> {
202 self.url.as_ref()
203 }
204}
205
206impl Display for AgencyKind {
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 write!(
213 f,
214 "{}",
215 if f.alternate() {
216 match self {
217 Self::StandardsSetting => "Standards-Setting Agency",
218 Self::Regulatory => "Regulatory Agency",
219 Self::Maintaining => "Maintaining Agency",
220 }
221 } else {
222 match self {
223 Self::StandardsSetting => "standards",
224 Self::Regulatory => "regulatory",
225 Self::Maintaining => "maintaining",
226 }
227 }
228 )
229 }
230}
231
232impl FromStr for AgencyKind {
233 type Err = CoreError;
234
235 fn from_str(s: &str) -> Result<Self, Self::Err> {
236 match s {
237 "standards" => Ok(Self::StandardsSetting),
238 "regulatory" => Ok(Self::Regulatory),
239 "maintaining" => Ok(Self::Maintaining),
240 _ => Err(CoreError::InvalidValueFromStr(s.to_string(), "AgencyKind")),
241 }
242 }
243}
244
245impl AgencyKind {
246 pub fn is_standards_setting(&self) -> bool {
247 matches!(self, Self::StandardsSetting)
248 }
249
250 pub fn is_regulatory(&self) -> bool {
251 matches!(self, Self::Regulatory)
252 }
253
254 pub fn is_maintaining(&self) -> bool {
255 matches!(self, Self::Maintaining)
256 }
257}
258
259impl Display for Jurisdiction {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 write!(
266 f,
267 "{}",
268 match self {
269 Self::International => "international".to_string(),
270 Self::Just(cc) => cc.to_string(),
271 Self::All(ccs) => ccs
272 .iter()
273 .map(|c| c.as_str())
274 .collect::<Vec<_>>()
275 .join(", "),
276 }
277 )
278 }
279}
280
281impl FromStr for Jurisdiction {
282 type Err = CoreError;
283
284 fn from_str(s: &str) -> Result<Self, Self::Err> {
285 if s == "international" {
286 Ok(Self::International)
287 } else if s.contains(',') {
288 let list: Result<Vec<CountryCode>, CoreError> =
289 s.split(',').map(CountryCode::from_str).collect();
290 Ok(Self::All(list?))
291 } else {
292 Ok(Self::Just(CountryCode::from_str(s)?))
293 }
294 }
295}
296
297impl Jurisdiction {
298 pub fn contains(&self, country: &CountryCode) -> bool {
299 match self {
300 Jurisdiction::International => true,
301 Jurisdiction::Just(country_code) => country_code == country,
302 Jurisdiction::All(country_codes) => country_codes.contains(country),
303 }
304 }
305}
306
307