1#![warn(
5 anonymous_parameters,
6 missing_copy_implementations,
7 missing_debug_implementations,
8 rust_2018_idioms,
9 rustdoc::private_doc_tests,
10 trivial_casts,
11 trivial_numeric_casts,
12 unused,
13 future_incompatible,
14 nonstandard_style,
15 unsafe_code,
16 unused_import_braces,
17 unused_results,
18 variant_size_differences
19)]
20
21use std::io::Read;
22
23use eyre::{Result, WrapErr};
24use isahc::{prelude::*, Request};
25use serde::{Deserialize, Serialize};
26
27const DEFAULT_TYPEFORM_URL: &str = "https://api.typeform.com";
28const GET_FORM_RESPONSES_PATH: &str = "/forms/{form_id}/responses";
29
30#[derive(Debug)]
32pub struct Typeform {
33 url: String,
34 form_id: String,
35 token: String,
36}
37
38impl Typeform {
39 pub fn new(form_id: &str, token: &str) -> Typeform {
41 Typeform {
42 url: DEFAULT_TYPEFORM_URL.to_string(),
43 form_id: form_id.to_string(),
44 token: token.to_owned(),
45 }
46 }
47
48 pub fn responses(&self) -> Result<Responses> {
52 Request::get(format!(
53 "{}{}",
54 self.url,
55 GET_FORM_RESPONSES_PATH.replace("{form_id}", &self.form_id),
56 ))
57 .header("Authorization", format!("Bearer {}", &self.token))
58 .body(())
59 .wrap_err("Failed to build a request.")?
60 .send()
61 .wrap_err("Failed to send get request.")?
62 .json()
63 .wrap_err("Failed to deserialize a response.")
64 }
65
66 pub fn responses_after(&self, token: &str) -> Result<Responses> {
70 Request::get(format!(
71 "{}{}?after={}&page_size=1",
72 self.url,
73 GET_FORM_RESPONSES_PATH.replace("{form_id}", &self.form_id),
74 token,
75 ))
76 .header("Authorization", format!("Bearer {}", &self.token))
77 .body(())
78 .wrap_err("Failed to build a request.")?
79 .send()
80 .wrap_err("Failed to send get request.")?
81 .json()
82 .wrap_err("Failed to deserialize a response.")
83 }
84
85 pub fn response_file(
89 &self,
90 response_id: String,
91 field_id: String,
92 filename: String,
93 ) -> Result<Vec<u8>> {
94 let mut bytes = Vec::new();
95 let url = format!(
96 "{}{}/{}/fields/{}/files/{}",
97 self.url,
98 GET_FORM_RESPONSES_PATH.replace("{form_id}", &self.form_id),
99 response_id,
100 field_id,
101 urlencoding::encode(&filename)
102 );
103 dbg!(&url);
104 let _size = Request::get(url)
105 .header("Authorization", format!("Bearer {}", &self.token))
106 .body(())
107 .expect("Failed to build a request.")
108 .send()
109 .expect("Failed to send get request.")
110 .body_mut()
111 .read_to_end(&mut bytes)
112 .expect("Failed to deserialize a response.");
113 Ok(bytes)
114 }
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize)]
119pub struct Responses {
120 pub total_items: Option<u16>,
122 pub page_count: Option<u8>,
124 pub items: Vec<Response>,
126}
127
128#[derive(Clone, Debug, Serialize, Deserialize)]
130#[serde(rename_all = "snake_case")]
131pub struct Response {
132 pub token: String,
134 pub response_id: Option<String>,
136 pub landed_at: String,
138 pub submitted_at: String,
140 pub metadata: Metadata,
142 pub definition: Option<Definition>,
144 pub answers: Option<Answers>,
145 pub calculated: Calculated,
146}
147
148#[derive(Clone, Debug, Serialize, Deserialize)]
150pub struct Metadata {
151 pub user_agent: String,
152 pub platform: Option<String>,
154 pub referer: String,
155 pub network_id: String,
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize)]
161pub struct Definition {
162 pub fields: Fields,
163}
164
165#[derive(Clone, Debug, Serialize, Deserialize)]
166pub struct Fields(Vec<Field>);
167
168#[derive(Clone, Debug, Serialize, Deserialize)]
169pub struct Field {
170 pub id: String,
171 pub _type: String,
172 pub title: String,
173 pub description: String,
174}
175
176#[derive(Clone, Debug, Serialize, Deserialize)]
177pub struct Answers(Vec<Answer>);
178
179#[derive(Clone, Debug, Serialize, Deserialize)]
180pub struct Answer {
181 pub field: AnswerField,
182 #[serde(rename = "type")]
184 pub _type: AnswerType,
185 pub choice: Option<Choice>,
187 pub choices: Option<Choices>,
189 pub date: Option<String>,
190 pub email: Option<String>,
191 pub file_url: Option<String>,
192 pub number: Option<i32>,
193 pub boolean: Option<bool>,
194 pub text: Option<String>,
195 pub url: Option<String>,
196 pub payment: Option<Payment>,
197 pub phone_number: Option<String>,
198}
199
200impl Answer {
201 pub fn as_str(&self) -> Option<&str> {
202 match &self._type {
203 AnswerType::Text => self.text.as_deref(),
204 AnswerType::Url => self.url.as_deref(),
205 AnswerType::FileUrl => self.file_url.as_deref(),
206 AnswerType::Choice => {
207 let choice = self.choice.as_ref()?;
208 match choice {
209 Choice {
210 label: Some(label), ..
211 } => Some(label.as_str()),
212 Choice {
213 label: None,
214 other: Some(other),
215 } => Some(other.as_str()),
216 _ => None,
217 }
218 }
219 AnswerType::PhoneNumber => self.phone_number.as_deref(),
220 AnswerType::Email => self.email.as_deref(),
221 _ => None,
222 }
223 }
224}
225
226impl Answers {
227 pub fn find(&self, _ref: &str) -> Option<&Answer> {
229 self.0.iter().find(|answer| answer.field._ref == _ref)
230 }
231}
232
233#[derive(Clone, Debug, Serialize, Deserialize)]
234pub struct AnswerField {
235 pub id: String,
237 #[serde(rename = "type")]
239 pub _type: String,
240 #[serde(rename = "ref")]
242 pub _ref: String,
243 pub title: Option<String>,
245}
246
247#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
248#[serde(rename_all = "snake_case")]
249pub enum AnswerType {
250 Choice,
251 Choices,
252 Date,
253 Email,
254 Url,
255 FileUrl,
256 Number,
257 Boolean,
258 Text,
259 Payment,
260 PhoneNumber,
261}
262
263#[derive(Clone, Debug, Serialize, Deserialize)]
264pub struct Choice {
265 pub label: Option<String>,
266 pub other: Option<String>,
267}
268
269#[derive(Clone, Debug, Serialize, Deserialize)]
270pub struct Labels(Vec<String>);
271
272#[derive(Clone, Debug, Serialize, Deserialize)]
273pub struct Choices {
274 pub labels: Labels,
275 pub other: Option<String>,
276}
277
278#[derive(Clone, Debug, Serialize, Deserialize)]
279pub struct Payment {
280 pub amount: String,
281 pub last4: String,
282 pub name: String,
283}
284
285#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
286pub struct Calculated {
287 pub score: i32,
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 use std::fs::File;
295 use std::io::BufReader;
296
297 #[test]
298 fn parse_valid_responses_from_json_should_pass() {
299 let file = File::open("tests/typeform_responses.json").expect("Failed to open a file.");
300 let reader = BufReader::new(file);
301 let _responses: Responses =
302 serde_json::from_reader(reader).expect("Failed to build responses from reader.");
303 }
304
305 #[test]
306 fn parse_valid_responses2_from_json_should_pass() {
307 let file = File::open("tests/typeform_responses2.json").expect("Failed to open a file.");
308 let reader = BufReader::new(file);
309 let _responses: Responses =
310 serde_json::from_reader(reader).expect("Failed to build responses from reader.");
311 }
312
313 #[test]
314 fn parse_valid_responses3_from_json_should_pass() {
315 let file = File::open("tests/typeform_responses3.json").expect("Failed to open a file.");
316 let reader = BufReader::new(file);
317 let _responses: Responses =
318 serde_json::from_reader(reader).expect("Failed to build responses from reader.");
319 }
320
321 #[test]
322 fn parse_valid_responses4_from_json_should_pass() {
323 let file = File::open("tests/typeform_responses4.json").expect("Failed to open a file.");
324 let reader = BufReader::new(file);
325 let _responses: Responses =
326 serde_json::from_reader(reader).expect("Failed to build responses from reader.");
327 }
328
329 #[test]
330 fn build_request() {
331 Request::get(format!("https://api.typeform.com/forms/ED6iRRjj/responses/448xvbqqrfemwrz4cu038448xvqx6d3w/fields/wYuykcQCh8F3/files/{}", urlencoding::encode("860786e2f952-1080х1080.png")))
332 .body(())
333 .expect("Failed to build a request.");
334 }
335}