refactor: DDD/CQRS architecture, unified crate layout
- crates: common→application, api→presentation, infrastructure/*→adapters/* - new crates: api-types, infra-wiring - domain: errors/, models/, value_objects/, ports/, services/ - application: CQRS use cases (songs/, tabs/) w/ commands, queries, deps - unified DomainError replaces RepositoryError - workspace deps, unused dep cleanup - fix: parse plain-text chord lines (UG drops spans mid-song) - tests extracted to separate modules (tests/ dirs)
This commit is contained in:
52
crates/adapters/ug-parser/src/fetcher.rs
Normal file
52
crates/adapters/ug-parser/src/fetcher.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use async_trait::async_trait;
|
||||
use domain::{FetchError, TabFetcherPort, TabSource};
|
||||
|
||||
pub struct UgTabFetcher {
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl UgTabFetcher {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UgTabFetcher {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TabFetcherPort for UgTabFetcher {
|
||||
async fn fetch(&self, source: TabSource) -> Result<String, FetchError> {
|
||||
match source {
|
||||
TabSource::File(path) => Ok(tokio::fs::read_to_string(&path).await?),
|
||||
TabSource::Url(url) => {
|
||||
let resp = self
|
||||
.client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| FetchError::Network(e.to_string()))?;
|
||||
let content_type = resp
|
||||
.headers()
|
||||
.get(reqwest::header::CONTENT_TYPE)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("");
|
||||
if !content_type.contains("text/html") {
|
||||
return Err(FetchError::InvalidContentType);
|
||||
}
|
||||
resp.text()
|
||||
.await
|
||||
.map_err(|e| FetchError::Network(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/fetcher.rs"]
|
||||
mod tests;
|
||||
Reference in New Issue
Block a user