add http-json, rss, and media data source adapters
http-json: generic HTTP+JSON polling adapter, converts serde_json to domain Value. 4 tests. rss: XML RSS feed parser, extracts items into Value array. 1 test. media: Navidrome/Subsonic getNowPlaying adapter. 2 tests with fake server.
This commit is contained in:
68
crates/adapters/http-json/src/lib.rs
Normal file
68
crates/adapters/http-json/src/lib.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use domain::{DataSource, DataSourcePort, Value};
|
||||
|
||||
pub struct HttpJsonAdapter {
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HttpJsonError {
|
||||
Request(reqwest::Error),
|
||||
NoUrl,
|
||||
Parse(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for HttpJsonError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
HttpJsonError::Request(e) => write!(f, "request: {e}"),
|
||||
HttpJsonError::NoUrl => write!(f, "no url configured"),
|
||||
HttpJsonError::Parse(e) => write!(f, "parse: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpJsonAdapter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn json_to_value(json: serde_json::Value) -> Value {
|
||||
match json {
|
||||
serde_json::Value::Null => Value::Null,
|
||||
serde_json::Value::Bool(b) => Value::Bool(b),
|
||||
serde_json::Value::Number(n) => Value::Number(n.as_f64().unwrap_or(0.0)),
|
||||
serde_json::Value::String(s) => Value::String(s),
|
||||
serde_json::Value::Array(arr) => {
|
||||
Value::Array(arr.into_iter().map(json_to_value).collect())
|
||||
}
|
||||
serde_json::Value::Object(map) => {
|
||||
Value::Object(map.into_iter().map(|(k, v)| (k, json_to_value(v))).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DataSourcePort for HttpJsonAdapter {
|
||||
type Error = HttpJsonError;
|
||||
|
||||
async fn poll(&self, source: &DataSource) -> Result<Value, Self::Error> {
|
||||
let url = source.config.url.as_ref().ok_or(HttpJsonError::NoUrl)?;
|
||||
|
||||
let mut req = self.client.get(url);
|
||||
|
||||
for (key, val) in &source.config.headers {
|
||||
req = req.header(key, val);
|
||||
}
|
||||
|
||||
if let Some(api_key) = &source.config.api_key {
|
||||
req = req.header("Authorization", format!("Bearer {api_key}"));
|
||||
}
|
||||
|
||||
let resp = req.send().await.map_err(HttpJsonError::Request)?;
|
||||
let json: serde_json::Value = resp.json().await.map_err(HttpJsonError::Request)?;
|
||||
|
||||
Ok(json_to_value(json))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user