spacetimedb_cli/
api.rs

1use reqwest::{header, Client, RequestBuilder};
2use serde::Deserialize;
3use serde_json::value::RawValue;
4
5use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9;
6use spacetimedb_lib::de::serde::DeserializeWrapper;
7use spacetimedb_lib::sats::ProductType;
8use spacetimedb_lib::Identity;
9
10use crate::util::{AuthHeader, ResponseExt};
11
12static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
13
14#[derive(Debug, Clone)]
15pub struct Connection {
16    pub(crate) host: String,
17    pub(crate) database_identity: Identity,
18    pub(crate) database: String,
19    pub(crate) auth_header: AuthHeader,
20}
21
22impl Connection {
23    pub fn db_uri(&self, endpoint: &str) -> String {
24        [
25            &self.host,
26            "/v1/database/",
27            &self.database_identity.to_hex(),
28            "/",
29            endpoint,
30        ]
31        .concat()
32    }
33}
34
35pub fn build_client(con: &Connection) -> Client {
36    let mut builder = Client::builder().user_agent(APP_USER_AGENT);
37
38    if let Some(auth_header) = con.auth_header.to_header() {
39        let headers = http::HeaderMap::from_iter([(header::AUTHORIZATION, auth_header)]);
40
41        builder = builder.default_headers(headers);
42    }
43
44    builder.build().unwrap()
45}
46
47pub struct ClientApi {
48    pub con: Connection,
49    client: Client,
50}
51
52impl ClientApi {
53    pub fn new(con: Connection) -> Self {
54        let client = build_client(&con);
55        Self { con, client }
56    }
57
58    pub fn sql(&self) -> RequestBuilder {
59        self.client.post(self.con.db_uri("sql"))
60    }
61
62    /// Reads the `ModuleDef` from the `schema` endpoint.
63    pub async fn module_def(&self) -> anyhow::Result<RawModuleDefV9> {
64        let res = self
65            .client
66            .get(self.con.db_uri("schema"))
67            .query(&[("version", "9")])
68            .send()
69            .await?;
70        let DeserializeWrapper(module_def) = res.json_or_error().await?;
71        Ok(module_def)
72    }
73
74    pub async fn call(&self, reducer_name: &str, arg_json: String) -> anyhow::Result<reqwest::Response> {
75        Ok(self
76            .client
77            .post(self.con.db_uri("call") + "/" + reducer_name)
78            .header(http::header::CONTENT_TYPE, "application/json")
79            .body(arg_json)
80            .send()
81            .await?)
82    }
83}
84
85#[derive(Debug, Clone, Deserialize)]
86pub struct StmtResultJson<'a> {
87    pub schema: ProductType,
88    #[serde(borrow)]
89    pub rows: Vec<&'a RawValue>,
90}
91
92pub fn from_json_seed<'de, T: serde::de::DeserializeSeed<'de>>(
93    s: &'de str,
94    seed: T,
95) -> Result<T::Value, serde_json::Error> {
96    let mut de = serde_json::Deserializer::from_str(s);
97    let out = seed.deserialize(&mut de)?;
98    de.end()?;
99    Ok(out)
100}