manas_http/header/prefer/
preference.rs1use std::str::FromStr;
5
6use once_cell::sync::Lazy;
7
8use crate::field::{
9 pvalue::{InvalidEncodedPFieldValue, PFieldValue},
10 rules::{
11 parameter::{FieldParameter, InvalidEncodedFieldParameter},
12 parameter_name::FieldParameterName,
13 parameter_value::FieldParameterValue,
14 parameters::FieldParameters,
15 },
16};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Preference {
28 pub token_name: FieldParameterName,
30
31 pub token_value: FieldParameterValue,
33
34 pub params: FieldParameters,
36}
37
38pub static TOKEN_NAME_RETURN: Lazy<FieldParameterName> =
40 Lazy::new(|| "return".parse().expect("Must be valid."));
41
42pub static PARAM_NAME_INCLUDE: Lazy<FieldParameterName> =
44 Lazy::new(|| "include".parse().expect("Must be valid."));
45
46pub static TOKEN_VALUE_REPRESENTATION: Lazy<FieldParameterValue> =
48 Lazy::new(|| "representation".try_into().expect("Must be valid."));
49
50#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
51pub enum InvalidEncodedPreference {
53 #[error("Given header value is not a valid parameterized header value")]
55 InvalidPFieldValue(#[from] InvalidEncodedPFieldValue),
56
57 #[error("Given preference has invalid preference token")]
59 InvalidPreferenceToken(#[from] InvalidEncodedFieldParameter),
60}
61
62impl FromStr for Preference {
63 type Err = InvalidEncodedPreference;
64
65 fn from_str(value: &str) -> Result<Self, Self::Err> {
66 let PFieldValue {
67 base_value: pref_token,
68 params,
69 } = PFieldValue::decode(value, true)?;
70
71 let FieldParameter {
72 name: token_name,
73 value: token_value,
74 } = FieldParameter::decode(&pref_token, true)?;
75
76 Ok(Self {
77 token_name,
78 token_value,
79 params,
80 })
81 }
82}
83
84impl Preference {
85 #[inline]
87 pub(crate) fn push_encoded_str(&self, buffer: &mut String) {
88 self.token_name.push_encoded_str(buffer);
89 buffer.push('=');
90 self.token_value.push_encoded_str(buffer);
91
92 if self.params.len() > 0 {
93 buffer.push_str("; ");
94 self.params.push_encoded_str(buffer);
95 }
96 }
97
98 #[inline]
100 pub fn str_encode(&self) -> String {
101 let mut encoded = String::new();
102 self.push_encoded_str(&mut encoded);
103 encoded
104 }
105}
106
107#[cfg(test)]
108pub(crate) mod tests_decode {
109 use claims::*;
110 use rstest::*;
111
112 use super::*;
113 use crate::field::rules::parameters::tests_parse::assert_matches_param_records;
114
115 #[rstest]
116 #[case::invalid_param_key("abc; def/ghi = 123")]
117 #[case::invalid_param_value("abc; def=a b")]
118 fn preference_with_invalid_params_will_be_rejected(#[case] preference_str: &str) {
119 assert_matches!(
120 assert_err!(Preference::from_str(preference_str)),
121 InvalidEncodedPreference::InvalidPFieldValue(..)
122 );
123 }
124
125 #[rstest]
126 #[case("abc/def; a=1")]
127 #[case("abc def; b=2")]
128 #[case("abc = def/pqr; b=2")]
129 #[case("abc = def pqr; b=2")]
130 fn preference_with_invalid_preference_token_will_be_rejected(#[case] preference_str: &str) {
131 assert_matches!(
132 assert_err!(Preference::from_str(preference_str)),
133 InvalidEncodedPreference::InvalidPreferenceToken(..)
134 );
135 }
136
137 pub fn assert_valid_preference(preference_str: &str) -> Preference {
138 assert_ok!(Preference::from_str(preference_str))
139 }
140
141 pub fn assert_preference_match(
142 preference: &Preference,
143 expected_token_name: &str,
144 expected_token_value: &str,
145 expected_params: &[(&str, &str)],
146 ) {
147 assert_eq!(preference.token_name, expected_token_name.parse().unwrap());
148 assert_eq!(preference.token_value.as_ref(), expected_token_value);
149 assert_matches_param_records(&preference.params, expected_params);
150 }
151
152 #[rstest]
153 #[case("foo; bar", "foo", "", &[("bar","")])]
154 #[case("foo; bar=\"\"", "foo", "", &[("bar","")])]
155 #[case("foo=\"\"; bar", "foo", "", &[("bar","")])]
156 #[case("respond-async; wait=100", "respond-async", "", &[("wait","100")])]
157 #[case("return=minimal; foo=\"some parameter\"", "return", "minimal", &[("foo", "some parameter")])]
158 #[case(
159 r#"return=representation; include="http://www.w3.org/ns/ldp#PreferMembership http://www.w3.org/ns/ldp#PreferMinimalContainer""#,
160 "return",
161 "representation",
162 &[("include", "http://www.w3.org/ns/ldp#PreferMembership http://www.w3.org/ns/ldp#PreferMinimalContainer")]
163 )]
164 fn valid_preference_will_be_round_tripped_correctly(
165 #[case] preference_str: &str,
166 #[case] expected_token_name: &str,
167 #[case] expected_token_value: &str,
168 #[case] expected_params: &[(&str, &str)],
169 ) {
170 let preference = assert_valid_preference(preference_str);
171 assert_preference_match(
172 &preference,
173 expected_token_name,
174 expected_token_value,
175 expected_params,
176 );
177
178 let encoded_preference_str = preference.str_encode();
179 let round_tripped_preference = assert_valid_preference(&encoded_preference_str);
180 assert_preference_match(
181 &round_tripped_preference,
182 expected_token_name,
183 expected_token_value,
184 expected_params,
185 );
186 }
187}