use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::atomic::AtomicBool;
use std::sync::{Mutex, MutexGuard, PoisonError};
use crate::server::Drip;
use crate::{MockNexus, Scenario};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
Status(u16),
Reset,
PartialRead { bytes: usize },
Stalled,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReqLog {
pub method: String,
pub path: String,
pub outcome: Outcome,
}
#[derive(Debug)]
pub(crate) struct Shared {
pub(crate) scenario: Scenario,
pub(crate) store: Mutex<HashMap<String, Vec<u8>>>,
pub(crate) log: Mutex<Vec<ReqLog>>,
pub(crate) first_request: Mutex<HashMap<String, u32>>,
pub(crate) first_put: Mutex<HashMap<String, u32>>,
pub(crate) first_get: Mutex<HashMap<String, u32>>,
pub(crate) drift: AtomicBool,
pub(crate) auth_b64: Option<String>,
pub(crate) drip: Option<Drip>,
pub(crate) sizeless: bool,
pub(crate) partial_first_put: Option<usize>,
pub(crate) cut_first_get: Option<(usize, bool)>,
pub(crate) rate_first: Option<(usize, u64)>,
pub(crate) flaky_first: Option<u32>,
pub(crate) members: Vec<SocketAddr>,
}
impl Shared {
pub(crate) fn hosted(scenario: Scenario, base_url: String) -> Self {
let auth_b64 = match &scenario {
Scenario::Auth401 { user, pass } | Scenario::Auth403 { user, pass } => {
Some(crate::base64::encode(format!("{user}:{pass}").as_bytes()))
}
_ => None,
};
let drip = match &scenario {
Scenario::Slow {
chunk_delay_ms,
chunk_size,
} => Some(Drip {
delay_ms: *chunk_delay_ms,
chunk_size: *chunk_size,
}),
_ => None,
};
let sizeless = matches!(&scenario, Scenario::Sizeless);
let (partial_first_put, cut_first_get, rate_first, flaky_first) = match &scenario {
Scenario::PartialPut {
first_attempt_bytes,
} => (Some(*first_attempt_bytes), None, None, None),
Scenario::CutBody {
after_bytes,
fake_length,
} => (None, Some((*after_bytes, *fake_length)), None, None),
Scenario::RateLimit {
first_429s,
retry_after_secs,
} => (None, None, Some((*first_429s, *retry_after_secs)), None),
Scenario::Flaky { first_failures } => (None, None, None, Some(*first_failures)),
_ => (None, None, None, None),
};
Self {
store: Mutex::new(initial_store(&scenario, &base_url)),
scenario,
log: Mutex::new(Vec::new()),
first_request: Mutex::new(HashMap::new()),
first_put: Mutex::new(HashMap::new()),
first_get: Mutex::new(HashMap::new()),
drift: AtomicBool::new(false),
auth_b64,
drip,
sizeless,
partial_first_put,
cut_first_get,
rate_first,
flaky_first,
members: Vec::new(),
}
}
pub(crate) fn group(members: &[&MockNexus]) -> Self {
Self {
members: members.iter().map(|m| m.addr()).collect(),
..Self::hosted(Scenario::Atomic, String::new())
}
}
}
pub(crate) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex.lock().unwrap_or_else(PoisonError::into_inner)
}
fn initial_store(scenario: &Scenario, base_url: &str) -> HashMap<String, Vec<u8>> {
let mut store = HashMap::new();
if matches!(scenario, Scenario::NoService) {
return store;
}
let doc = format!(
concat!(
"[",
r#"{{"name":"raw-main","format":"raw","type":"hosted","url":"{base}repository/raw-main/","attributes":{{}}}}"#,
",",
r#"{{"name":"raw-all","format":"raw","type":"group","url":"{base}repository/raw-all/","attributes":{{}}}}"#,
"]"
),
base = base_url
);
store.insert("service/rest/v1/repositories".to_owned(), doc.into_bytes());
store
}