1use 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#[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#[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#[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 #[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}