aster_forge_webdav/
extension.rs

1//! Typed `WebDAV` RFC extension packages and their static discovery descriptors.
2
3use crate::capability::DavResourceState;
4use crate::request::{DavMethod, DavMethodSet};
5
6/// Closed set of RFC extension packages understood by the protocol engine.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum DavExtensionPackage {
9    AccessControl,
10    VersionControl,
11    CheckoutInPlace,
12    VersionHistory,
13    Workspace,
14    Update,
15    Label,
16    WorkingResource,
17    Merge,
18    Baseline,
19    Activity,
20    VersionControlledCollection,
21    Search,
22    Quota,
23    CollectionSync,
24    ExtendedMkcol,
25    CurrentPrincipal,
26    OrderedCollections,
27    RedirectReferences,
28    Bindings,
29    AddMember,
30    Prefer,
31}
32
33impl DavExtensionPackage {
34    /// Packages in canonical discovery and validation order.
35    pub const ALL: [Self; 22] = [
36        Self::AccessControl,
37        Self::VersionControl,
38        Self::CheckoutInPlace,
39        Self::VersionHistory,
40        Self::Workspace,
41        Self::Update,
42        Self::Label,
43        Self::WorkingResource,
44        Self::Merge,
45        Self::Baseline,
46        Self::Activity,
47        Self::VersionControlledCollection,
48        Self::Search,
49        Self::Quota,
50        Self::CollectionSync,
51        Self::ExtendedMkcol,
52        Self::CurrentPrincipal,
53        Self::OrderedCollections,
54        Self::RedirectReferences,
55        Self::Bindings,
56        Self::AddMember,
57        Self::Prefer,
58    ];
59
60    const fn index(self) -> u32 {
61        match self {
62            Self::AccessControl => 0,
63            Self::VersionControl => 1,
64            Self::CheckoutInPlace => 2,
65            Self::VersionHistory => 3,
66            Self::Workspace => 4,
67            Self::Update => 5,
68            Self::Label => 6,
69            Self::WorkingResource => 7,
70            Self::Merge => 8,
71            Self::Baseline => 9,
72            Self::Activity => 10,
73            Self::VersionControlledCollection => 11,
74            Self::Search => 12,
75            Self::Quota => 13,
76            Self::CollectionSync => 14,
77            Self::ExtendedMkcol => 15,
78            Self::CurrentPrincipal => 16,
79            Self::OrderedCollections => 17,
80            Self::RedirectReferences => 18,
81            Self::Bindings => 19,
82            Self::AddMember => 20,
83            Self::Prefer => 21,
84        }
85    }
86
87    /// Returns the static RFC descriptor for this package.
88    #[must_use]
89    pub const fn descriptor(self) -> &'static DavExtensionDescriptor {
90        &DESCRIPTORS[self.index() as usize]
91    }
92}
93
94/// Allocation-free set of extension packages.
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
96pub struct DavExtensionSet(u32);
97
98impl DavExtensionSet {
99    #[must_use]
100    pub const fn empty() -> Self {
101        Self(0)
102    }
103
104    #[must_use]
105    pub const fn from_packages(packages: &[DavExtensionPackage]) -> Self {
106        let mut set = Self::empty();
107        let mut index = 0;
108        while index < packages.len() {
109            set = set.with(packages[index]);
110            index += 1;
111        }
112        set
113    }
114
115    #[must_use]
116    pub const fn with(self, package: DavExtensionPackage) -> Self {
117        Self(self.0 | (1u32 << package.index()))
118    }
119
120    #[must_use]
121    pub const fn union(self, other: Self) -> Self {
122        Self(self.0 | other.0)
123    }
124
125    #[must_use]
126    pub const fn contains(self, package: DavExtensionPackage) -> bool {
127        self.0 & (1u32 << package.index()) != 0
128    }
129
130    #[must_use]
131    pub const fn contains_all(self, required: Self) -> bool {
132        required.0 & !self.0 == 0
133    }
134
135    #[must_use]
136    pub const fn is_subset_of(self, maximum: Self) -> bool {
137        maximum.contains_all(self)
138    }
139
140    #[must_use]
141    pub const fn is_empty(self) -> bool {
142        self.0 == 0
143    }
144
145    #[must_use]
146    pub const fn iter(self) -> DavExtensionSetIter {
147        DavExtensionSetIter {
148            set: self,
149            index: 0,
150        }
151    }
152}
153
154/// Iterator over packages in canonical order.
155#[derive(Debug, Clone)]
156pub struct DavExtensionSetIter {
157    set: DavExtensionSet,
158    index: usize,
159}
160
161impl Iterator for DavExtensionSetIter {
162    type Item = DavExtensionPackage;
163
164    fn next(&mut self) -> Option<Self::Item> {
165        while self.index < DavExtensionPackage::ALL.len() {
166            let package = DavExtensionPackage::ALL[self.index];
167            self.index += 1;
168            if self.set.contains(package) {
169                return Some(package);
170            }
171        }
172        None
173    }
174}
175
176/// Resource states to which a package or method contribution can apply.
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub struct DavResourceStateSet(u16);
179
180impl DavResourceStateSet {
181    #[must_use]
182    pub const fn from_states(states: &[DavResourceState]) -> Self {
183        let mut bits = 0u16;
184        let mut index = 0;
185        while index < states.len() {
186            bits |= 1u16 << resource_index(states[index]);
187            index += 1;
188        }
189        Self(bits)
190    }
191
192    #[must_use]
193    pub const fn contains(self, state: DavResourceState) -> bool {
194        self.0 & (1u16 << resource_index(state)) != 0
195    }
196
197    #[must_use]
198    pub const fn union(self, other: Self) -> Self {
199        Self(self.0 | other.0)
200    }
201}
202
203const fn resource_index(state: DavResourceState) -> u32 {
204    match state {
205        DavResourceState::Unmapped => 0,
206        DavResourceState::File => 1,
207        DavResourceState::Collection => 2,
208        DavResourceState::MountRoot => 3,
209        DavResourceState::Principal => 4,
210        DavResourceState::RedirectReference => 5,
211        DavResourceState::AddMemberEndpoint => 6,
212    }
213}
214
215/// Body contract contributed by one extension method.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum DavExtensionBodyKind {
218    Xml,
219    Stream,
220}
221
222/// One target-aware method contribution from an RFC package.
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub struct DavExtensionMethod {
225    pub method: DavMethod,
226    pub resources: DavResourceStateSet,
227    pub body: DavExtensionBodyKind,
228}
229
230macro_rules! protocol_enum {
231    (
232        $(#[$enum_meta:meta])*
233        pub enum $name:ident {
234            $($variant:ident => $local_name:literal),+ $(,)?
235        }
236    ) => {
237        $(#[$enum_meta])*
238        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239        #[repr(u8)]
240        pub enum $name {
241            $($variant),+
242        }
243
244        impl $name {
245            /// Number of variants in this protocol enum.
246            pub const COUNT: usize = [$(Self::$variant),+].len();
247
248            /// Values in canonical discovery and parsing order.
249            pub const ALL: [Self; Self::COUNT] = [$(Self::$variant),+];
250
251            pub(crate) const fn index(self) -> usize {
252                self as usize
253            }
254
255            /// DAV namespace local name.
256            #[must_use]
257            pub const fn local_name(self) -> &'static str {
258                match self {
259                    $(Self::$variant => $local_name),+
260                }
261            }
262        }
263    };
264}
265
266protocol_enum! {
267    /// Standard live properties that can be discovered through Forge's catalog.
268    pub enum DavLiveProperty {
269        CreationDate => "creationdate",
270        DisplayName => "displayname",
271        GetContentLanguage => "getcontentlanguage",
272        GetContentLength => "getcontentlength",
273        GetContentType => "getcontenttype",
274        GetEtag => "getetag",
275        GetLastModified => "getlastmodified",
276        LockDiscovery => "lockdiscovery",
277        ResourceType => "resourcetype",
278        SupportedLock => "supportedlock",
279        AlternateUriSet => "alternate-URI-set",
280        PrincipalUrl => "principal-URL",
281        GroupMemberSet => "group-member-set",
282        GroupMembership => "group-membership",
283        Owner => "owner",
284        Group => "group",
285        SupportedPrivilegeSet => "supported-privilege-set",
286        CurrentUserPrivilegeSet => "current-user-privilege-set",
287        Acl => "acl",
288        AclRestrictions => "acl-restrictions",
289        InheritedAclSet => "inherited-acl-set",
290        PrincipalCollectionSet => "principal-collection-set",
291        Comment => "comment",
292        CreatorDisplayName => "creator-displayname",
293        SupportedMethodSet => "supported-method-set",
294        SupportedLivePropertySet => "supported-live-property-set",
295        SupportedReportSet => "supported-report-set",
296        CheckedIn => "checked-in",
297        AutoVersion => "auto-version",
298        CheckedOut => "checked-out",
299        PredecessorSet => "predecessor-set",
300        SuccessorSet => "successor-set",
301        CheckoutSet => "checkout-set",
302        VersionName => "version-name",
303        CheckoutFork => "checkout-fork",
304        CheckinFork => "checkin-fork",
305        VersionSet => "version-set",
306        RootVersion => "root-version",
307        VersionHistory => "version-history",
308        WorkspaceCheckoutSet => "workspace-checkout-set",
309        Workspace => "workspace",
310        LabelNameSet => "label-name-set",
311        AutoUpdate => "auto-update",
312        MergeSet => "merge-set",
313        AutoMergeSet => "auto-merge-set",
314        BaselineControlledCollection => "baseline-controlled-collection",
315        SubbaselineSet => "subbaseline-set",
316        BaselineCollection => "baseline-collection",
317        VersionControlledConfiguration => "version-controlled-configuration",
318        BaselineControlledCollectionSet => "baseline-controlled-collection-set",
319        ActivityVersionSet => "activity-version-set",
320        ActivityCheckoutSet => "activity-checkout-set",
321        SubactivitySet => "subactivity-set",
322        CurrentWorkspaceSet => "current-workspace-set",
323        ActivitySet => "activity-set",
324        Unreserved => "unreserved",
325        CurrentActivitySet => "current-activity-set",
326        EclipsedSet => "eclipsed-set",
327        VersionControlledBindingSet => "version-controlled-binding-set",
328        SupportedQueryGrammarSet => "supported-query-grammar-set",
329        QuotaAvailableBytes => "quota-available-bytes",
330        QuotaUsedBytes => "quota-used-bytes",
331        SyncToken => "sync-token",
332        CurrentUserPrincipal => "current-user-principal",
333        OrderingType => "ordering-type",
334        RedirectLifetime => "redirect-lifetime",
335        RefTarget => "reftarget",
336        ResourceId => "resource-id",
337        ParentSet => "parent-set",
338        AddMember => "add-member",
339    }
340}
341
342protocol_enum! {
343    /// REPORT types contributed by RFC packages.
344    pub enum DavReportType {
345        VersionTree => "version-tree",
346        ExpandProperty => "expand-property",
347        LocateByHistory => "locate-by-history",
348        MergePreview => "merge-preview",
349        CompareBaseline => "compare-baseline",
350        LatestActivityVersion => "latest-activity-version",
351        AclPrincipalPropSet => "acl-principal-prop-set",
352        PrincipalMatch => "principal-match",
353        PrincipalPropertySearch => "principal-property-search",
354        PrincipalSearchPropertySet => "principal-search-property-set",
355        SyncCollection => "sync-collection",
356    }
357}
358
359/// RFC 8144 preference behavior exposed by a package.
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
361pub struct DavPreferenceSet(u8);
362
363impl DavPreferenceSet {
364    pub const RETURN_MINIMAL: Self = Self(1);
365    pub const RETURN_REPRESENTATION: Self = Self(1 << 1);
366    pub const DEPTH_NO_ROOT: Self = Self(1 << 2);
367
368    #[must_use]
369    pub const fn empty() -> Self {
370        Self(0)
371    }
372
373    #[must_use]
374    pub const fn all() -> Self {
375        Self(Self::RETURN_MINIMAL.0 | Self::RETURN_REPRESENTATION.0 | Self::DEPTH_NO_ROOT.0)
376    }
377
378    #[must_use]
379    pub const fn union(self, other: Self) -> Self {
380        Self(self.0 | other.0)
381    }
382
383    #[must_use]
384    pub const fn contains(self, preference: Self) -> bool {
385        self.0 & preference.0 == preference.0
386    }
387}
388
389/// Static RFC metadata used by capability, dispatch, property and REPORT discovery.
390#[derive(Debug, Clone, Copy)]
391pub struct DavExtensionDescriptor {
392    pub package: DavExtensionPackage,
393    pub rfc: &'static str,
394    pub dav_token: Option<&'static str>,
395    pub prerequisites: DavExtensionSet,
396    pub resources: DavResourceStateSet,
397    pub methods: &'static [DavExtensionMethod],
398    pub live_properties: &'static [DavLiveProperty],
399    pub reports: &'static [DavReportType],
400    pub preferences: DavPreferenceSet,
401}
402
403const ALL_RESOURCES: DavResourceStateSet = DavResourceStateSet::from_states(&[
404    DavResourceState::Unmapped,
405    DavResourceState::File,
406    DavResourceState::Collection,
407    DavResourceState::MountRoot,
408    DavResourceState::Principal,
409    DavResourceState::RedirectReference,
410    DavResourceState::AddMemberEndpoint,
411]);
412const MAPPED_RESOURCES: DavResourceStateSet = DavResourceStateSet::from_states(&[
413    DavResourceState::File,
414    DavResourceState::Collection,
415    DavResourceState::MountRoot,
416    DavResourceState::Principal,
417    DavResourceState::RedirectReference,
418]);
419const CONTENT_RESOURCES: DavResourceStateSet = DavResourceStateSet::from_states(&[
420    DavResourceState::File,
421    DavResourceState::Collection,
422    DavResourceState::MountRoot,
423]);
424const COLLECTIONS: DavResourceStateSet =
425    DavResourceStateSet::from_states(&[DavResourceState::Collection, DavResourceState::MountRoot]);
426const UNMAPPED: DavResourceStateSet =
427    DavResourceStateSet::from_states(&[DavResourceState::Unmapped]);
428const ORDERABLE: DavResourceStateSet = DavResourceStateSet::from_states(&[
429    DavResourceState::Unmapped,
430    DavResourceState::Collection,
431    DavResourceState::MountRoot,
432]);
433const REDIRECT_TARGETS: DavResourceStateSet = DavResourceStateSet::from_states(&[
434    DavResourceState::Unmapped,
435    DavResourceState::File,
436    DavResourceState::Collection,
437    DavResourceState::MountRoot,
438    DavResourceState::Principal,
439    DavResourceState::RedirectReference,
440]);
441const ADD_MEMBER_TARGETS: DavResourceStateSet = DavResourceStateSet::from_states(&[
442    DavResourceState::Collection,
443    DavResourceState::MountRoot,
444    DavResourceState::AddMemberEndpoint,
445]);
446
447const XML: DavExtensionBodyKind = DavExtensionBodyKind::Xml;
448
449const ACL_METHODS: &[DavExtensionMethod] = &[
450    DavExtensionMethod {
451        method: DavMethod::Acl,
452        resources: MAPPED_RESOURCES,
453        body: XML,
454    },
455    DavExtensionMethod {
456        method: DavMethod::Report,
457        resources: MAPPED_RESOURCES,
458        body: XML,
459    },
460];
461const VERSION_CONTROL_METHODS: &[DavExtensionMethod] = &[
462    DavExtensionMethod {
463        method: DavMethod::VersionControl,
464        resources: CONTENT_RESOURCES.union(UNMAPPED),
465        body: XML,
466    },
467    DavExtensionMethod {
468        method: DavMethod::Report,
469        resources: CONTENT_RESOURCES,
470        body: XML,
471    },
472];
473const CHECKOUT_METHODS: &[DavExtensionMethod] = &[
474    DavExtensionMethod {
475        method: DavMethod::Checkout,
476        resources: CONTENT_RESOURCES,
477        body: XML,
478    },
479    DavExtensionMethod {
480        method: DavMethod::Checkin,
481        resources: CONTENT_RESOURCES,
482        body: XML,
483    },
484    DavExtensionMethod {
485        method: DavMethod::Uncheckout,
486        resources: CONTENT_RESOURCES,
487        body: XML,
488    },
489];
490const WORKSPACE_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
491    method: DavMethod::Mkworkspace,
492    resources: UNMAPPED,
493    body: XML,
494}];
495const UPDATE_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
496    method: DavMethod::Update,
497    resources: CONTENT_RESOURCES,
498    body: XML,
499}];
500const LABEL_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
501    method: DavMethod::Label,
502    resources: CONTENT_RESOURCES,
503    body: XML,
504}];
505const WORKING_RESOURCE_METHODS: &[DavExtensionMethod] = CHECKOUT_METHODS;
506const MERGE_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
507    method: DavMethod::Merge,
508    resources: CONTENT_RESOURCES,
509    body: XML,
510}];
511const BASELINE_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
512    method: DavMethod::BaselineControl,
513    resources: COLLECTIONS,
514    body: XML,
515}];
516const ACTIVITY_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
517    method: DavMethod::Mkactivity,
518    resources: UNMAPPED,
519    body: XML,
520}];
521const SEARCH_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
522    method: DavMethod::Search,
523    resources: MAPPED_RESOURCES,
524    body: XML,
525}];
526const SYNC_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
527    method: DavMethod::Report,
528    resources: COLLECTIONS,
529    body: XML,
530}];
531const EXTENDED_MKCOL_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
532    method: DavMethod::Mkcol,
533    resources: UNMAPPED,
534    body: XML,
535}];
536const ORDERED_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
537    method: DavMethod::Orderpatch,
538    resources: COLLECTIONS,
539    body: XML,
540}];
541const REDIRECT_METHODS: &[DavExtensionMethod] = &[
542    DavExtensionMethod {
543        method: DavMethod::Mkredirectref,
544        resources: UNMAPPED,
545        body: XML,
546    },
547    DavExtensionMethod {
548        method: DavMethod::Updateredirectref,
549        resources: DavResourceStateSet::from_states(&[DavResourceState::RedirectReference]),
550        body: XML,
551    },
552];
553const BIND_METHODS: &[DavExtensionMethod] = &[
554    DavExtensionMethod {
555        method: DavMethod::Bind,
556        resources: COLLECTIONS,
557        body: XML,
558    },
559    DavExtensionMethod {
560        method: DavMethod::Unbind,
561        resources: COLLECTIONS,
562        body: XML,
563    },
564    DavExtensionMethod {
565        method: DavMethod::Rebind,
566        resources: COLLECTIONS,
567        body: XML,
568    },
569];
570const ADD_MEMBER_METHODS: &[DavExtensionMethod] = &[DavExtensionMethod {
571    method: DavMethod::Post,
572    resources: DavResourceStateSet::from_states(&[DavResourceState::AddMemberEndpoint]),
573    body: DavExtensionBodyKind::Stream,
574}];
575
576const ACL_PROPERTIES: &[DavLiveProperty] = &[
577    DavLiveProperty::AlternateUriSet,
578    DavLiveProperty::PrincipalUrl,
579    DavLiveProperty::GroupMemberSet,
580    DavLiveProperty::GroupMembership,
581    DavLiveProperty::Owner,
582    DavLiveProperty::Group,
583    DavLiveProperty::SupportedPrivilegeSet,
584    DavLiveProperty::CurrentUserPrivilegeSet,
585    DavLiveProperty::Acl,
586    DavLiveProperty::AclRestrictions,
587    DavLiveProperty::InheritedAclSet,
588    DavLiveProperty::PrincipalCollectionSet,
589];
590const VERSION_CONTROL_PROPERTIES: &[DavLiveProperty] = &[
591    DavLiveProperty::Comment,
592    DavLiveProperty::CreatorDisplayName,
593    DavLiveProperty::SupportedMethodSet,
594    DavLiveProperty::SupportedLivePropertySet,
595    DavLiveProperty::SupportedReportSet,
596    DavLiveProperty::CheckedIn,
597    DavLiveProperty::AutoVersion,
598    DavLiveProperty::CheckedOut,
599    DavLiveProperty::PredecessorSet,
600    DavLiveProperty::SuccessorSet,
601    DavLiveProperty::CheckoutSet,
602    DavLiveProperty::VersionName,
603];
604const CHECKOUT_PROPERTIES: &[DavLiveProperty] =
605    &[DavLiveProperty::CheckoutFork, DavLiveProperty::CheckinFork];
606const VERSION_HISTORY_PROPERTIES: &[DavLiveProperty] = &[
607    DavLiveProperty::VersionSet,
608    DavLiveProperty::RootVersion,
609    DavLiveProperty::VersionHistory,
610];
611const WORKSPACE_PROPERTIES: &[DavLiveProperty] = &[
612    DavLiveProperty::WorkspaceCheckoutSet,
613    DavLiveProperty::Workspace,
614];
615const LABEL_PROPERTIES: &[DavLiveProperty] = &[DavLiveProperty::LabelNameSet];
616const WORKING_RESOURCE_PROPERTIES: &[DavLiveProperty] = &[DavLiveProperty::AutoUpdate];
617const MERGE_PROPERTIES: &[DavLiveProperty] =
618    &[DavLiveProperty::MergeSet, DavLiveProperty::AutoMergeSet];
619const BASELINE_PROPERTIES: &[DavLiveProperty] = &[
620    DavLiveProperty::BaselineControlledCollection,
621    DavLiveProperty::SubbaselineSet,
622    DavLiveProperty::BaselineCollection,
623    DavLiveProperty::VersionControlledConfiguration,
624    DavLiveProperty::BaselineControlledCollectionSet,
625];
626const ACTIVITY_PROPERTIES: &[DavLiveProperty] = &[
627    DavLiveProperty::ActivityVersionSet,
628    DavLiveProperty::ActivityCheckoutSet,
629    DavLiveProperty::SubactivitySet,
630    DavLiveProperty::CurrentWorkspaceSet,
631    DavLiveProperty::ActivitySet,
632    DavLiveProperty::Unreserved,
633    DavLiveProperty::CurrentActivitySet,
634];
635const VCC_PROPERTIES: &[DavLiveProperty] = &[
636    DavLiveProperty::EclipsedSet,
637    DavLiveProperty::VersionControlledBindingSet,
638];
639const SEARCH_PROPERTIES: &[DavLiveProperty] = &[DavLiveProperty::SupportedQueryGrammarSet];
640const QUOTA_PROPERTIES: &[DavLiveProperty] = &[
641    DavLiveProperty::QuotaAvailableBytes,
642    DavLiveProperty::QuotaUsedBytes,
643];
644const SYNC_PROPERTIES: &[DavLiveProperty] = &[DavLiveProperty::SyncToken];
645const CURRENT_PRINCIPAL_PROPERTIES: &[DavLiveProperty] = &[DavLiveProperty::CurrentUserPrincipal];
646const ORDERED_PROPERTIES: &[DavLiveProperty] = &[DavLiveProperty::OrderingType];
647const REDIRECT_PROPERTIES: &[DavLiveProperty] = &[
648    DavLiveProperty::RedirectLifetime,
649    DavLiveProperty::RefTarget,
650];
651const BIND_PROPERTIES: &[DavLiveProperty] =
652    &[DavLiveProperty::ResourceId, DavLiveProperty::ParentSet];
653const ADD_MEMBER_PROPERTIES: &[DavLiveProperty] = &[DavLiveProperty::AddMember];
654
655const ACL_REPORTS: &[DavReportType] = &[
656    DavReportType::AclPrincipalPropSet,
657    DavReportType::PrincipalMatch,
658    DavReportType::PrincipalPropertySearch,
659    DavReportType::PrincipalSearchPropertySet,
660];
661const VERSION_CONTROL_REPORTS: &[DavReportType] =
662    &[DavReportType::VersionTree, DavReportType::ExpandProperty];
663const VERSION_HISTORY_REPORTS: &[DavReportType] = &[DavReportType::LocateByHistory];
664const MERGE_REPORTS: &[DavReportType] = &[DavReportType::MergePreview];
665const BASELINE_REPORTS: &[DavReportType] = &[DavReportType::CompareBaseline];
666const ACTIVITY_REPORTS: &[DavReportType] = &[DavReportType::LatestActivityVersion];
667const SYNC_REPORTS: &[DavReportType] = &[DavReportType::SyncCollection];
668
669const EMPTY_METHODS: &[DavExtensionMethod] = &[];
670const EMPTY_PROPERTIES: &[DavLiveProperty] = &[];
671const EMPTY_REPORTS: &[DavReportType] = &[];
672const VERSION_CONTROL_REQUIRED: DavExtensionSet =
673    DavExtensionSet::from_packages(&[DavExtensionPackage::VersionControl]);
674const WORKSPACE_REQUIRED: DavExtensionSet = DavExtensionSet::from_packages(&[
675    DavExtensionPackage::VersionControl,
676    DavExtensionPackage::CheckoutInPlace,
677    DavExtensionPackage::VersionHistory,
678]);
679
680macro_rules! descriptor {
681    (
682        $package:expr,
683        $rfc:expr,
684        $dav_token:expr,
685        $prerequisites:expr,
686        $resources:expr,
687        $methods:expr,
688        $live_properties:expr,
689        $reports:expr $(,)?
690    ) => {
691        DavExtensionDescriptor {
692            package: $package,
693            rfc: $rfc,
694            dav_token: $dav_token,
695            prerequisites: $prerequisites,
696            resources: $resources,
697            methods: $methods,
698            live_properties: $live_properties,
699            reports: $reports,
700            preferences: DavPreferenceSet::empty(),
701        }
702    };
703}
704
705const DESCRIPTORS: [DavExtensionDescriptor; 22] = [
706    descriptor!(
707        DavExtensionPackage::AccessControl,
708        "RFC 3744",
709        Some("access-control"),
710        DavExtensionSet::empty(),
711        MAPPED_RESOURCES,
712        ACL_METHODS,
713        ACL_PROPERTIES,
714        ACL_REPORTS,
715    ),
716    descriptor!(
717        DavExtensionPackage::VersionControl,
718        "RFC 3253 section 3",
719        Some("version-control"),
720        DavExtensionSet::empty(),
721        ALL_RESOURCES,
722        VERSION_CONTROL_METHODS,
723        VERSION_CONTROL_PROPERTIES,
724        VERSION_CONTROL_REPORTS,
725    ),
726    descriptor!(
727        DavExtensionPackage::CheckoutInPlace,
728        "RFC 3253 section 4",
729        Some("checkout-in-place"),
730        VERSION_CONTROL_REQUIRED,
731        ALL_RESOURCES,
732        CHECKOUT_METHODS,
733        CHECKOUT_PROPERTIES,
734        EMPTY_REPORTS,
735    ),
736    descriptor!(
737        DavExtensionPackage::VersionHistory,
738        "RFC 3253 section 5",
739        Some("version-history"),
740        VERSION_CONTROL_REQUIRED,
741        ALL_RESOURCES,
742        EMPTY_METHODS,
743        VERSION_HISTORY_PROPERTIES,
744        VERSION_HISTORY_REPORTS,
745    ),
746    descriptor!(
747        DavExtensionPackage::Workspace,
748        "RFC 3253 section 6",
749        Some("workspace"),
750        WORKSPACE_REQUIRED,
751        ALL_RESOURCES,
752        WORKSPACE_METHODS,
753        WORKSPACE_PROPERTIES,
754        EMPTY_REPORTS,
755    ),
756    descriptor!(
757        DavExtensionPackage::Update,
758        "RFC 3253 section 7",
759        Some("update"),
760        VERSION_CONTROL_REQUIRED,
761        ALL_RESOURCES,
762        UPDATE_METHODS,
763        EMPTY_PROPERTIES,
764        EMPTY_REPORTS,
765    ),
766    descriptor!(
767        DavExtensionPackage::Label,
768        "RFC 3253 section 8",
769        Some("label"),
770        VERSION_CONTROL_REQUIRED,
771        ALL_RESOURCES,
772        LABEL_METHODS,
773        LABEL_PROPERTIES,
774        EMPTY_REPORTS,
775    ),
776    descriptor!(
777        DavExtensionPackage::WorkingResource,
778        "RFC 3253 section 9",
779        Some("working-resource"),
780        VERSION_CONTROL_REQUIRED,
781        ALL_RESOURCES,
782        WORKING_RESOURCE_METHODS,
783        WORKING_RESOURCE_PROPERTIES,
784        EMPTY_REPORTS,
785    ),
786    descriptor!(
787        DavExtensionPackage::Merge,
788        "RFC 3253 section 11",
789        Some("merge"),
790        VERSION_CONTROL_REQUIRED,
791        ALL_RESOURCES,
792        MERGE_METHODS,
793        MERGE_PROPERTIES,
794        MERGE_REPORTS,
795    ),
796    descriptor!(
797        DavExtensionPackage::Baseline,
798        "RFC 3253 section 12",
799        Some("baseline"),
800        VERSION_CONTROL_REQUIRED,
801        ALL_RESOURCES,
802        BASELINE_METHODS,
803        BASELINE_PROPERTIES,
804        BASELINE_REPORTS,
805    ),
806    descriptor!(
807        DavExtensionPackage::Activity,
808        "RFC 3253 section 13",
809        Some("activity"),
810        VERSION_CONTROL_REQUIRED,
811        ALL_RESOURCES,
812        ACTIVITY_METHODS,
813        ACTIVITY_PROPERTIES,
814        ACTIVITY_REPORTS,
815    ),
816    descriptor!(
817        DavExtensionPackage::VersionControlledCollection,
818        "RFC 3253 section 14",
819        Some("version-controlled-collection"),
820        VERSION_CONTROL_REQUIRED,
821        ALL_RESOURCES,
822        EMPTY_METHODS,
823        VCC_PROPERTIES,
824        EMPTY_REPORTS,
825    ),
826    descriptor!(
827        DavExtensionPackage::Search,
828        "RFC 5323",
829        None,
830        DavExtensionSet::empty(),
831        MAPPED_RESOURCES,
832        SEARCH_METHODS,
833        SEARCH_PROPERTIES,
834        EMPTY_REPORTS,
835    ),
836    descriptor!(
837        DavExtensionPackage::Quota,
838        "RFC 4331",
839        None,
840        DavExtensionSet::empty(),
841        CONTENT_RESOURCES,
842        EMPTY_METHODS,
843        QUOTA_PROPERTIES,
844        EMPTY_REPORTS,
845    ),
846    descriptor!(
847        DavExtensionPackage::CollectionSync,
848        "RFC 6578",
849        None,
850        DavExtensionSet::empty(),
851        COLLECTIONS,
852        SYNC_METHODS,
853        SYNC_PROPERTIES,
854        SYNC_REPORTS,
855    ),
856    descriptor!(
857        DavExtensionPackage::ExtendedMkcol,
858        "RFC 5689",
859        Some("extended-mkcol"),
860        DavExtensionSet::empty(),
861        UNMAPPED,
862        EXTENDED_MKCOL_METHODS,
863        EMPTY_PROPERTIES,
864        EMPTY_REPORTS,
865    ),
866    descriptor!(
867        DavExtensionPackage::CurrentPrincipal,
868        "RFC 5397",
869        None,
870        DavExtensionSet::empty(),
871        MAPPED_RESOURCES,
872        EMPTY_METHODS,
873        CURRENT_PRINCIPAL_PROPERTIES,
874        EMPTY_REPORTS,
875    ),
876    descriptor!(
877        DavExtensionPackage::OrderedCollections,
878        "RFC 3648",
879        Some("ordered-collections"),
880        DavExtensionSet::empty(),
881        ORDERABLE,
882        ORDERED_METHODS,
883        ORDERED_PROPERTIES,
884        EMPTY_REPORTS,
885    ),
886    descriptor!(
887        DavExtensionPackage::RedirectReferences,
888        "RFC 4437",
889        Some("redirectrefs"),
890        DavExtensionSet::empty(),
891        REDIRECT_TARGETS,
892        REDIRECT_METHODS,
893        REDIRECT_PROPERTIES,
894        EMPTY_REPORTS,
895    ),
896    descriptor!(
897        DavExtensionPackage::Bindings,
898        "RFC 5842",
899        Some("bind"),
900        DavExtensionSet::empty(),
901        MAPPED_RESOURCES,
902        BIND_METHODS,
903        BIND_PROPERTIES,
904        EMPTY_REPORTS,
905    ),
906    descriptor!(
907        DavExtensionPackage::AddMember,
908        "RFC 5995",
909        None,
910        DavExtensionSet::empty(),
911        ADD_MEMBER_TARGETS,
912        ADD_MEMBER_METHODS,
913        ADD_MEMBER_PROPERTIES,
914        EMPTY_REPORTS,
915    ),
916    DavExtensionDescriptor {
917        package: DavExtensionPackage::Prefer,
918        rfc: "RFC 8144",
919        dav_token: None,
920        prerequisites: DavExtensionSet::empty(),
921        resources: ALL_RESOURCES,
922        methods: EMPTY_METHODS,
923        live_properties: EMPTY_PROPERTIES,
924        reports: EMPTY_REPORTS,
925        preferences: DavPreferenceSet::all(),
926    },
927];
928
929/// Returns methods implemented by enabled packages for the selected resource state.
930#[must_use]
931pub fn extension_methods(packages: DavExtensionSet, resource: DavResourceState) -> DavMethodSet {
932    let mut methods = DavMethodSet::empty();
933    for package in packages.iter() {
934        for contribution in package.descriptor().methods {
935            if contribution.resources.contains(resource) {
936                methods = methods.with(contribution.method);
937            }
938        }
939    }
940    methods
941}
942
943/// Returns the extension body contract for a method on the selected resource.
944#[must_use]
945pub fn extension_body_kind(
946    packages: DavExtensionSet,
947    resource: DavResourceState,
948    method: DavMethod,
949) -> Option<DavExtensionBodyKind> {
950    for package in packages.iter() {
951        for contribution in package.descriptor().methods {
952            if contribution.method == method && contribution.resources.contains(resource) {
953                return Some(contribution.body);
954            }
955        }
956    }
957    None
958}