Skip to main content

calendar_types/
set.rs

1//! Types for finite set values.
2
3use std::{convert::Infallible, fmt, str::FromStr};
4
5use strum::{Display, EnumString};
6
7/// A token which may be a statically known value of type `T` or else an unknown value of type
8/// `S`.
9///
10/// The principal use of this type is to allow finite enums to be extended with arbitrary values,
11/// most commonly some unknown string which is permissible but statically unknowable.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum Token<T, S> {
14    /// A statically known value.
15    Known(T),
16    /// An unknown or vendor-defined value.
17    Unknown(S),
18}
19
20impl<T: Default, S> Default for Token<T, S> {
21    fn default() -> Self {
22        Self::Known(Default::default())
23    }
24}
25
26impl<T, S> FromStr for Token<T, S>
27where
28    T: FromStr,
29    for<'a> &'a str: Into<S>,
30{
31    type Err = Infallible;
32
33    fn from_str(s: &str) -> Result<Self, Self::Err> {
34        match T::from_str(s) {
35            Ok(value) => Ok(Token::Known(value)),
36            Err(_) => Ok(Token::Unknown(s.into())),
37        }
38    }
39}
40
41impl<T, S> Token<T, S> {
42    /// Like [`FromStr`], but uses a fallible conversion for the unknown variant.
43    pub fn try_from_str<'a>(s: &'a str) -> Result<Self, <&'a str as TryInto<S>>::Error>
44    where
45        T: FromStr,
46        &'a str: TryInto<S>,
47    {
48        match T::from_str(s) {
49            Ok(value) => Ok(Token::Known(value)),
50            Err(_) => s.try_into().map(Token::Unknown),
51        }
52    }
53
54    /// Maps the unknown value of a `Token`, leaving known values unchanged.
55    pub fn map_unknown<U>(self, f: impl FnOnce(S) -> U) -> Token<T, U> {
56        match self {
57            Token::Known(t) => Token::Known(t),
58            Token::Unknown(s) => Token::Unknown(f(s)),
59        }
60    }
61}
62
63impl<T: fmt::Display, S: fmt::Display> fmt::Display for Token<T, S> {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Token::Known(t) => fmt::Display::fmt(t, f),
67            Token::Unknown(s) => fmt::Display::fmt(s, f),
68        }
69    }
70}
71
72/// A link relation from the [IANA Link Relations Registry].
73///
74/// [IANA Link Relations Registry]: https://www.iana.org/assignments/link-relations/
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, Display)]
76#[non_exhaustive]
77#[strum(ascii_case_insensitive)]
78pub enum LinkRelation {
79    /// Refers to a resource that is the subject of the link's context.
80    ///
81    /// Source: RFC 6903, Section 2.
82    #[strum(serialize = "about")]
83    About,
84    /// Asserts that the link target provides an access control description.
85    ///
86    /// Source: Web Access Control.
87    #[strum(serialize = "acl")]
88    Acl,
89    /// Refers to a substitute for this context.
90    ///
91    /// Source: HTML.
92    #[strum(serialize = "alternate")]
93    Alternate,
94    /// Refers to an AMP HTML version of the context.
95    ///
96    /// Source: AMP HTML.
97    #[strum(serialize = "amphtml")]
98    AmpHtml,
99    /// Refers to a list of published APIs.
100    ///
101    /// Source: RFC 9727.
102    #[strum(serialize = "api-catalog")]
103    ApiCatalog,
104    /// Refers to an appendix in a collection of resources.
105    ///
106    /// Source: HTML 4.01.
107    #[strum(serialize = "appendix")]
108    Appendix,
109    /// Refers to an icon for the context (synonym for `icon`).
110    ///
111    /// Source: Apple Web Application Configuration.
112    #[strum(serialize = "apple-touch-icon")]
113    AppleTouchIcon,
114    /// Refers to a launch screen for the context.
115    ///
116    /// Source: Apple Web Application Configuration.
117    #[strum(serialize = "apple-touch-startup-image")]
118    AppleTouchStartupImage,
119    /// Refers to a collection of records, documents, or other materials.
120    ///
121    /// Source: HTML 5.
122    #[strum(serialize = "archives")]
123    Archives,
124    /// Refers to the context's author.
125    ///
126    /// Source: HTML.
127    #[strum(serialize = "author")]
128    Author,
129    /// Identifies the entity that blocks access to a resource.
130    ///
131    /// Source: RFC 7725.
132    #[strum(serialize = "blocked-by")]
133    BlockedBy,
134    /// Gives a permanent link to use for bookmarking purposes.
135    ///
136    /// Source: HTML.
137    #[strum(serialize = "bookmark")]
138    Bookmark,
139    /// Links to a C2PA Manifest associated with the link context.
140    ///
141    /// Source: C2PA Technical Specification.
142    #[strum(serialize = "c2pa-manifest")]
143    C2paManifest,
144    /// Designates the preferred version of a resource.
145    ///
146    /// Source: RFC 6596.
147    #[strum(serialize = "canonical")]
148    Canonical,
149    /// Refers to a chapter in a collection of resources.
150    ///
151    /// Source: HTML 4.01.
152    #[strum(serialize = "chapter")]
153    Chapter,
154    /// Indicates the preferred URI for permanent citation.
155    ///
156    /// Source: RFC 8574.
157    #[strum(serialize = "cite-as")]
158    CiteAs,
159    /// Represents a collection resource for the context.
160    ///
161    /// Source: RFC 6573.
162    #[strum(serialize = "collection")]
163    Collection,
164    /// Refers to a compression dictionary for content encoding.
165    ///
166    /// Source: RFC 9842, Section 3.
167    #[strum(serialize = "compression-dictionary")]
168    CompressionDictionary,
169    /// Refers to a table of contents.
170    ///
171    /// Source: HTML 4.01.
172    #[strum(serialize = "contents")]
173    Contents,
174    /// The document was later converted from the linked document.
175    ///
176    /// Source: RFC 7991.
177    #[strum(serialize = "convertedfrom")]
178    ConvertedFrom,
179    /// Refers to a copyright statement for the context.
180    ///
181    /// Source: HTML 4.01.
182    #[strum(serialize = "copyright")]
183    Copyright,
184    /// Refers to a resource from which a submission form can be obtained.
185    ///
186    /// Source: RFC 6861.
187    #[strum(serialize = "create-form")]
188    CreateForm,
189    /// Refers to the most recent item(s) in a collection.
190    ///
191    /// Source: RFC 5005.
192    #[strum(serialize = "current")]
193    Current,
194    /// Links to documentation about deprecation of the context.
195    ///
196    /// Source: RFC 9745, Section 3.
197    #[strum(serialize = "deprecation")]
198    Deprecation,
199    /// Refers to a resource providing information about the context.
200    ///
201    /// Source: Protocol for Web Description Resources (POWDER).
202    #[strum(serialize = "describedby")]
203    DescribedBy,
204    /// The relationship asserts that resource A describes resource B.
205    ///
206    /// Source: RFC 6892.
207    #[strum(serialize = "describes")]
208    Describes,
209    /// Refers to a list of patent disclosures.
210    ///
211    /// Source: RFC 6579.
212    #[strum(serialize = "disclosure")]
213    Disclosure,
214    /// Indicates an origin whose resources should be resolved early.
215    ///
216    /// Source: HTML.
217    #[strum(serialize = "dns-prefetch")]
218    DnsPrefetch,
219    /// Refers to a resource with byte-for-byte identical representation.
220    ///
221    /// Source: RFC 6249.
222    #[strum(serialize = "duplicate")]
223    Duplicate,
224    /// Refers to a resource that can be used to edit the context.
225    ///
226    /// Source: RFC 5023.
227    #[strum(serialize = "edit")]
228    Edit,
229    /// Refers to a submission form for editing the context.
230    ///
231    /// Source: RFC 6861.
232    #[strum(serialize = "edit-form")]
233    EditForm,
234    /// Refers to a resource for editing media associated with the context.
235    ///
236    /// Source: RFC 5023.
237    #[strum(serialize = "edit-media")]
238    EditMedia,
239    /// Identifies a potentially large related resource.
240    ///
241    /// Source: RFC 4287.
242    #[strum(serialize = "enclosure")]
243    Enclosure,
244    /// Indicates a resource not part of the same site as the context.
245    ///
246    /// Source: HTML.
247    #[strum(serialize = "external")]
248    External,
249    /// Refers to the furthest preceding resource in a series.
250    ///
251    /// Source: RFC 8288.
252    #[strum(serialize = "first")]
253    First,
254    /// Refers to a resource providing IP geolocation information.
255    ///
256    /// Source: RFC 9877.
257    #[strum(serialize = "geofeed")]
258    Geofeed,
259    /// Refers to a glossary of terms.
260    ///
261    /// Source: HTML 4.01.
262    #[strum(serialize = "glossary")]
263    Glossary,
264    /// Refers to context-sensitive help.
265    ///
266    /// Source: HTML.
267    #[strum(serialize = "help")]
268    Help,
269    /// Refers to a resource hosted by the server.
270    ///
271    /// Source: RFC 6690.
272    #[strum(serialize = "hosts")]
273    Hosts,
274    /// Refers to a hub for subscribing to updates.
275    ///
276    /// Source: WebSub.
277    #[strum(serialize = "hub")]
278    Hub,
279    /// Refers to STUN/TURN server information for ICE connections.
280    ///
281    /// Source: RFC 9725.
282    #[strum(serialize = "ice-server")]
283    IceServer,
284    /// Refers to an icon representing the context.
285    ///
286    /// Source: HTML.
287    #[strum(serialize = "icon")]
288    Icon,
289    /// Refers to an index for the context.
290    ///
291    /// Source: HTML 4.01.
292    #[strum(serialize = "index")]
293    Index,
294    /// Refers to a time interval ending before the context interval.
295    ///
296    /// Source: Time Ontology in OWL, Section 4.2.21.
297    #[strum(serialize = "intervalafter")]
298    IntervalAfter,
299    /// Refers to a time interval starting after the context interval.
300    ///
301    /// Source: Time Ontology in OWL, Section 4.2.22.
302    #[strum(serialize = "intervalbefore")]
303    IntervalBefore,
304    /// Refers to a time interval contained within the context interval.
305    ///
306    /// Source: Time Ontology in OWL, Section 4.2.23.
307    #[strum(serialize = "intervalcontains")]
308    IntervalContains,
309    /// Refers to a time interval disjoint from the context interval.
310    ///
311    /// Source: Time Ontology in OWL, Section 4.2.24.
312    #[strum(serialize = "intervaldisjoint")]
313    IntervalDisjoint,
314    /// Refers to a time interval containing the context interval.
315    ///
316    /// Source: Time Ontology in OWL, Section 4.2.25.
317    #[strum(serialize = "intervalduring")]
318    IntervalDuring,
319    /// Refers to a time interval matching the context interval.
320    ///
321    /// Source: Time Ontology in OWL, Section 4.2.26.
322    #[strum(serialize = "intervalequals")]
323    IntervalEquals,
324    /// Refers to a time interval with matching end to the context.
325    ///
326    /// Source: Time Ontology in OWL, Section 4.2.27.
327    #[strum(serialize = "intervalfinishedby")]
328    IntervalFinishedBy,
329    /// Refers to a time interval finishing at the context end.
330    ///
331    /// Source: Time Ontology in OWL, Section 4.2.28.
332    #[strum(serialize = "intervalfinishes")]
333    IntervalFinishes,
334    /// Refers to a time interval encompassing the context interval.
335    ///
336    /// Source: Time Ontology in OWL, Section 4.2.29.
337    #[strum(serialize = "intervalin")]
338    IntervalIn,
339    /// Refers to a time interval starting at the context end.
340    ///
341    /// Source: Time Ontology in OWL, Section 4.2.30.
342    #[strum(serialize = "intervalmeets")]
343    IntervalMeets,
344    /// Refers to a time interval ending at the context start.
345    ///
346    /// Source: Time Ontology in OWL, Section 4.2.31.
347    #[strum(serialize = "intervalmetby")]
348    IntervalMetBy,
349    /// Refers to a time interval overlapping from before context start.
350    ///
351    /// Source: Time Ontology in OWL, Section 4.2.32.
352    #[strum(serialize = "intervaloverlappedby")]
353    IntervalOverlappedBy,
354    /// Refers to a time interval overlapping past the context end.
355    ///
356    /// Source: Time Ontology in OWL, Section 4.2.33.
357    #[strum(serialize = "intervaloverlaps")]
358    IntervalOverlaps,
359    /// Refers to a time interval with matching start to the context.
360    ///
361    /// Source: Time Ontology in OWL, Section 4.2.34.
362    #[strum(serialize = "intervalstartedby")]
363    IntervalStartedBy,
364    /// Refers to a time interval starting at the context start.
365    ///
366    /// Source: Time Ontology in OWL, Section 4.2.35.
367    #[strum(serialize = "intervalstarts")]
368    IntervalStarts,
369    /// Refers to a member of a collection.
370    ///
371    /// Source: RFC 6573.
372    #[strum(serialize = "item")]
373    Item,
374    /// Refers to the furthest following resource in a series.
375    ///
376    /// Source: RFC 8288.
377    #[strum(serialize = "last")]
378    Last,
379    /// Points to a resource with the latest/current version.
380    ///
381    /// Source: RFC 5829.
382    #[strum(serialize = "latest-version")]
383    LatestVersion,
384    /// Refers to a license associated with the context.
385    ///
386    /// Source: RFC 4946.
387    #[strum(serialize = "license")]
388    License,
389    /// Refers to a set of links with the context as a participant.
390    ///
391    /// Source: RFC 9264.
392    #[strum(serialize = "linkset")]
393    Linkset,
394    /// Refers to further information as a Link-based Resource Descriptor.
395    ///
396    /// Source: RFC 6415.
397    #[strum(serialize = "lrdd")]
398    Lrdd,
399    /// Links to a manifest file for the context.
400    ///
401    /// Source: Web App Manifest.
402    #[strum(serialize = "manifest")]
403    Manifest,
404    /// Refers to a mask applicable to the icon.
405    ///
406    /// Source: Creating Pinned Tab Icons (Apple).
407    #[strum(serialize = "mask-icon")]
408    MaskIcon,
409    /// Refers to a resource about the author of the link's context.
410    ///
411    /// Source: Microformats rel=me.
412    #[strum(serialize = "me")]
413    Me,
414    /// Refers to a feed of personalized media recommendations.
415    ///
416    /// Source: Media Feeds.
417    #[strum(serialize = "media-feed")]
418    MediaFeed,
419    /// Indicates a fixed resource representing a prior state.
420    ///
421    /// Source: RFC 7089.
422    #[strum(serialize = "memento")]
423    Memento,
424    /// Refers to the context's Micropub endpoint.
425    ///
426    /// Source: Micropub.
427    #[strum(serialize = "micropub")]
428    Micropub,
429    /// Refers to a module for preemptive fetching.
430    ///
431    /// Source: HTML.
432    #[strum(serialize = "modulepreload")]
433    ModulePreload,
434    /// Refers to a resource for monitoring changes.
435    ///
436    /// Source: RFC 5989.
437    #[strum(serialize = "monitor")]
438    Monitor,
439    /// Refers to a resource for monitoring a group of resources.
440    ///
441    /// Source: RFC 5989.
442    #[strum(serialize = "monitor-group")]
443    MonitorGroup,
444    /// Refers to the next resource in a series.
445    ///
446    /// Source: HTML.
447    #[strum(serialize = "next")]
448    Next,
449    /// Refers to the immediately following archive resource.
450    ///
451    /// Source: RFC 5005.
452    #[strum(serialize = "next-archive")]
453    NextArchive,
454    /// Indicates the author does not endorse the link target.
455    ///
456    /// Source: HTML.
457    #[strum(serialize = "nofollow")]
458    NoFollow,
459    /// Indicates the new context should not be an auxiliary browsing context.
460    ///
461    /// Source: HTML.
462    #[strum(serialize = "noopener")]
463    NoOpener,
464    /// Indicates no referrer information should be leaked.
465    ///
466    /// Source: HTML.
467    #[strum(serialize = "noreferrer")]
468    NoReferrer,
469    /// Indicates the new context is an auxiliary browsing context.
470    ///
471    /// Source: HTML.
472    #[strum(serialize = "opener")]
473    Opener,
474    /// Refers to a server for OpenID Authentication identity assertion.
475    ///
476    /// Source: OpenID Authentication 2.0.
477    #[strum(serialize = "openid2.local_id")]
478    OpenId2LocalId,
479    /// Refers to a server accepting OpenID Authentication protocol messages.
480    ///
481    /// Source: OpenID Authentication 2.0.
482    #[strum(serialize = "openid2.provider")]
483    OpenId2Provider,
484    /// Refers to an Original Resource in a Memento context.
485    ///
486    /// Source: RFC 7089.
487    #[strum(serialize = "original")]
488    Original,
489    /// Refers to a P3P privacy policy.
490    ///
491    /// Source: The Platform for Privacy Preferences 1.0.
492    #[strum(serialize = "p3pv1")]
493    P3pv1,
494    /// Indicates a resource where payment is accepted.
495    ///
496    /// Source: RFC 8288.
497    #[strum(serialize = "payment")]
498    Payment,
499    /// Refers to a Pingback resource address.
500    ///
501    /// Source: Pingback 1.0.
502    #[strum(serialize = "pingback")]
503    Pingback,
504    /// Indicates an origin to which early connection should be made.
505    ///
506    /// Source: HTML.
507    #[strum(serialize = "preconnect")]
508    Preconnect,
509    /// Points to a resource with a predecessor version in history.
510    ///
511    /// Source: RFC 5829.
512    #[strum(serialize = "predecessor-version")]
513    PredecessorVersion,
514    /// Indicates a resource that should be fetched early.
515    ///
516    /// Source: HTML.
517    #[strum(serialize = "prefetch")]
518    Prefetch,
519    /// Indicates a resource that should be loaded early without blocking.
520    ///
521    /// Source: Preload.
522    #[strum(serialize = "preload")]
523    Preload,
524    /// Indicates a resource to fetch and execute for next navigation.
525    ///
526    /// Source: Resource Hints.
527    #[strum(serialize = "prerender")]
528    Prerender,
529    /// Refers to the previous resource in a series.
530    ///
531    /// Source: HTML.
532    #[strum(serialize = "prev")]
533    Prev,
534    /// Refers to a preview of the context.
535    ///
536    /// Source: RFC 6903, Section 3.
537    #[strum(serialize = "preview")]
538    Preview,
539    /// Refers to the previous resource in a series (synonym for `prev`).
540    ///
541    /// Source: HTML 4.01.
542    #[strum(serialize = "previous")]
543    Previous,
544    /// Refers to the immediately preceding archive resource.
545    ///
546    /// Source: RFC 5005.
547    #[strum(serialize = "prev-archive")]
548    PrevArchive,
549    /// Refers to a privacy policy for the context.
550    ///
551    /// Source: RFC 6903, Section 4.
552    #[strum(serialize = "privacy-policy")]
553    PrivacyPolicy,
554    /// Identifies a resource conforming to a profile.
555    ///
556    /// Source: RFC 6906.
557    #[strum(serialize = "profile")]
558    Profile,
559    /// Links to the publication manifest with metadata.
560    ///
561    /// Source: Publication Manifest.
562    #[strum(serialize = "publication")]
563    Publication,
564    /// Used in RDAP RIR search to filter for "active" objects.
565    ///
566    /// Source: RFC 9910.
567    #[strum(serialize = "rdap-active")]
568    RdapActive,
569    /// Used in RDAP to refer to child objects without children.
570    ///
571    /// Source: RFC 9910.
572    #[strum(serialize = "rdap-bottom")]
573    RdapBottom,
574    /// Used in RDAP to refer to a set of child objects.
575    ///
576    /// Source: RFC 9910.
577    #[strum(serialize = "rdap-down")]
578    RdapDown,
579    /// Used in RDAP to refer to the topmost parent in a hierarchy.
580    ///
581    /// Source: RFC 9910.
582    #[strum(serialize = "rdap-top")]
583    RdapTop,
584    /// Used in RDAP to refer to a parent object in a hierarchy.
585    ///
586    /// Source: RFC 9910.
587    #[strum(serialize = "rdap-up")]
588    RdapUp,
589    /// Identifies a related resource.
590    ///
591    /// Source: RFC 4287.
592    #[strum(serialize = "related")]
593    Related,
594    /// Identifies a resource replying to the context.
595    ///
596    /// Source: RFC 4685.
597    #[strum(serialize = "replies")]
598    Replies,
599    /// Identifies the root of a RESTCONF API.
600    ///
601    /// Source: RFC 8040.
602    #[strum(serialize = "restconf")]
603    Restconf,
604    /// Refers to an input value for a rule instance.
605    ///
606    /// Source: OCF Core Optional 2.2.0.
607    #[strum(serialize = "ruleinput")]
608    RuleInput,
609    /// Refers to a resource for searching the context.
610    ///
611    /// Source: OpenSearch.
612    #[strum(serialize = "search")]
613    Search,
614    /// Refers to a section in a collection of resources.
615    ///
616    /// Source: HTML 4.01.
617    #[strum(serialize = "section")]
618    Section,
619    /// Conveys an identifier for the link's context.
620    ///
621    /// Source: RFC 4287.
622    ///
623    /// Note: Named `Self_` to avoid conflict with the Rust keyword.
624    #[strum(serialize = "self")]
625    Self_,
626    /// Indicates a URI for a service document.
627    ///
628    /// Source: RFC 5023.
629    #[strum(serialize = "service")]
630    Service,
631    /// Identifies a service description for machines.
632    ///
633    /// Source: RFC 8631.
634    #[strum(serialize = "service-desc")]
635    ServiceDesc,
636    /// Identifies a service documentation for humans.
637    ///
638    /// Source: RFC 8631.
639    #[strum(serialize = "service-doc")]
640    ServiceDoc,
641    /// Identifies general metadata for machines.
642    ///
643    /// Source: RFC 8631.
644    #[strum(serialize = "service-meta")]
645    ServiceMeta,
646    /// Refers to a SIP trunking capability set document.
647    ///
648    /// Source: RFC 9409.
649    #[strum(serialize = "sip-trunking-capability")]
650    SipTrunkingCapability,
651    /// Indicates a sponsored resource within the context.
652    ///
653    /// Source: Qualify Outbound Links (Google).
654    #[strum(serialize = "sponsored")]
655    Sponsored,
656    /// Refers to the first resource in a collection.
657    ///
658    /// Source: HTML 4.01.
659    #[strum(serialize = "start")]
660    Start,
661    /// Identifies a resource representing the context's status.
662    ///
663    /// Source: RFC 8631.
664    #[strum(serialize = "status")]
665    Status,
666    /// Refers to a stylesheet.
667    ///
668    /// Source: HTML.
669    #[strum(serialize = "stylesheet")]
670    Stylesheet,
671    /// Refers to a subsection in a collection of resources.
672    ///
673    /// Source: HTML 4.01.
674    #[strum(serialize = "subsection")]
675    Subsection,
676    /// Points to a resource with a successor version in history.
677    ///
678    /// Source: RFC 5829.
679    #[strum(serialize = "successor-version")]
680    SuccessorVersion,
681    /// Identifies a resource with retirement policy information.
682    ///
683    /// Source: RFC 8594.
684    #[strum(serialize = "sunset")]
685    Sunset,
686    /// Refers to a tag applying to the document.
687    ///
688    /// Source: HTML.
689    #[strum(serialize = "tag")]
690    Tag,
691    /// Refers to terms of service for the context.
692    ///
693    /// Source: RFC 6903, Section 5.
694    #[strum(serialize = "terms-of-service")]
695    TermsOfService,
696    /// Refers to a TimeGate for an Original Resource.
697    ///
698    /// Source: RFC 7089.
699    #[strum(serialize = "timegate")]
700    TimeGate,
701    /// Refers to a TimeMap for an Original Resource.
702    ///
703    /// Source: RFC 7089.
704    #[strum(serialize = "timemap")]
705    TimeMap,
706    /// Refers to a resource identifying the abstract semantic type.
707    ///
708    /// Source: RFC 6903, Section 6.
709    #[strum(serialize = "type")]
710    Type,
711    /// Indicates user-generated content within the context.
712    ///
713    /// Source: Qualify Outbound Links (Google).
714    #[strum(serialize = "ugc")]
715    Ugc,
716    /// Refers to a parent document in a hierarchy.
717    ///
718    /// Source: RFC 8288.
719    #[strum(serialize = "up")]
720    Up,
721    /// Points to a version history resource.
722    ///
723    /// Source: RFC 5829.
724    #[strum(serialize = "version-history")]
725    VersionHistory,
726    /// Identifies a source of information in the context.
727    ///
728    /// Source: RFC 4287.
729    #[strum(serialize = "via")]
730    Via,
731    /// Identifies a target supporting the Webmention protocol.
732    ///
733    /// Source: Webmention.
734    #[strum(serialize = "webmention")]
735    Webmention,
736    /// Points to a working copy for the resource.
737    ///
738    /// Source: RFC 5829.
739    #[strum(serialize = "working-copy")]
740    WorkingCopy,
741    /// Points to the versioned resource source of a working copy.
742    ///
743    /// Source: RFC 5829.
744    #[strum(serialize = "working-copy-of")]
745    WorkingCopyOf,
746}
747
748/// A location type from the [IANA Location Types Registry].
749///
750/// [IANA Location Types Registry]: https://www.iana.org/assignments/location-type-registry/
751#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, Display)]
752#[non_exhaustive]
753#[strum(ascii_case_insensitive)]
754pub enum LocationType {
755    /// A device used for flight (airplane, helicopter, glider, balloon, etc.).
756    ///
757    /// Source: RFC 4589.
758    #[strum(serialize = "aircraft")]
759    Aircraft,
760    /// A place from which aircraft operate (airport, heliport).
761    ///
762    /// Source: RFC 4589.
763    #[strum(serialize = "airport")]
764    Airport,
765    /// Enclosed area used for sports events.
766    ///
767    /// Source: RFC 4589.
768    #[strum(serialize = "arena")]
769    Arena,
770    /// An automotive vehicle designed for passenger transportation.
771    ///
772    /// Source: RFC 4589.
773    #[strum(serialize = "automobile")]
774    Automobile,
775    /// Business establishment for financial services.
776    ///
777    /// Source: RFC 4589.
778    #[strum(serialize = "bank")]
779    Bank,
780    /// A bar or saloon.
781    ///
782    /// Source: RFC 4589.
783    #[strum(serialize = "bar")]
784    Bar,
785    /// A two-wheeled pedal-propelled vehicle.
786    ///
787    /// Source: RFC 4589.
788    #[strum(serialize = "bicycle")]
789    Bicycle,
790    /// A large motor vehicle designed to carry passengers.
791    ///
792    /// Source: RFC 4589.
793    #[strum(serialize = "bus")]
794    Bus,
795    /// Terminal that serves bus passengers (bus depot, bus terminal).
796    ///
797    /// Source: RFC 4589.
798    #[strum(serialize = "bus-station")]
799    BusStation,
800    /// A small informal establishment serving refreshments; coffee shop.
801    ///
802    /// Source: RFC 4589.
803    #[strum(serialize = "cafe")]
804    Cafe,
805    /// An area used for camping, often with facilities for tents or RVs.
806    ///
807    /// Source: NH Division of Emergency Services and Communications.
808    #[strum(serialize = "campground")]
809    Campground,
810    /// A facility providing care services (nursing home, assisted living).
811    ///
812    /// Source: NH Division of Emergency Services and Communications.
813    #[strum(serialize = "care-facility")]
814    CareFacility,
815    /// Academic classroom or lecture hall.
816    ///
817    /// Source: RFC 4589.
818    #[strum(serialize = "classroom")]
819    Classroom,
820    /// Dance club, nightclub, or discotheque.
821    ///
822    /// Source: RFC 4589.
823    #[strum(serialize = "club")]
824    Club,
825    /// Construction site.
826    ///
827    /// Source: RFC 4589.
828    #[strum(serialize = "construction")]
829    Construction,
830    /// Convention center or exhibition hall.
831    ///
832    /// Source: RFC 4589.
833    #[strum(serialize = "convention-center")]
834    ConventionCenter,
835    /// A building or housing unit that is not attached to other buildings.
836    ///
837    /// Source: NH Division of Emergency Services and Communications.
838    #[strum(serialize = "detached-unit")]
839    DetachedUnit,
840    /// A facility housing firefighting equipment and personnel.
841    ///
842    /// Source: NH Division of Emergency Services and Communications.
843    #[strum(serialize = "fire-station")]
844    FireStation,
845    /// Government building (legislative, executive, judicial, police, military).
846    ///
847    /// Source: RFC 4589.
848    #[strum(serialize = "government")]
849    Government,
850    /// Hospital, hospice, medical clinic, mental institution, or doctor's office.
851    ///
852    /// Source: RFC 4589.
853    #[strum(serialize = "hospital")]
854    Hospital,
855    /// Hotel, motel, inn, or other lodging establishment.
856    ///
857    /// Source: RFC 4589.
858    #[strum(serialize = "hotel")]
859    Hotel,
860    /// Industrial setting (manufacturing floor, power plant).
861    ///
862    /// Source: RFC 4589.
863    #[strum(serialize = "industrial")]
864    Industrial,
865    /// A location identified by a landmark or notable address.
866    ///
867    /// Source: NH Division of Emergency Services and Communications.
868    #[strum(serialize = "landmark-address")]
869    LandmarkAddress,
870    /// Library or other public place for literary and artistic materials.
871    ///
872    /// Source: RFC 4589.
873    #[strum(serialize = "library")]
874    Library,
875    /// A two-wheeled automotive vehicle, including a scooter.
876    ///
877    /// Source: RFC 4589.
878    #[strum(serialize = "motorcycle")]
879    Motorcycle,
880    /// A garage owned or operated by a municipality.
881    ///
882    /// Source: NH Division of Emergency Services and Communications.
883    #[strum(serialize = "municipal-garage")]
884    MunicipalGarage,
885    /// A building housing collections of artifacts or specimens.
886    ///
887    /// Source: NENA-STA-004.1.1-2025, NG9-1-1 CLDXF-US.
888    #[strum(serialize = "museum")]
889    Museum,
890    /// Business setting, such as an office.
891    ///
892    /// Source: RFC 4589.
893    #[strum(serialize = "office")]
894    Office,
895    /// A place without a registered place type representation.
896    ///
897    /// Source: RFC 4589.
898    #[strum(serialize = "other")]
899    Other,
900    /// Outside a building, in the open air (park, city streets).
901    ///
902    /// Source: RFC 4589.
903    #[strum(serialize = "outdoors")]
904    Outdoors,
905    /// A parking lot or parking garage.
906    ///
907    /// Source: RFC 4589.
908    #[strum(serialize = "parking")]
909    Parking,
910    /// A public telephone booth or kiosk.
911    ///
912    /// Source: NH Division of Emergency Services and Communications.
913    #[strum(serialize = "phone-box")]
914    PhoneBox,
915    /// A religious site (church, chapel, mosque, shrine, synagogue, temple).
916    ///
917    /// Source: RFC 4589.
918    #[strum(serialize = "place-of-worship")]
919    PlaceOfWorship,
920    /// A post office or mail facility.
921    ///
922    /// Source: NH Division of Emergency Services and Communications.
923    #[strum(serialize = "post-office")]
924    PostOffice,
925    /// Correctional institution (prison, penitentiary, jail, brig).
926    ///
927    /// Source: RFC 4589.
928    #[strum(serialize = "prison")]
929    Prison,
930    /// Public area (shopping mall, street, park, public building, conveyance).
931    ///
932    /// Source: RFC 4589.
933    #[strum(serialize = "public")]
934    Public,
935    /// Any form of public transport (aircraft, bus, train, ship).
936    ///
937    /// Source: RFC 4589.
938    #[strum(serialize = "public-transport")]
939    PublicTransport,
940    /// A private or residential setting.
941    ///
942    /// Source: RFC 4589.
943    #[strum(serialize = "residence")]
944    Residence,
945    /// Restaurant, coffee shop, or other public dining establishment.
946    ///
947    /// Source: RFC 4589.
948    #[strum(serialize = "restaurant")]
949    Restaurant,
950    /// School or university property.
951    ///
952    /// Source: RFC 4589.
953    #[strum(serialize = "school")]
954    School,
955    /// Shopping mall or area with stores accessible by common passageways.
956    ///
957    /// Source: RFC 4589.
958    #[strum(serialize = "shopping-area")]
959    ShoppingArea,
960    /// Large structure for sports events, including a racetrack.
961    ///
962    /// Source: RFC 4589.
963    #[strum(serialize = "stadium")]
964    Stadium,
965    /// Place where merchandise is offered for sale; a shop.
966    ///
967    /// Source: RFC 4589.
968    #[strum(serialize = "store")]
969    Store,
970    /// A public thoroughfare (avenue, street, alley, lane, road, sidewalk).
971    ///
972    /// Source: RFC 4589.
973    #[strum(serialize = "street")]
974    Street,
975    /// Theater, auditorium, movie theater, or similar presentation facility.
976    ///
977    /// Source: RFC 4589.
978    #[strum(serialize = "theater")]
979    Theater,
980    /// A booth or station for collecting tolls.
981    ///
982    /// Source: NH Division of Emergency Services and Communications.
983    #[strum(serialize = "toll-booth")]
984    TollBooth,
985    /// A building housing local government offices.
986    ///
987    /// Source: NH Division of Emergency Services and Communications.
988    #[strum(serialize = "town-hall")]
989    TownHall,
990    /// Train, monorail, maglev, cable car, or similar conveyance.
991    ///
992    /// Source: RFC 4589.
993    #[strum(serialize = "train")]
994    Train,
995    /// Terminal where trains load or unload passengers or goods.
996    ///
997    /// Source: RFC 4589.
998    #[strum(serialize = "train-station")]
999    TrainStation,
1000    /// An automotive vehicle for hauling goods rather than people.
1001    ///
1002    /// Source: RFC 4589.
1003    #[strum(serialize = "truck")]
1004    Truck,
1005    /// In a land-, water-, or aircraft that is in motion.
1006    ///
1007    /// Source: RFC 4589.
1008    #[strum(serialize = "underway")]
1009    Underway,
1010    /// The type of place is unknown.
1011    ///
1012    /// Source: RFC 4589.
1013    #[strum(serialize = "unknown")]
1014    Unknown,
1015    /// A utility box or cabinet housing utility equipment.
1016    ///
1017    /// Source: NH Division of Emergency Services and Communications.
1018    #[strum(serialize = "utilitybox")]
1019    UtilityBox,
1020    /// Place for storing goods (storehouse, self-storage facility).
1021    ///
1022    /// Source: RFC 4589.
1023    #[strum(serialize = "warehouse")]
1024    Warehouse,
1025    /// A facility for processing or transferring waste materials.
1026    ///
1027    /// Source: NH Division of Emergency Services and Communications.
1028    #[strum(serialize = "waste-transfer-facility")]
1029    WasteTransferFacility,
1030    /// In, on, or above bodies of water (ocean, lake, river, canal).
1031    ///
1032    /// Source: RFC 4589.
1033    #[strum(serialize = "water")]
1034    Water,
1035    /// A facility for water treatment or distribution.
1036    ///
1037    /// Source: NH Division of Emergency Services and Communications.
1038    #[strum(serialize = "water-facility")]
1039    WaterFacility,
1040    /// A vessel for travel on water (boat, ship).
1041    ///
1042    /// Source: RFC 4589.
1043    #[strum(serialize = "watercraft")]
1044    Watercraft,
1045    /// A camp providing programs and activities for youth.
1046    ///
1047    /// Source: NH Division of Emergency Services and Communications.
1048    #[strum(serialize = "youth-camp")]
1049    YouthCamp,
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054    use super::*;
1055    use std::str::FromStr;
1056
1057    #[test]
1058    fn link_relation_from_str_lowercase() {
1059        assert_eq!(
1060            LinkRelation::from_str("about").unwrap(),
1061            LinkRelation::About
1062        );
1063        assert_eq!(
1064            LinkRelation::from_str("api-catalog").unwrap(),
1065            LinkRelation::ApiCatalog
1066        );
1067        assert_eq!(LinkRelation::from_str("self").unwrap(), LinkRelation::Self_);
1068        assert_eq!(
1069            LinkRelation::from_str("openid2.provider").unwrap(),
1070            LinkRelation::OpenId2Provider
1071        );
1072    }
1073
1074    #[test]
1075    fn link_relation_from_str_case_insensitive() {
1076        assert_eq!(
1077            LinkRelation::from_str("ABOUT").unwrap(),
1078            LinkRelation::About
1079        );
1080        assert_eq!(
1081            LinkRelation::from_str("About").unwrap(),
1082            LinkRelation::About
1083        );
1084        assert_eq!(
1085            LinkRelation::from_str("API-CATALOG").unwrap(),
1086            LinkRelation::ApiCatalog
1087        );
1088        assert_eq!(
1089            LinkRelation::from_str("Api-Catalog").unwrap(),
1090            LinkRelation::ApiCatalog
1091        );
1092        assert_eq!(LinkRelation::from_str("SELF").unwrap(), LinkRelation::Self_);
1093        assert_eq!(
1094            LinkRelation::from_str("OPENID2.PROVIDER").unwrap(),
1095            LinkRelation::OpenId2Provider
1096        );
1097    }
1098
1099    #[test]
1100    fn link_relation_from_str_rejects_invalid() {
1101        assert!(LinkRelation::from_str("").is_err());
1102        assert!(LinkRelation::from_str("not-a-relation").is_err());
1103        assert!(LinkRelation::from_str("foobar").is_err());
1104        assert!(LinkRelation::from_str("about ").is_err());
1105        assert!(LinkRelation::from_str(" about").is_err());
1106    }
1107
1108    #[test]
1109    fn link_relation_from_str_rejects_wrong_format() {
1110        // Snake case is NOT a valid alternative to kebab case
1111        assert!(LinkRelation::from_str("api_catalog").is_err());
1112        assert!(LinkRelation::from_str("cite_as").is_err());
1113        assert!(LinkRelation::from_str("edit_form").is_err());
1114
1115        // PascalCase variant names are NOT valid
1116        assert!(LinkRelation::from_str("ApiCatalog").is_err());
1117        assert!(LinkRelation::from_str("CiteAs").is_err());
1118        assert!(LinkRelation::from_str("EditForm").is_err());
1119
1120        // camelCase is NOT valid
1121        assert!(LinkRelation::from_str("apiCatalog").is_err());
1122        assert!(LinkRelation::from_str("citeAs").is_err());
1123    }
1124
1125    #[test]
1126    fn location_type_from_str_lowercase() {
1127        assert_eq!(
1128            LocationType::from_str("aircraft").unwrap(),
1129            LocationType::Aircraft
1130        );
1131        assert_eq!(
1132            LocationType::from_str("bus-station").unwrap(),
1133            LocationType::BusStation
1134        );
1135        assert_eq!(
1136            LocationType::from_str("place-of-worship").unwrap(),
1137            LocationType::PlaceOfWorship
1138        );
1139        // Note: "utilitybox" has no hyphen per IANA registry
1140        assert_eq!(
1141            LocationType::from_str("utilitybox").unwrap(),
1142            LocationType::UtilityBox
1143        );
1144    }
1145
1146    #[test]
1147    fn location_type_from_str_case_insensitive() {
1148        assert_eq!(
1149            LocationType::from_str("AIRCRAFT").unwrap(),
1150            LocationType::Aircraft
1151        );
1152        assert_eq!(
1153            LocationType::from_str("Aircraft").unwrap(),
1154            LocationType::Aircraft
1155        );
1156        assert_eq!(
1157            LocationType::from_str("BUS-STATION").unwrap(),
1158            LocationType::BusStation
1159        );
1160        assert_eq!(
1161            LocationType::from_str("Bus-Station").unwrap(),
1162            LocationType::BusStation
1163        );
1164        assert_eq!(
1165            LocationType::from_str("PLACE-OF-WORSHIP").unwrap(),
1166            LocationType::PlaceOfWorship
1167        );
1168        assert_eq!(
1169            LocationType::from_str("UTILITYBOX").unwrap(),
1170            LocationType::UtilityBox
1171        );
1172    }
1173
1174    #[test]
1175    fn location_type_from_str_rejects_invalid() {
1176        assert!(LocationType::from_str("").is_err());
1177        assert!(LocationType::from_str("not-a-location").is_err());
1178        assert!(LocationType::from_str("foobar").is_err());
1179        assert!(LocationType::from_str("airport ").is_err());
1180        assert!(LocationType::from_str(" airport").is_err());
1181    }
1182
1183    #[test]
1184    fn location_type_from_str_rejects_wrong_format() {
1185        // Snake case is NOT a valid alternative to kebab case
1186        assert!(LocationType::from_str("bus_station").is_err());
1187        assert!(LocationType::from_str("fire_station").is_err());
1188        assert!(LocationType::from_str("place_of_worship").is_err());
1189
1190        // PascalCase variant names are NOT valid
1191        assert!(LocationType::from_str("BusStation").is_err());
1192        assert!(LocationType::from_str("FireStation").is_err());
1193        assert!(LocationType::from_str("PlaceOfWorship").is_err());
1194
1195        // camelCase is NOT valid
1196        assert!(LocationType::from_str("busStation").is_err());
1197        assert!(LocationType::from_str("fireStation").is_err());
1198
1199        // "utility-box" with hyphen is NOT valid (registry uses "utilitybox")
1200        assert!(LocationType::from_str("utility-box").is_err());
1201    }
1202}