use std::time::Duration;
use serde::Deserialize;
use super::server::ServerUrl;
const DISCOVERY_TIMEOUT_SECS: u64 = 3;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct DiscoveredOrg {
pub org: String,
pub base_url: String,
}
pub fn discover_org(platform: &ServerUrl, repo_full_name: &str) -> Option<DiscoveredOrg> {
let url = format!(
"{}/.well-known/aretta-org?repo={}",
platform.as_str(),
super::oauth::url_encode(repo_full_name),
);
let config = ureq::Agent::config_builder()
.timeout_global(Some(Duration::from_secs(DISCOVERY_TIMEOUT_SECS)))
.user_agent(format!("aristo/{}", env!("CARGO_PKG_VERSION")))
.http_status_as_error(false)
.build();
let agent: ureq::Agent = config.into();
let response = agent.get(&url).call().ok()?;
let status = response.status().as_u16();
let body = read_body_capped(response, 16 * 1024);
map_discovery_response(status, &body)
}
pub(crate) fn map_discovery_response(status: u16, body: &str) -> Option<DiscoveredOrg> {
if !(200..=299).contains(&status) {
return None;
}
let org: DiscoveredOrg = serde_json::from_str(body).ok()?;
if org.base_url.trim().is_empty() {
return None;
}
Some(org)
}
fn read_body_capped(response: ureq::http::Response<ureq::Body>, cap: usize) -> String {
use std::io::Read;
let mut reader = response.into_body().into_reader();
let mut buf = Vec::with_capacity(4 * 1024);
let mut tmp = [0u8; 4 * 1024];
while buf.len() < cap {
match reader.read(&mut tmp) {
Ok(0) => break,
Ok(n) => {
let take = (cap - buf.len()).min(n);
buf.extend_from_slice(&tmp[..take]);
}
Err(_) => break,
}
}
String::from_utf8_lossy(&buf).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn map_200_with_valid_body_yields_discovered_org() {
let body = r#"{"org":"tursodatabase","base_url":"https://turso.aretta.ai"}"#;
let got = map_discovery_response(200, body).expect("should discover");
assert_eq!(got.org, "tursodatabase");
assert_eq!(got.base_url, "https://turso.aretta.ai");
}
#[test]
fn map_404_yields_none() {
assert!(map_discovery_response(404, r#"{"error":"not found"}"#).is_none());
}
#[test]
fn map_non_2xx_yields_none() {
assert!(map_discovery_response(500, "boom").is_none());
assert!(map_discovery_response(301, "").is_none());
}
#[test]
fn map_2xx_unparseable_body_yields_none() {
assert!(map_discovery_response(200, "not json").is_none());
}
#[test]
fn map_2xx_empty_base_url_yields_none() {
let body = r#"{"org":"x","base_url":" "}"#;
assert!(map_discovery_response(200, body).is_none());
}
#[test]
fn discover_org_against_closed_port_returns_none() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
drop(listener);
let platform = ServerUrl::Custom(format!("http://{addr}"));
assert!(discover_org(&platform, "owner/repo").is_none());
}
}