gitig/
client.rs

1use std::str;
2
3use crate::error::Error;
4use crate::error::Result;
5
6const API_URL: &str = "https://www.toptal.com/developers/gitignore/api";
7
8pub fn list_templates() -> Result<Vec<String>> {
9    get(&list_endpoint())
10        .and_then(|res| res.text())
11        .map_err(|_| Error::fetch_templates_failed())
12        .map(|content| {
13            content
14                .lines()
15                .filter(|l| !l.is_empty())
16                .flat_map(|l| l.split(',').map(|t| t.to_owned()))
17                .collect::<Vec<String>>()
18        })
19}
20
21pub fn create<'a, I>(templates: I) -> Result<String>
22where
23    I: IntoIterator<Item = &'a str>,
24{
25    let templates = templates
26        .into_iter()
27        .map(|t| t.to_owned())
28        .collect::<Vec<String>>();
29    let res = get(&create_endpoint(&templates))?;
30    if res.status().is_client_error() || res.status().is_server_error() {
31        return match res.status() {
32            reqwest::StatusCode::NOT_FOUND => handle_404(&templates),
33            _ => Err(Error::generic()),
34        };
35    }
36    Ok(res.text()?)
37}
38
39fn list_endpoint() -> String {
40    format!("{}/list", API_URL)
41}
42
43fn create_endpoint(templates: &[String]) -> String {
44    let templates_str = templates.join(",");
45    format!("{}/{}", API_URL, templates_str)
46}
47
48fn get(url: &str) -> reqwest::Result<reqwest::blocking::Response> {
49    let client = reqwest::blocking::ClientBuilder::new()
50        .user_agent("Mozilla/5.0")
51        .build()?;
52    client.get(url).send()
53}
54
55fn handle_404(templates: &[String]) -> Result<String> {
56    // try to provide the user with a helpful error message
57    let Ok(known_templates) = list_templates() else { return Err(Error::generic());};
58    let unknown_templates = find_unknown_templates(&known_templates, templates);
59    let err = if !unknown_templates.is_empty() {
60        Error::TemplateNotFound(unknown_templates)
61    } else {
62        Error::generic()
63    };
64    Err(err)
65}
66
67fn find_unknown_templates(known_templates: &[String], templates: &[String]) -> Vec<String> {
68    // We could convert to HashSets and do a difference, but that would require
69    // cloning the known_templates Vec. And since templates is likely < 10, it's
70    // probably faster to just iterate over it.
71    templates
72        .iter()
73        .filter(|t| !known_templates.contains(t))
74        .map(|t| (*t).to_owned())
75        .collect::<Vec<String>>()
76}