codes_agency/lib.rs
1/*!
2This package provides a common code representing standards agencies.
3
4The two core types, [Agency] and [Standard] work together to provide reporting
5capabilities to other *codes* project packages. Specifically a package that
6provides types corresponding to a standard definition can have an instance of
7the [Standard] struct that describes the standard. This in turn references the
8[Agency] that controls the standard.
9
10```rust
11use codes_agency::{Agency, Standard};
12
13// Taken from codes_iso_4217
14pub const ISO_4217: Standard = Standard::new_with_long_ref(
15 Agency::ISO,
16 "4217",
17 "ISO 4217:2015",
18 "Currency codes",
19 "https://www.iso.org/iso-4217-currency-codes.html",
20);
21
22assert_eq!(ISO_4217.agency().to_string(), String::from("ISO"));
23assert_eq!(ISO_4217.short_ref(), "4217");
24assert_eq!(ISO_4217.long_ref(), Some(&"ISO 4217:2015"));
25assert_eq!(ISO_4217.title(), "Currency codes");
26assert_eq!(ISO_4217.url(), "https://www.iso.org/iso-4217-currency-codes.html");
27```
28*/
29
30#![warn(
31 unknown_lints,
32 // ---------- Stylistic
33 absolute_paths_not_starting_with_crate,
34 elided_lifetimes_in_paths,
35 explicit_outlives_requirements,
36 macro_use_extern_crate,
37 nonstandard_style, /* group */
38 noop_method_call,
39 rust_2018_idioms,
40 single_use_lifetimes,
41 trivial_casts,
42 trivial_numeric_casts,
43 // ---------- Future
44 future_incompatible, /* group */
45 rust_2021_compatibility, /* group */
46 // ---------- Public
47 missing_debug_implementations,
48 // missing_docs,
49 unreachable_pub,
50 // ---------- Unsafe
51 unsafe_code,
52 unsafe_op_in_unsafe_fn,
53 // ---------- Unused
54 unused, /* group */
55)]
56#![deny(
57 // ---------- Public
58 exported_private_dependencies,
59 private_in_public,
60 // ---------- Deprecated
61 anonymous_parameters,
62 bare_trait_objects,
63 ellipsis_inclusive_range_patterns,
64 // ---------- Unsafe
65 deref_nullptr,
66 drop_bounds,
67 dyn_drop,
68)]
69
70use codes_common::code_impl;
71use std::str::FromStr;
72
73#[cfg(feature = "serde")]
74use serde::{Deserialize, Serialize};
75
76// ------------------------------------------------------------------------------------------------
77// Public Types
78// ------------------------------------------------------------------------------------------------
79
80///
81/// This enumeration allows for the identification of well-known standards agencies. This is
82/// useful in documenting crates that implement such standards.
83///
84#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
85#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
86pub enum Agency {
87 /// [GS1](https://www.gs1.org)
88 GS1,
89 /// [Internet Assigned Numbers Authority](https://www.iana.org)
90 IANA,
91 /// [IEEE](https://www.ieee.org)
92 IEEE,
93 /// [The Internet Engineering Task Force](https://www.ietf.org)
94 IETF,
95 /// [International Organization for Standardization](https://www.iso.org)
96 ISO,
97 /// [The United Nations](https://www.un.org)
98 UN,
99}
100
101/// Provides an array of all defined [Agency] codes, useful for queries.
102pub const ALL_CODES: [Agency; 4] = [Agency::IANA, Agency::IEEE, Agency::IETF, Agency::ISO];
103
104///
105/// This structure allows for the description of a specific standard, or specification,
106/// issued by a well-known standards agency. Note that different versions of a standard
107/// should be different instances with *at least* different long references.
108///
109#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
110#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
111pub struct Standard {
112 agency: Agency,
113 short_ref: &'static str,
114 long_ref: Option<&'static str>,
115 title: &'static str,
116 url: &'static str,
117}
118
119pub trait Standardized {
120 fn defining_standard() -> &'static Standard;
121}
122
123///
124/// An error associated with handling String representations of the Agency short name.
125///
126#[derive(Clone, Debug, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
128pub enum AgencyError {
129 /// An error generated by `FromStr`
130 FromStr(String),
131}
132
133// ------------------------------------------------------------------------------------------------
134// Public Macros
135// ------------------------------------------------------------------------------------------------
136
137#[macro_export]
138macro_rules! standardized_type {
139 ($type_name:ty, $standard:expr) => {
140 impl $crate::Standardized for $type_name {
141 fn defining_standard() -> &'static $crate::Standard {
142 &$standard
143 }
144 }
145 };
146}
147// ------------------------------------------------------------------------------------------------
148// Implementations
149// ------------------------------------------------------------------------------------------------
150
151impl FromStr for Agency {
152 type Err = AgencyError;
153
154 fn from_str(s: &str) -> Result<Self, Self::Err> {
155 match s {
156 "GS1" => Ok(Self::GS1),
157 "IANA" => Ok(Self::IANA),
158 "IEEE" => Ok(Self::IEEE),
159 "IETF" => Ok(Self::IETF),
160 "ISO" => Ok(Self::ISO),
161 "UN" => Ok(Self::UN),
162 _ => Err(AgencyError::FromStr(s.to_string())),
163 }
164 }
165}
166
167code_impl!(Agency, short_name);
168
169impl Agency {
170 ///
171 /// Return the short name, usually an acronym or abbreviation, of the agency.
172 /// This is usually the same set of characters as the variant name.
173 ///
174 pub const fn short_name(&self) -> &'static str {
175 match self {
176 Self::GS1 => "GS1",
177 Self::IANA => "IANA",
178 Self::IEEE => "IEEE",
179 Self::IETF => "IETF",
180 Self::ISO => "ISO",
181 Self::UN => "UN",
182 }
183 }
184
185 ///
186 /// A longer name, if one exists, for the agency.
187 ///
188 pub const fn name(&self) -> &'static str {
189 match self {
190 Self::GS1 => "GS1 AISBL",
191 Self::IANA => "Internet Assigned Numbers Authority",
192 Self::IEEE => "IEEE",
193 Self::IETF => "The Internet Engineering Task Force",
194 Self::ISO => "International Organization for Standardization",
195 Self::UN => "The United Nations",
196 }
197 }
198
199 ///
200 /// A URL for the agency.
201 ///
202 pub const fn url(&self) -> &'static str {
203 match self {
204 Self::GS1 => "https://www.gs1.org",
205 Self::IANA => "https://www.iana.org",
206 Self::IEEE => "https://www.ieee.org",
207 Self::IETF => "https://www.ietf.org",
208 Self::ISO => "https://www.iso.org",
209 Self::UN => "https://www.un.org",
210 }
211 }
212
213 ///
214 /// Some agencies are hierarchical, this returns a parent agency, if one exists.
215 ///
216 pub const fn parent_agency(&self) -> Option<&Self> {
217 None
218 }
219
220 ///
221 /// Return a [URN](https://www.rfc-editor.org/rfc/rfc8141) that identifies
222 /// an agency. The *namespace identifier* is "agency" (this is not a
223 /// [registered](https://www.iana.org/assignments/urn-namespaces/urn-namespaces.xhtml)
224 /// value) and the *namespace-specific string* is the agency and parent
225 /// agency set in order and separated with "/".
226 ///
227 /// # Example
228 ///
229 /// ```rust,ignore
230 /// let urn = Agency::CEFACT.agency_urn();
231 /// assert_eq!(&urn, "urn:agency:un/unece/cefact");
232 /// ```
233 ///
234 pub fn agency_urn(&self) -> String {
235 let mut path = vec![self.short_name().to_lowercase()];
236 let agency = self;
237 while let Some(agency) = agency.parent_agency() {
238 path.insert(0, agency.short_name().to_lowercase());
239 }
240 format!("urn:agency:{}", path.join("/"))
241 }
242}
243
244// ------------------------------------------------------------------------------------------------
245
246impl Standard {
247 ///
248 /// Create a new Standard **without** a long reference value.
249 ///
250 pub const fn new(
251 agency: Agency,
252 short_ref: &'static str,
253 title: &'static str,
254 url: &'static str,
255 ) -> Self {
256 Self {
257 agency,
258 short_ref,
259 long_ref: None,
260 title,
261 url,
262 }
263 }
264
265 ///
266 /// Create a new Standard **with** a long reference value.
267 ///
268 pub const fn new_with_long_ref(
269 agency: Agency,
270 short_ref: &'static str,
271 long_ref: &'static str,
272 title: &'static str,
273 url: &'static str,
274 ) -> Self {
275 Self {
276 agency,
277 short_ref,
278 long_ref: Some(long_ref),
279 title,
280 url,
281 }
282 }
283
284 ///
285 /// Return the [Agency] that controls this standard.
286 ///
287 pub const fn agency(&self) -> Agency {
288 self.agency
289 }
290
291 ///
292 /// Return the short reference, or number, of this standard.
293 ///
294 pub const fn short_ref(&self) -> &'static str {
295 self.short_ref
296 }
297
298 ///
299 /// Return a longer reference, if one exists, for this standard,
300 ///
301 pub const fn long_ref(&self) -> Option<&&'static str> {
302 self.long_ref.as_ref()
303 }
304
305 ///
306 /// Return the textual title of this standard.
307 ///
308 pub const fn title(&self) -> &'static str {
309 self.title
310 }
311
312 ///
313 /// Return the URL for this standard.
314 ///
315 pub const fn url(&self) -> &'static str {
316 self.url
317 }
318}
319
320// ------------------------------------------------------------------------------------------------
321
322impl std::fmt::Display for AgencyError {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 write!(
325 f,
326 "{}",
327 match self {
328 Self::FromStr(s) => format!("The short name {:?} is not a known agency", s),
329 }
330 )
331 }
332}
333
334impl std::error::Error for AgencyError {}
335
336// ------------------------------------------------------------------------------------------------
337// Modules
338// ------------------------------------------------------------------------------------------------