pinboard_rs/api/v1/posts/
add.rs1use crate::api::endpoint_prelude::*;
8use crate::api::v1::Limit;
9use chrono::NaiveDate;
10use derive_builder::Builder;
11
12#[derive(Builder, Debug)]
46#[builder(setter(strip_option), build_fn(validate = "Self::validate"))]
47pub struct Add<'a> {
48 url: url::Url,
50 #[builder(setter(into), default = "self.default_description()?.into()")]
52 description: Cow<'a, str>,
53 #[builder(setter(into), default)]
55 extended: Option<Cow<'a, str>>,
56 #[builder(setter(into), default)]
58 tags: Option<Cow<'a, [&'a str]>>,
59 #[builder(default)]
61 dt: Option<NaiveDate>,
62 #[builder(default)]
65 replace: Option<bool>,
66 #[builder(default)]
68 shared: Option<bool>,
69 #[builder(default)]
71 toread: Option<bool>,
72}
73
74impl<'a> AddBuilder<'a> {
75 fn default_description(&self) -> Result<String, String> {
77 match self.url {
78 Some(ref url) => Ok(url.to_string()),
79 _ => Err("Could not make default `description` from `url`".to_string()),
80 }
81 }
82
83 fn validate(&self) -> Result<(), String> {
85 if let Some(Some(ref xs)) = self.tags {
86 if xs.len() > 100 {
87 return Err(format!(
88 "Endpoint only accepts up to 100 tags (received {})",
89 xs.len()
90 ));
91 }
92 }
93 Ok(())
94 }
95}
96
97impl<'a> Add<'a> {
98 pub fn builder() -> AddBuilder<'a> {
100 AddBuilder::default()
101 }
102}
103
104impl<'a> Endpoint for Add<'a> {
105 fn method(&self) -> Method {
106 Method::GET
107 }
108
109 fn endpoint(&self) -> Cow<'static, str> {
110 "v1/posts/add".into()
111 }
112
113 fn parameters(&self) -> QueryParams {
114 let mut params = QueryParams::default();
115
116 params
117 .push("url", self.url.as_ref())
118 .push("description", self.description.as_ref())
119 .push_opt("extended", self.extended.as_ref())
120 .push_opt("tags", self.tags.as_ref().map(|xs| xs.join(" ")))
121 .push_opt("dt", self.dt)
122 .push_opt(
123 "replace",
124 self.replace.map(|x| if x { "yes" } else { "no" }),
125 )
126 .push_opt("shared", self.shared.map(|x| if x { "yes" } else { "no" }))
127 .push_opt("toread", self.toread.map(|x| if x { "yes" } else { "no" }));
128
129 params
130 }
131}
132
133impl<'a> Limit for Add<'a> {}
134
135#[cfg(test)]
136mod tests {
137 use crate::api::v1::{posts::Add, Limit};
138 use crate::api::{self, Query};
139 use crate::test::client::{ExpectedUrl, SingleTestClient};
140 use chrono::NaiveDate;
141
142 static TITLE: &str = "Some Title";
143 static URL: &str = "http://pinboard.test/";
144 fn test_url() -> url::Url {
145 url::Url::parse(URL).unwrap()
146 }
147
148 #[test]
149 fn url_is_required() {
150 let err = Add::builder().description(TITLE).build().unwrap_err();
151 assert_eq!(&err.to_string(), "`url` must be initialized")
152 }
153
154 #[test]
155 fn description_is_required() {
156 let add = Add::builder().url(test_url()).build().unwrap();
157 assert_eq!(add.description, URL)
158 }
159
160 #[test]
161 fn endpoint() {
162 let endpoint = ExpectedUrl::builder()
163 .endpoint("v1/posts/add")
164 .add_query_params(&[("url", URL), ("description", TITLE)])
165 .build()
166 .unwrap();
167 let client = SingleTestClient::new_raw(endpoint, "");
168
169 let endpoint = Add::builder()
170 .url(test_url())
171 .description(TITLE)
172 .build()
173 .unwrap();
174 api::ignore(endpoint).query(&client).unwrap();
175 }
176
177 #[test]
178 fn endpoint_extended() {
179 let endpoint = ExpectedUrl::builder()
180 .endpoint("v1/posts/add")
181 .add_query_params(&[
182 ("url", URL),
183 ("description", TITLE),
184 ("extended", "some extended text"),
185 ])
186 .build()
187 .unwrap();
188 let client = SingleTestClient::new_raw(endpoint, "");
189
190 let endpoint = Add::builder()
191 .url(test_url())
192 .description(TITLE)
193 .extended("some extended text")
194 .build()
195 .unwrap();
196 api::ignore(endpoint).query(&client).unwrap();
197 }
198
199 #[test]
200 fn endpoint_tags() {
201 let endpoint = ExpectedUrl::builder()
202 .endpoint("v1/posts/add")
203 .add_query_params(&[("url", URL), ("description", TITLE), ("tags", "one two")])
204 .build()
205 .unwrap();
206 let client = SingleTestClient::new_raw(endpoint, "");
207
208 let endpoint = Add::builder()
209 .url(test_url())
210 .description(TITLE)
211 .tags(vec!["one", "two"])
212 .build()
213 .unwrap();
214 api::ignore(endpoint).query(&client).unwrap();
215 }
216
217 #[test]
218 fn endpoint_tags_101() {
219 let err = Add::builder()
220 .url(test_url())
221 .description(TITLE)
222 .tags(vec!["one"; 101])
223 .build()
224 .unwrap_err();
225
226 assert_eq!(
227 &err.to_string(),
228 "Endpoint only accepts up to 100 tags (received 101)"
229 )
230 }
231
232 #[test]
233 fn endpoint_dt() {
234 let endpoint = ExpectedUrl::builder()
235 .endpoint("v1/posts/add")
236 .add_query_params(&[("url", URL), ("description", TITLE), ("dt", "2021-03-04")])
237 .build()
238 .unwrap();
239 let client = SingleTestClient::new_raw(endpoint, "");
240
241 let endpoint = Add::builder()
242 .url(test_url())
243 .description(TITLE)
244 .dt(NaiveDate::from_ymd_opt(2021, 3, 4).expect("Valid date"))
245 .build()
246 .unwrap();
247 api::ignore(endpoint).query(&client).unwrap();
248 }
249
250 #[test]
251 fn endpoint_replace() {
252 let endpoint = ExpectedUrl::builder()
253 .endpoint("v1/posts/add")
254 .add_query_params(&[("url", URL), ("description", TITLE), ("replace", "yes")])
255 .build()
256 .unwrap();
257 let client = SingleTestClient::new_raw(endpoint, "");
258
259 let endpoint = Add::builder()
260 .url(test_url())
261 .description(TITLE)
262 .replace(true)
263 .build()
264 .unwrap();
265 api::ignore(endpoint).query(&client).unwrap();
266 }
267
268 #[test]
269 fn endpoint_shared() {
270 let endpoint = ExpectedUrl::builder()
271 .endpoint("v1/posts/add")
272 .add_query_params(&[("url", URL), ("description", TITLE), ("shared", "yes")])
273 .build()
274 .unwrap();
275 let client = SingleTestClient::new_raw(endpoint, "");
276
277 let endpoint = Add::builder()
278 .url(test_url())
279 .description(TITLE)
280 .shared(true)
281 .build()
282 .unwrap();
283 api::ignore(endpoint).query(&client).unwrap();
284 }
285
286 #[test]
287 fn endpoint_toread() {
288 let endpoint = ExpectedUrl::builder()
289 .endpoint("v1/posts/add")
290 .add_query_params(&[("url", URL), ("description", TITLE), ("toread", "yes")])
291 .build()
292 .unwrap();
293 let client = SingleTestClient::new_raw(endpoint, "");
294
295 let endpoint = Add::builder()
296 .url(test_url())
297 .description(TITLE)
298 .toread(true)
299 .build()
300 .unwrap();
301 api::ignore(endpoint).query(&client).unwrap();
302 }
303
304 #[test]
305 fn limit() {
306 assert_eq!(Add::secs_between_calls(), 3)
307 }
308}