chain_registry/
get.rs

1use crate::github::Content;
2use eyre::Report;
3use http::Method;
4use serde::de::DeserializeOwned;
5
6pub use crate::{assets::*, chain::*, paths::*};
7
8#[cfg(all(feature = "registry-cache"))]
9pub use self::cache::*;
10
11
12const VERSION: &str = env!("CARGO_PKG_VERSION");
13// In the future we may want to provide a way for a user to set the desired ref for the registry
14// module to use when querying.
15const GIT_REF: &str = "d063b0fd6d1c20d6476880e5ea2212ade009f69e";
16const RAW_FILE_REPO_URL: &str = "https://raw.githubusercontent.com/cosmos/chain-registry";
17const REPO_URL: &str = "https://api.github.com/repos/cosmos/chain-registry/contents";
18
19async fn get(url: String) -> Result<String, Report> {
20    let client = reqwest::Client::new();
21    let req = client
22        .request(Method::GET, url)
23        .header("User-Agent", format!("ocular/{}", VERSION))
24        .build()?;
25    Ok(client.execute(req).await?.text().await?)
26}
27
28/// Gets a list of chain names from the registry
29pub async fn list_chains() -> Result<Vec<String>, Report> {
30    let url = format!("{}?ref={}", REPO_URL, GIT_REF,);
31    let json: String = get(url).await?;
32    let contents: Vec<Content> = serde_json::from_str(json.as_str())?;
33
34    Ok(contents
35        .iter()
36        .filter(|c| c.type_field == "dir" && !c.name.starts_with('_') && c.name != ".github")
37        .map(|c| c.clone().name)
38        .collect())
39}
40
41/// Gets a list of path names from the registry in the form <chain_a>-<chain_b>
42pub async fn list_paths() -> Result<Vec<String>, Report> {
43    let url = format!("{}/_IBC?ref={}", REPO_URL, GIT_REF,);
44    let json: String = get(url).await?;
45    let contents: Vec<Content> = serde_json::from_str(json.as_str())?;
46
47    Ok(contents
48        .iter()
49        .filter(|c| c.type_field == "file" && !c.name.starts_with('_') && c.name.ends_with(".json"))
50        .map(|c| c.name[..c.name.len() - ".json".len()].to_string())
51        .collect())
52}
53
54/// Retrieves the deserialized `assets.json` for a given chain. The result will contain
55/// `None` if the there is no `assets.json` present.
56///
57/// # Arguments
58///
59/// * `name` - The chain name. Must match the name of the chain's folder in the root directory of the
60/// [chain registry](https://github.com/cosmos/chain-registry).
61pub async fn get_assets(name: &str) -> Result<Option<AssetList>, Report> {
62    let path = format!("{}/assetlist.json", name);
63    let data = get_file_content(GIT_REF, &path).await?;
64
65    Ok(parse_json(data).await)
66}
67
68/// Retrieves the deserialized `chain.json` for a given chain. The result will contain
69/// `None` if the there is no `chain.json` present.
70///
71/// # Arguments
72///
73/// * `name` - The chain name. Must match the name of the chain's folder in the root directory of the
74/// [chain registry](https://github.com/cosmos/chain-registry).
75pub async fn get_chain(name: &str) -> Result<Option<ChainInfo>, Report> {
76    let path = format!("{}/chain.json", name);
77    let data = get_file_content(GIT_REF, &path).await?;
78
79    Ok(parse_json(data).await)
80}
81
82/// Retrieves the deserialized IBC path json for a given pair of chains. The result will contain
83/// `None` if the there is no path present.
84///
85/// # Arguments
86///
87/// * `name` - The chain name. Must match the name of the chain's folder in the root directory of the
88/// [chain registry](https://github.com/cosmos/chain-registry).
89pub async fn get_path(chain_a: &str, chain_b: &str) -> Result<Option<IBCPath>, Report> {
90    // path names order the chain names alphabetically
91    let path = format!(
92        "_IBC/{}-{}.json",
93        chain_a.min(chain_b),
94        chain_a.max(chain_b)
95    );
96    let data = get_file_content(GIT_REF, &path).await?;
97
98    Ok(parse_json(data).await)
99}
100
101async fn get_file_content(r#ref: &str, path: &str) -> Result<String, Report> {
102    let url = format!("{}/{}/{}", RAW_FILE_REPO_URL, r#ref, path);
103    Ok(reqwest::get(url).await?.text().await?)
104}
105
106async fn parse_json<T>(data: String) -> Option<T>
107where
108    T: DeserializeOwned,
109{
110    serde_json::from_str(&data).ok()
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use assay::assay;
117
118    #[assay]
119    async fn gets_content_from_registry() {
120        let result = get_file_content(GIT_REF, "cosmoshub/chain.json").await;
121
122        result.unwrap();
123    }
124
125    #[assay]
126    async fn parses_chain_info() {
127        let result = get_file_content(GIT_REF, "cosmoshub/chain.json")
128            .await
129            .unwrap();
130        let result = parse_json::<ChainInfo>(result).await;
131
132        result.unwrap();
133    }
134
135    #[assay]
136    async fn gets_chain() {
137        let result = get_chain("cosmoshub").await;
138
139        result.unwrap();
140    }
141
142    #[assay]
143    async fn lists_chains() {
144        list_chains().await.unwrap();
145    }
146
147    #[assay]
148    async fn lists_paths() {
149        let paths = list_paths().await.unwrap();
150        assert!(paths.len() > 0);
151        paths
152            .iter()
153            .for_each(|path| assert!(!path.ends_with(".json")))
154    }
155
156    #[assay]
157    async fn gets_path_in_order() {
158        let chain_a = "cosmoshub";
159        let chain_b = "osmosis";
160        let result = get_path(chain_a, chain_b).await.unwrap().unwrap();
161        assert_eq!(result.chain_1.chain_name, "cosmoshub");
162        assert_eq!(result.chain_2.chain_name, "osmosis");
163    }
164
165    #[assay]
166    async fn gets_path_out_of_order() {
167        let chain_a = "cosmoshub";
168        let chain_b = "osmosis";
169        let result = get_path(chain_b, chain_a).await.unwrap().unwrap();
170        assert_eq!(result.chain_1.chain_name, "cosmoshub");
171        assert_eq!(result.chain_2.chain_name, "osmosis");
172    }
173
174    #[assay]
175    async fn get_path_not_present_returns_none() {
176        let chain_a = "fake";
177        let chain_b = "osmosis";
178        let result = get_path(chain_b, chain_a).await.unwrap();
179        assert!(result.is_none())
180    }
181}