Skip to main content

typesafe/
blocking.rs

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