#![warn(
anonymous_parameters,
missing_copy_implementations,
missing_debug_implementations,
rust_2018_idioms,
rustdoc::private_doc_tests,
trivial_casts,
trivial_numeric_casts,
unused,
future_incompatible,
nonstandard_style,
unsafe_code,
unused_import_braces,
unused_results,
variant_size_differences
)]
use std::io::Read;
use eyre::{Result, WrapErr};
use isahc::{prelude::*, Request};
use serde::{Deserialize, Serialize};
const DEFAULT_TYPEFORM_URL: &str = "https://api.typeform.com";
const GET_FORM_RESPONSES_PATH: &str = "/forms/{form_id}/responses";
#[derive(Debug)]
pub struct Typeform {
url: String,
form_id: String,
token: String,
}
impl Typeform {
pub fn new(form_id: &str, token: &str) -> Typeform {
Typeform {
url: DEFAULT_TYPEFORM_URL.to_string(),
form_id: form_id.to_string(),
token: token.to_owned(),
}
}
pub fn responses(&self) -> Result<Responses> {
Request::get(format!(
"{}{}",
self.url,
GET_FORM_RESPONSES_PATH.replace("{form_id}", &self.form_id),
))
.header("Authorization", format!("Bearer {}", &self.token))
.body(())
.wrap_err("Failed to build a request.")?
.send()
.wrap_err("Failed to send get request.")?
.json()
.wrap_err("Failed to deserialize a response.")
}
pub fn responses_after(&self, token: &str) -> Result<Responses> {
Request::get(format!(
"{}{}?after={}&page_size=1",
self.url,
GET_FORM_RESPONSES_PATH.replace("{form_id}", &self.form_id),
token,
))
.header("Authorization", format!("Bearer {}", &self.token))
.body(())
.wrap_err("Failed to build a request.")?
.send()
.wrap_err("Failed to send get request.")?
.json()
.wrap_err("Failed to deserialize a response.")
}
pub fn response_file(
&self,
response_id: String,
field_id: String,
filename: String,
) -> Result<Vec<u8>> {
let mut bytes = Vec::new();
let url = format!(
"{}{}/{}/fields/{}/files/{}",
self.url,
GET_FORM_RESPONSES_PATH.replace("{form_id}", &self.form_id),
response_id,
field_id,
urlencoding::encode(&filename)
);
dbg!(&url);
let _size = Request::get(url)
.header("Authorization", format!("Bearer {}", &self.token))
.body(())
.expect("Failed to build a request.")
.send()
.expect("Failed to send get request.")
.body_mut()
.read_to_end(&mut bytes)
.expect("Failed to deserialize a response.");
Ok(bytes)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Responses {
pub total_items: Option<u16>,
pub page_count: Option<u8>,
pub items: Vec<Response>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct Response {
pub token: String,
pub response_id: Option<String>,
pub landed_at: String,
pub submitted_at: String,
pub metadata: Metadata,
pub definition: Option<Definition>,
pub answers: Option<Answers>,
pub calculated: Calculated,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Metadata {
pub user_agent: String,
pub platform: Option<String>,
pub referer: String,
pub network_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Definition {
pub fields: Fields,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Fields(Vec<Field>);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Field {
pub id: String,
pub _type: String,
pub title: String,
pub description: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Answers(Vec<Answer>);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Answer {
pub field: AnswerField,
#[serde(rename = "type")]
pub _type: AnswerType,
pub choice: Option<Choice>,
pub choices: Option<Choices>,
pub date: Option<String>,
pub email: Option<String>,
pub file_url: Option<String>,
pub number: Option<i32>,
pub boolean: Option<bool>,
pub text: Option<String>,
pub url: Option<String>,
pub payment: Option<Payment>,
pub phone_number: Option<String>,
}
impl Answer {
pub fn as_str(&self) -> Option<&str> {
match &self._type {
AnswerType::Text => self.text.as_deref(),
AnswerType::Url => self.url.as_deref(),
AnswerType::FileUrl => self.file_url.as_deref(),
AnswerType::Choice => {
let choice = self.choice.as_ref()?;
match choice {
Choice {
label: Some(label), ..
} => Some(label.as_str()),
Choice {
label: None,
other: Some(other),
} => Some(other.as_str()),
_ => None,
}
}
AnswerType::PhoneNumber => self.phone_number.as_deref(),
AnswerType::Email => self.email.as_deref(),
_ => None,
}
}
}
impl Answers {
pub fn find(&self, _ref: &str) -> Option<&Answer> {
self.0.iter().find(|answer| answer.field._ref == _ref)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AnswerField {
pub id: String,
#[serde(rename = "type")]
pub _type: String,
#[serde(rename = "ref")]
pub _ref: String,
pub title: Option<String>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnswerType {
Choice,
Choices,
Date,
Email,
Url,
FileUrl,
Number,
Boolean,
Text,
Payment,
PhoneNumber,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Choice {
pub label: Option<String>,
pub other: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Labels(Vec<String>);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Choices {
pub labels: Labels,
pub other: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Payment {
pub amount: String,
pub last4: String,
pub name: String,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Calculated {
pub score: i32,
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::BufReader;
#[test]
fn parse_valid_responses_from_json_should_pass() {
let file = File::open("tests/typeform_responses.json").expect("Failed to open a file.");
let reader = BufReader::new(file);
let _responses: Responses =
serde_json::from_reader(reader).expect("Failed to build responses from reader.");
}
#[test]
fn parse_valid_responses2_from_json_should_pass() {
let file = File::open("tests/typeform_responses2.json").expect("Failed to open a file.");
let reader = BufReader::new(file);
let _responses: Responses =
serde_json::from_reader(reader).expect("Failed to build responses from reader.");
}
#[test]
fn parse_valid_responses3_from_json_should_pass() {
let file = File::open("tests/typeform_responses3.json").expect("Failed to open a file.");
let reader = BufReader::new(file);
let _responses: Responses =
serde_json::from_reader(reader).expect("Failed to build responses from reader.");
}
#[test]
fn parse_valid_responses4_from_json_should_pass() {
let file = File::open("tests/typeform_responses4.json").expect("Failed to open a file.");
let reader = BufReader::new(file);
let _responses: Responses =
serde_json::from_reader(reader).expect("Failed to build responses from reader.");
}
#[test]
fn build_request() {
Request::get(format!("https://api.typeform.com/forms/ED6iRRjj/responses/448xvbqqrfemwrz4cu038448xvqx6d3w/fields/wYuykcQCh8F3/files/{}", urlencoding::encode("860786e2f952-1080х1080.png")))
.body(())
.expect("Failed to build a request.");
}
}