use std::path::PathBuf;
use crate::store::new_id;
pub struct Artifact {
pub dir: PathBuf,
pub bundle: Option<PathBuf>,
}
pub struct OpenRequest {
pub id: String,
pub origin: String,
pub created_at: i64,
pub brief_id: Option<String>,
pub artifact: Option<Artifact>,
}
#[derive(Default)]
pub struct Requests {
items: Vec<OpenRequest>,
}
impl Requests {
fn next_id(&self, now: i64) -> String {
let id = new_id(now);
if self.items.iter().any(|r| r.id == id) {
format!("{id}{}", self.items.len())
} else {
id
}
}
fn push(&mut self, origin: String, artifact: Option<Artifact>, now: i64) -> String {
let id = self.next_id(now);
self.items.push(OpenRequest {
id: id.clone(),
origin,
created_at: now,
brief_id: None,
artifact,
});
id
}
pub fn create(&mut self, url: &str, now: i64) -> String {
self.push(origin_of(url), None, now)
}
pub fn create_local(&mut self, origin: &str, artifact: Artifact, now: i64) -> String {
self.push(origin.to_string(), Some(artifact), now)
}
pub fn fulfill_by_id(&mut self, id: &str, brief_id: &str) -> bool {
match self
.items
.iter_mut()
.find(|r| r.id == id && r.brief_id.is_none())
{
Some(request) => {
request.brief_id = Some(brief_id.to_string());
true
}
None => false,
}
}
pub fn fulfill(&mut self, brief_url: &str, brief_id: &str) -> bool {
let origin = origin_of(brief_url);
let candidate = self
.items
.iter_mut()
.filter(|r| r.brief_id.is_none() && r.origin == origin)
.min_by_key(|r| r.created_at);
match candidate {
Some(request) => {
request.brief_id = Some(brief_id.to_string());
true
}
None => false,
}
}
pub fn get(&self, id: &str) -> Option<&OpenRequest> {
self.items.iter().find(|r| r.id == id)
}
}
pub fn origin_of(url: &str) -> String {
match url.split_once("://") {
Some((scheme, rest)) => {
let authority = rest.split('/').next().unwrap_or(rest);
format!("{scheme}://{authority}")
}
None => url.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn origins_drop_path_and_query() {
assert_eq!(origin_of("https://x.test/a/b?c=1"), "https://x.test");
assert_eq!(
origin_of("http://localhost:3000/checkout"),
"http://localhost:3000"
);
assert_eq!(origin_of("weird"), "weird");
}
#[test]
fn fulfills_oldest_same_origin_request() {
let mut requests = Requests::default();
let a = requests.create("https://x.test/one", 10);
let b = requests.create("https://x.test/two", 20);
let other = requests.create("https://y.test/z", 30);
assert!(requests.fulfill("https://x.test/three", "brief1"));
assert_eq!(
requests.get(&a).unwrap().brief_id.as_deref(),
Some("brief1")
);
assert_eq!(requests.get(&b).unwrap().brief_id, None);
assert_eq!(requests.get(&other).unwrap().brief_id, None);
assert!(requests.fulfill("https://x.test/four", "brief2"));
assert_eq!(
requests.get(&b).unwrap().brief_id.as_deref(),
Some("brief2")
);
assert!(!requests.fulfill("https://x.test/five", "brief3"));
}
#[test]
fn unknown_id_is_none() {
let requests = Requests::default();
assert!(requests.get("nope").is_none());
}
#[test]
fn fulfills_local_request_by_id_not_origin() {
let mut requests = Requests::default();
let origin = "http://127.0.0.1:8787";
let artifact = || Artifact {
dir: PathBuf::from("/tmp/a"),
bundle: None,
};
let a = requests.create_local(origin, artifact(), 10);
let b = requests.create_local(origin, artifact(), 20);
assert!(requests.fulfill_by_id(&b, "brief-b"));
assert_eq!(requests.get(&a).unwrap().brief_id, None);
assert_eq!(
requests.get(&b).unwrap().brief_id.as_deref(),
Some("brief-b")
);
assert!(!requests.fulfill_by_id("nope", "x"));
assert!(!requests.fulfill_by_id(&b, "again"));
}
}