bms_table/fetch/
reqwest.rs1#![cfg(feature = "reqwest")]
21
22use anyhow::{Result, anyhow};
23use serde_json::Value;
24use std::collections::HashMap;
25use url::Url;
26
27use crate::{BmsTable, BmsTableIndexItem, BmsTableRaw};
28
29pub async fn fetch_table_full(web_url: &str) -> Result<(BmsTable, BmsTableRaw)> {
45 let web_url = Url::parse(web_url)?;
46 let web_response = reqwest::Client::new()
47 .get(web_url.clone())
48 .send()
49 .await
50 .map_err(|e| anyhow!("When fetching web: {e}"))?
51 .text()
52 .await
53 .map_err(|e| anyhow!("When parsing web response: {e}"))?;
54 let (header_url, header_json, header_raw) =
55 match crate::fetch::get_web_header_json_value(&web_response)? {
56 crate::fetch::HeaderQueryContent::Url(header_url_string) => {
57 let header_url = web_url.join(&header_url_string)?;
58 let header_response = reqwest::Client::new()
59 .get(header_url.clone())
60 .send()
61 .await
62 .map_err(|e| anyhow!("When fetching header: {e}"))?;
63 let header_response_string = header_response
64 .text()
65 .await
66 .map_err(|e| anyhow!("When parsing header response: {e}"))?;
67 let crate::fetch::HeaderQueryContent::Json(header_json) =
68 crate::fetch::get_web_header_json_value(&header_response_string)?
69 else {
70 return Err(anyhow!(
71 "Cycled header found. web_url: {web_url}, header_url: {header_url_string}"
72 ));
73 };
74 (header_url, header_json, header_response_string)
75 }
76 crate::fetch::HeaderQueryContent::Json(value) => {
77 let header_raw = serde_json::to_string(&value)?;
78 (web_url, value, header_raw)
79 }
80 };
81 let data_url_str = header_json
82 .get("data_url")
83 .ok_or_else(|| anyhow!("\"data_url\" not found in header json!"))?
84 .as_str()
85 .ok_or_else(|| anyhow!("\"data_url\" is not a string!"))?;
86 let data_url = header_url.join(data_url_str)?;
87 let data_response = reqwest::Client::new()
88 .get(data_url)
89 .send()
90 .await
91 .map_err(|e| anyhow!("When fetching web: {e}"))?
92 .text()
93 .await
94 .map_err(|e| anyhow!("When parsing web response: {e}"))?;
95 let data_json: Value = serde_json::from_str(&data_response)?;
96 let header: crate::BmsTableHeader = serde_json::from_value(header_json)
98 .map_err(|e| anyhow!("When parsing header json: {e}"))?;
99 let data: crate::BmsTableData =
100 serde_json::from_value(data_json).map_err(|e| anyhow!("When parsing data json: {e}"))?;
101 Ok((
102 BmsTable { header, data },
103 BmsTableRaw {
104 header_raw,
105 data_raw: data_response,
106 },
107 ))
108}
109
110pub async fn fetch_table(web_url: &str) -> Result<BmsTable> {
114 let (table, _raw) = fetch_table_full(web_url).await?;
115 Ok(table)
116}
117
118pub async fn fetch_table_index(web_url: &str) -> Result<Vec<BmsTableIndexItem>> {
123 let (out, _raw) = fetch_table_index_full(web_url).await?;
124 Ok(out)
125}
126
127pub async fn fetch_table_index_full(web_url: &str) -> Result<(Vec<BmsTableIndexItem>, String)> {
131 let web_url = Url::parse(web_url)?;
132 let response_text = reqwest::Client::new()
133 .get(web_url)
134 .send()
135 .await
136 .map_err(|e| anyhow!("When fetching table index: {e}"))?
137 .text()
138 .await
139 .map_err(|e| anyhow!("When parsing table index response: {e}"))?;
140
141 let value: Value = serde_json::from_str(&response_text)?;
142 let arr = value
143 .as_array()
144 .ok_or_else(|| anyhow!("Table index root is not an array"))?;
145
146 let mut out = Vec::with_capacity(arr.len());
147 for (idx, item) in arr.iter().enumerate() {
148 let obj = item
149 .as_object()
150 .ok_or_else(|| anyhow!("Table index item #{idx} is not an object"))?;
151
152 let name = obj
153 .get("name")
154 .and_then(|v| v.as_str())
155 .ok_or_else(|| anyhow!("Missing required field 'name' at index {idx}"))?;
156 let symbol = obj
157 .get("symbol")
158 .and_then(|v| v.as_str())
159 .ok_or_else(|| anyhow!("Missing required field 'symbol' at index {idx}"))?;
160 let url_str = obj
161 .get("url")
162 .and_then(|v| v.as_str())
163 .ok_or_else(|| anyhow!("Missing required field 'url' at index {idx}"))?;
164 let url = Url::parse(url_str)?;
165
166 #[cfg(feature = "serde")]
167 let extra = {
168 let mut m: HashMap<String, Value> = HashMap::new();
169 for (k, v) in obj.iter() {
170 if k != "name" && k != "symbol" && k != "url" {
171 m.insert(k.clone(), v.clone());
172 }
173 }
174 m
175 };
176
177 let entry = BmsTableIndexItem {
178 name: name.to_string(),
179 symbol: symbol.to_string(),
180 url,
181 #[cfg(feature = "serde")]
182 extra,
183 };
184 out.push(entry);
185 }
186
187 Ok((out, response_text))
188}