use super::support::{lock, percent_decode, plain, query_param};
use crate::scenario::Scenario;
use crate::server::{Request, Resp};
use crate::store::Shared;
pub(super) const EULA_DISCLAIMER: &str = "You are accepting an agreement. By using this Nexus repository manager you are agreeing to the Sonatype End User License Agreement (EULA) as outlined at https://links.sonatype.com/products/nxrm3/docs/ce-onboarding";
const ASSETS_TOTAL: usize = 35;
const ASSETS_PAGE: usize = 10;
const SEARCH_ROWS_PER_PAGE: usize = 50;
const PREFIX_Q_DIR: usize = 300;
const PREFIX_Q_SIBLING: usize = 4;
const PREFIX_Q_DECOY: usize = 200;
const PREFIX_Q_BULK: usize = 5000;
fn corpus(scenario: &Scenario) -> Vec<String> {
let mut paths = Vec::new();
for i in 0..ASSETS_TOTAL {
paths.push(format!("/v0.9.0-assets/asset-{i:03}.bin"));
}
if *scenario == Scenario::AssetsPrefixQ {
for i in 0..PREFIX_Q_DIR {
paths.push(format!("/v1.1.0-dir/dir-{i:04}.bin"));
}
for i in 0..PREFIX_Q_SIBLING {
paths.push(format!("/v1.1.0-dir2/dir2-{i:02}.bin"));
}
for i in 0..PREFIX_Q_BULK {
paths.push(format!("/v1.0.0-bulk/bulk-{i:04}.bin"));
}
for i in 0..PREFIX_Q_DECOY {
paths.push(format!("/backup/v1.1.0-dir/decoy-{i:04}.bin"));
}
}
paths
}
pub(super) fn q_matches(q: &str, path: &str) -> bool {
path.contains(q)
}
fn search_item(base: &str, path: &str, seed: usize) -> String {
let sha = format!("{seed:064x}");
format!(
"{{\"downloadUrl\":\"{base}/repository/raw-main{path}\",\"path\":\"{path}\",\"id\":\"raw-main:{seed:06}\",\"repository\":\"raw-main\",\"format\":\"raw\",\"checksum\":{{\"sha256\":\"{sha}\"}},\"lastModified\":\"2026-10-09T12:00:00.000+00:00\"}}"
)
}
pub(super) fn assets_page(shared: &Shared, req: &Request) -> Resp {
let q = query_param(&req.target, "q").map(percent_decode);
let token = query_param(&req.target, "continuationToken");
let start = token.and_then(|t| t.parse::<usize>().ok()).unwrap_or(0);
let page_size = if shared.scenario == Scenario::AssetsPrefixQ {
SEARCH_ROWS_PER_PAGE
} else {
ASSETS_PAGE
};
let all = corpus(&shared.scenario);
let matching: Vec<usize> = all
.iter()
.enumerate()
.filter(|(_, path)| q.as_deref().is_none_or(|q| q_matches(q, path)))
.map(|(i, _)| i)
.collect();
let total = matching.len();
let base = base_of(shared);
let page: Vec<String> = matching
.into_iter()
.skip(start)
.take(page_size)
.map(|i| search_item(&base, &all[i], i))
.collect();
let body = if start + page.len() < total {
format!(
"{{\"items\": [{}], \"continuationToken\": \"{}\"}}",
page.join(", "),
start + page_size
)
} else {
format!("{{\"items\": [{}]}}", page.join(", "))
};
plain(200, body.as_bytes(), None)
}
pub(super) fn audit_litter_page(shared: &Shared) -> Resp {
const CLEAN_DIGEST: &str = "a22ae4f83248fb43415404da410560378910398baf65f234d8de8cde3538351d";
let base = base_of(shared);
let item = |path: &str, digest: &str| {
format!(
"{{\"downloadUrl\":\"{base}/repository/raw-main/1.0.0/{path}\",\"path\":\"/1.0.0/{path}\",\"id\":\"raw-main:{path}\",\"repository\":\"raw-main\",\"format\":\"raw\",\"checksum\":{{\"sha256\":\"{digest}\"}},\"lastModified\":\"2026-10-13T12:00:00.000+00:00\"}}"
)
};
let items = [
item(
"broken/app.bin",
"cc03e1c1d5f1d2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7",
),
item(
"broken/app.bin.sha256",
"bb02e1c1d5f1d2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7",
),
item("clean/app.bin", CLEAN_DIGEST),
item("clean/app.bin.sha256", CLEAN_DIGEST),
item(
"dangling/app.bin.sha256",
"aa02e1c1d5f1d2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7",
),
item(
"orphan/app.bin",
"cc02e1c1d5f1d2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7",
),
];
let body = format!("{{\"items\": [{}]}}", items.join(", "));
plain(200, body.as_bytes(), None)
}
pub(super) fn base_of(shared: &Shared) -> String {
lock(&shared.store)
.get("service/rest/v1/repositories")
.and_then(|doc| {
String::from_utf8_lossy(doc)
.split("\"url\":\"")
.nth(1)
.map(str::to_owned)
})
.and_then(|rest| rest.split("/repository/").next().map(str::to_owned))
.unwrap_or_default()
}
pub(super) fn seed_repositories_doc(shared: &Shared) -> String {
lock(&shared.store)
.get("service/rest/v1/repositories")
.map(|doc| String::from_utf8_lossy(doc).into_owned())
.unwrap_or_else(|| "[]".to_owned())
}