Skip to main content

typesafe/
client.rs

1//! The async client.
2
3use serde::Serialize;
4
5use crate::config::{CallOptions, Config};
6use crate::error::Error;
7use crate::question::Questions;
8use crate::request::{self, Prepared, Reply, Retries, Success};
9use crate::response::{Model, Response};
10
11/// An async client for the System One API. It needs a tokio runtime with the
12/// time driver enabled, which `#[tokio::main]` provides.
13///
14/// Cloning is cheap and shares the connection pool.
15///
16/// ```no_run
17/// use typesafe::{Choice, Client, Noul, Questions};
18///
19/// # async fn run() -> Result<(), typesafe::Error> {
20/// let client = Client::new(); // reads TYPESAFE_API_KEY
21/// let questions = Questions::new()
22///     .ask("billing", Noul::new("Is this ticket about billing?"))
23///     .ask("tone", Choice::new("What is the customer's tone?").options(["calm", "angry"]));
24///
25/// let response = client.system_one("I was charged twice!", &questions).await?;
26/// if response.noul("billing").unwrap().yes() {
27///     println!("billing, tone {}", response.choice("tone").unwrap().choice);
28/// }
29/// # Ok(())
30/// # }
31/// ```
32#[derive(Clone, Debug)]
33pub struct Client {
34    config: Config,
35    http: reqwest::Client,
36}
37
38impl Default for Client {
39    fn default() -> Self {
40        Client::new()
41    }
42}
43
44impl Client {
45    /// A client configured from the environment ([`Config::from_env`]).
46    ///
47    /// # Panics
48    ///
49    /// If the TLS backend can't be initialized, as `reqwest::Client::new`
50    /// does. [`Client::with_config`] returns that as an error instead.
51    pub fn new() -> Self {
52        Client::with_config(Config::from_env()).expect("the HTTP client could not be built")
53    }
54
55    pub fn with_config(config: Config) -> Result<Self, Error> {
56        let http = reqwest::Client::builder()
57            .build()
58            .map_err(Error::from_reqwest)?;
59        Ok(Client { config, http })
60    }
61
62    pub fn config(&self) -> &Config {
63        &self.config
64    }
65
66    /// Whether the client has an API key.
67    pub fn is_configured(&self) -> bool {
68        self.config.is_configured()
69    }
70
71    /// Asks `questions` about `state` in one request.
72    ///
73    /// `state` is a string, or anything that serializes to a JSON object or
74    /// array. A struct keeps its field order on the wire.
75    pub async fn system_one<S: Serialize + ?Sized>(
76        &self,
77        state: &S,
78        questions: &Questions,
79    ) -> Result<Response, Error> {
80        self.system_one_with(state, questions, &CallOptions::default())
81            .await
82    }
83
84    /// [`system_one`](Client::system_one) with per-call overrides.
85    pub async fn system_one_with<S: Serialize + ?Sized>(
86        &self,
87        state: &S,
88        questions: &Questions,
89        options: &CallOptions,
90    ) -> Result<Response, Error> {
91        let prepared = request::system_one(&self.config, state, questions, options)?;
92        let success = self.execute(prepared).await?;
93        request::decode_system_one(success, questions)
94    }
95
96    /// The model names the account can send, aliases included. Versioned ids
97    /// such as `jev-1.13.0` are accepted whether or not they're listed.
98    pub async fn models(&self) -> Result<Vec<Model>, Error> {
99        let prepared = request::models(&self.config)?;
100        let success = self.execute(prepared).await?;
101        request::decode_models(success)
102    }
103
104    async fn execute(&self, prepared: Prepared) -> Result<Success, Error> {
105        let mut retries = Retries::start(prepared.retry.clone());
106        loop {
107            retries.attempts += 1;
108            let result = self.attempt(&prepared, &retries).await;
109            match retries.after(&result) {
110                Some(delay) => tokio::time::sleep(delay).await,
111                None => return request::finish(result),
112            }
113        }
114    }
115
116    async fn attempt(&self, prepared: &Prepared, retries: &Retries) -> Result<Reply, Error> {
117        let mut builder = self
118            .http
119            .request(prepared.method.clone(), &prepared.url)
120            .headers(prepared.headers.clone())
121            .timeout(prepared.timeout);
122        if let Some((name, value)) = retries.retry_count_header() {
123            builder = builder.header(name, value);
124        }
125        if let Some(body) = &prepared.body {
126            builder = builder.body(body.clone());
127        }
128        let response = builder.send().await.map_err(Error::from_reqwest)?;
129        let status = response.status().as_u16();
130        let headers = response.headers().clone();
131        let body = response.bytes().await.map_err(Error::from_reqwest)?;
132        Ok(Reply {
133            status,
134            headers,
135            body: body.to_vec(),
136        })
137    }
138}