webdav_headers/
dav.rs

1// SPDX-FileCopyrightText: d-k-bo <d-k-bo@mailbox.org>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use std::{fmt::Display, str::FromStr};
6
7use itertools::Itertools;
8
9use crate::{utils::HeaderIteratorExt, CodedUrl, ParseString, DAV};
10
11pub use self::error::InvalidComplianceClass;
12
13/// The `DAV` header as defined in [RFC 4918](http://webdav.org/specs/rfc4918.html#HEADER_DAV).
14#[derive(Clone, Debug, PartialEq)]
15pub struct Dav(pub Vec<ComplianceClass>);
16
17impl headers::Header for Dav {
18    fn name() -> &'static http::HeaderName {
19        &DAV
20    }
21
22    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
23    where
24        Self: Sized,
25        I: Iterator<Item = &'i http::HeaderValue>,
26    {
27        Ok(Self(
28            values
29                .extract_str()?
30                .split(',')
31                .map(str::trim)
32                .map(ComplianceClass::from_str)
33                .collect::<Result<_, _>>()?,
34        ))
35    }
36
37    fn encode<E: Extend<http::HeaderValue>>(&self, values: &mut E) {
38        values.extend(std::iter::once(self.0.iter().join(",").try_into().unwrap()))
39    }
40}
41
42/// Compliance class identifiers used in the `DAV` header.
43#[derive(Clone, Debug, PartialEq)]
44pub enum ComplianceClass {
45    One,
46    Two,
47    Three,
48    CodedUrl(Box<CodedUrl>),
49    Tokens(Tokens),
50}
51impl Display for ComplianceClass {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::One => f.write_str("1"),
55            Self::Two => f.write_str("2"),
56            Self::Three => f.write_str("3"),
57            Self::CodedUrl(uri) => uri.fmt(f),
58            Self::Tokens(s) => s.fmt(f),
59        }
60    }
61}
62impl FromStr for ComplianceClass {
63    type Err = InvalidComplianceClass;
64
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        match s {
67            "" => Err(InvalidComplianceClass::Empty),
68            "1" => Ok(Self::One),
69            "2" => Ok(Self::Two),
70            "3" => Ok(Self::Three),
71            _ => match CodedUrl::peek(s) {
72                Ok((coded_url, _)) => Ok(Self::CodedUrl(Box::new(coded_url))),
73                Err(_) => Ok(Self::Tokens(s.parse()?)),
74            },
75        }
76    }
77}
78
79/// A freeform compliance class identifier.
80#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
81pub struct Tokens(String);
82
83impl Tokens {
84    pub fn as_str(&self) -> &str {
85        &self.0
86    }
87}
88
89impl AsRef<str> for Tokens {
90    fn as_ref(&self) -> &str {
91        &self.0
92    }
93}
94
95impl From<Tokens> for String {
96    fn from(Tokens(s): Tokens) -> Self {
97        s
98    }
99}
100
101impl std::fmt::Display for Tokens {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.write_str(self.as_str())
104    }
105}
106
107impl FromStr for Tokens {
108    type Err = InvalidComplianceClass;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        match s.chars().find(|c| {
112            !c.is_ascii()
113                || c.is_ascii_control()
114                || c.is_ascii_punctuation()
115                || c.is_ascii_whitespace()
116        }) {
117            Some(c) => Err(InvalidComplianceClass::InvalidChar(c)),
118            None => Ok(Self(s.to_owned())),
119        }
120    }
121}
122
123mod error {
124    /// Error returned when parsing [`ComplianceClass`](super::ComplianceClass)
125    /// from a string fails.
126    #[derive(Debug)]
127    pub enum InvalidComplianceClass {
128        Empty,
129        InvalidChar(char),
130    }
131
132    impl std::fmt::Display for InvalidComplianceClass {
133        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134            match self {
135                Self::Empty => f.write_str("empty compliance class"),
136                Self::InvalidChar(c) => write!(f, "invalid character in compliance class '{c:?}'"),
137            }
138        }
139    }
140
141    impl std::error::Error for InvalidComplianceClass {}
142
143    impl From<InvalidComplianceClass> for headers::Error {
144        fn from(_: InvalidComplianceClass) -> Self {
145            headers::Error::invalid()
146        }
147    }
148}
149
150#[cfg(test)]
151#[test]
152fn test() {
153    use crate::test::test;
154
155    test(
156        "1,2,3,<https://example.com/foo>,foobar",
157        Dav(vec![
158            ComplianceClass::One,
159            ComplianceClass::Two,
160            ComplianceClass::Three,
161            ComplianceClass::CodedUrl(Box::new(CodedUrl(
162                uniresid::AbsoluteUri::parse("https://example.com/foo").unwrap(),
163            ))),
164            ComplianceClass::Tokens("foobar".parse().unwrap()),
165        ]),
166    )
167}