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}