use crate::{
agents::Agent,
commit::sign_message,
errors::AtomicResult,
parse::{parse_json_ad_resource, ParseOpts},
Resource, Storelike,
};
#[tracing::instrument(skip(store), level = "info")]
pub fn fetch_resource(
subject: &str,
store: &impl Storelike,
client_agent: Option<&Agent>,
) -> AtomicResult<Resource> {
let body = fetch_body(subject, crate::parse::JSON_AD_MIME, client_agent)?;
let resource = parse_json_ad_resource(&body, store, &ParseOpts::default())
.map_err(|e| format!("Error parsing body of {}. {}", subject, e))?;
Ok(resource)
}
pub fn get_authentication_headers(url: &str, agent: &Agent) -> AtomicResult<Vec<(String, String)>> {
let mut headers = Vec::new();
let now = crate::utils::now().to_string();
let message = format!("{} {}", url, now);
let signature = sign_message(
&message,
agent
.private_key
.as_ref()
.ok_or("No private key in agent")?,
&agent.public_key,
)?;
headers.push(("x-atomic-public-key".into(), agent.public_key.to_string()));
headers.push(("x-atomic-signature".into(), signature));
headers.push(("x-atomic-timestamp".into(), now));
headers.push(("x-atomic-agent".into(), agent.subject.to_string()));
Ok(headers)
}
#[tracing::instrument(level = "info")]
pub fn fetch_body(
url: &str,
content_type: &str,
client_agent: Option<&Agent>,
) -> AtomicResult<String> {
if !url.starts_with("http") {
return Err(format!("Could not fetch url '{}', must start with http.", url).into());
}
if let Some(agent) = client_agent {
get_authentication_headers(url, agent)?;
}
let agent = ureq::builder()
.timeout(std::time::Duration::from_secs(2))
.build();
let resp = agent
.get(url)
.set("Accept", content_type)
.call()
.map_err(|e| format!("Error when server tried fetching {} : {}", url, e))?;
let status = resp.status();
let body = resp
.into_string()
.map_err(|e| format!("Could not parse HTTP response for {}: {}", url, e))?;
if status != 200 {
return Err(format!(
"Could not fetch url '{}'. Status: {}. Body: {}",
url, status, body
)
.into());
};
Ok(body)
}
pub fn post_commit(commit: &crate::Commit, store: &impl Storelike) -> AtomicResult<()> {
let server_url = crate::utils::server_url(commit.get_subject())?;
let endpoint = format!("{}commit", server_url);
post_commit_custom_endpoint(&endpoint, commit, store)
}
fn post_commit_custom_endpoint(
endpoint: &str,
commit: &crate::Commit,
store: &impl Storelike,
) -> AtomicResult<()> {
let json = commit.into_resource(store)?.to_json_ad()?;
let agent = ureq::builder()
.timeout(std::time::Duration::from_secs(2))
.build();
let resp = agent
.post(endpoint)
.set("Content-Type", "application/json")
.send_string(&json)
.map_err(|e| format!("Error when posting commit to {} : {}", endpoint, e))?;
if resp.status() != 200 {
Err(format!(
"Failed applying commit to {}. Status: {} Body: {}",
endpoint,
resp.status(),
resp.into_string()?
)
.into())
} else {
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[ignore]
fn fetch_resource_basic() {
let store = crate::Store::init().unwrap();
let resource = fetch_resource(crate::urls::SHORTNAME, &store, None).unwrap();
let shortname = resource.get(crate::urls::SHORTNAME).unwrap();
assert!(shortname.to_string() == "shortname");
}
#[test]
#[ignore]
fn post_commit_basic() {
}
}