linera_faucet_client/
lib.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! The client component of the Linera faucet.
5
6// TODO(#3362): generate this code
7
8use linera_base::{crypto::ValidatorPublicKey, data_types::ChainDescription};
9use linera_client::config::GenesisConfig;
10use linera_version::VersionInfo;
11use thiserror_context::Context;
12
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum ErrorInner {
16    #[error("JSON parsing error: {0:?}")]
17    Json(#[from] serde_json::Error),
18    #[error("GraphQL error: {0:?}")]
19    GraphQl(Vec<serde_json::Value>),
20    #[error("HTTP error: {0:?}")]
21    Http(#[from] reqwest::Error),
22}
23
24thiserror_context::impl_context!(Error(ErrorInner));
25
26/// A faucet instance that can be queried.
27#[derive(Debug, Clone)]
28pub struct Faucet {
29    url: String,
30}
31
32impl Faucet {
33    pub fn new(url: String) -> Self {
34        Self { url }
35    }
36
37    pub fn url(&self) -> &str {
38        &self.url
39    }
40
41    async fn query<Response: serde::de::DeserializeOwned>(
42        &self,
43        query: impl AsRef<str>,
44    ) -> Result<Response, Error> {
45        let query = query.as_ref();
46
47        #[derive(serde::Deserialize)]
48        struct GraphQlResponse<T> {
49            data: Option<T>,
50            errors: Option<Vec<serde_json::Value>>,
51        }
52
53        let builder = reqwest::ClientBuilder::new();
54
55        #[cfg(not(target_arch = "wasm32"))]
56        let builder = builder.timeout(std::time::Duration::from_secs(30));
57
58        let response: GraphQlResponse<Response> = builder
59            .build()
60            .unwrap()
61            .post(&self.url)
62            .json(&serde_json::json!({
63                "query": query,
64            }))
65            .send()
66            .await
67            .with_context(|| format!("executing query {query:?}"))?
68            .error_for_status()?
69            .json()
70            .await?;
71
72        if let Some(errors) = response.errors {
73            Err(ErrorInner::GraphQl(errors).into())
74        } else {
75            Ok(response
76                .data
77                .expect("no errors present but no data returned"))
78        }
79    }
80
81    pub async fn genesis_config(&self) -> Result<GenesisConfig, Error> {
82        #[derive(serde::Deserialize)]
83        #[serde(rename_all = "camelCase")]
84        struct Response {
85            genesis_config: GenesisConfig,
86        }
87
88        Ok(self
89            .query::<Response>("query { genesisConfig }")
90            .await?
91            .genesis_config)
92    }
93
94    pub async fn version_info(&self) -> Result<VersionInfo, Error> {
95        #[derive(serde::Deserialize)]
96        struct Response {
97            version: VersionInfo,
98        }
99
100        Ok(self.query::<Response>("query { version }").await?.version)
101    }
102
103    pub async fn claim(
104        &self,
105        owner: &linera_base::identifiers::AccountOwner,
106    ) -> Result<ChainDescription, Error> {
107        #[derive(serde::Deserialize)]
108        struct Response {
109            claim: ChainDescription,
110        }
111
112        Ok(self
113            .query::<Response>(format!("mutation {{ claim(owner: \"{owner}\") }}"))
114            .await?
115            .claim)
116    }
117
118    pub async fn current_validators(&self) -> Result<Vec<(ValidatorPublicKey, String)>, Error> {
119        #[derive(serde::Deserialize)]
120        #[serde(rename_all = "camelCase")]
121        struct Validator {
122            public_key: ValidatorPublicKey,
123            network_address: String,
124        }
125
126        #[derive(serde::Deserialize)]
127        #[serde(rename_all = "camelCase")]
128        struct Response {
129            current_validators: Vec<Validator>,
130        }
131
132        Ok(self
133            .query::<Response>("query { currentValidators { publicKey networkAddress } }")
134            .await?
135            .current_validators
136            .into_iter()
137            .map(|validator| (validator.public_key, validator.network_address))
138            .collect())
139    }
140}