Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
14 kB · 428 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429//! typed request keys (packet §29).//!//! the generator walks the finite request space as `RequestKey` values; only the nginx//! emitter stringifies them. two derivations matter://!//! - `cas_key`: resolved-commit-keyed; identical semantics share CAS bytes regardless//! of which alias reached them.//! - `route_key_spelled`: alias-keyed; distinct wire spellings get their own nginx map//! entries pointing at the same CAS object.
use knot_types::{BranchName, TagName};
/// how a repo was named on the wire. canonical identity stays/// repoDid -> owner + rkey in meta.git; aliases are routing conveniences.#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]pub enum RepoAlias { Did(String), OwnerRkey { owner: String, rkey: String }, OwnerName { owner: String, name: String },}
impl RepoAlias { pub fn to_param(&self) -> String { match self { Self::Did(did) => did.clone(), Self::OwnerRkey { owner, rkey } => format!("{owner}/{rkey}"), Self::OwnerName { owner, name } => format!("{owner}/{name}"), } }}
/// a ref spelling accepted by the finite revspec profile (packet §9).#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]pub enum RefAlias { /// empty ref: knot2 defines it as HEAD. Head, ShortBranch(BranchName), FullBranch(String), ShortTag(TagName), FullTag(String), /// full object-format-width public commit OID. Commit(String),}
impl RefAlias { pub fn to_param(&self) -> String { match self { Self::Head => String::new(), Self::ShortBranch(name) => name.as_str().to_string(), Self::ShortTag(name) => name.as_str().to_string(), Self::FullBranch(name) | Self::FullTag(name) | Self::Commit(name) => name.clone(), } }}
/// whether the response bytes for a request key can change across generations./// commit-OID-keyed reads are immutable: the same oid always yields the same bytes,/// so they carry long cache lifetimes and feed the cross-generation response cache./// everything anchored to a named ref or the registry is mutable and recomputed every build.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum Mutability { Immutable, Mutable,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]pub enum ArchiveKind { TarGz, Zip,}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]pub enum RequestKey { DidDocument, Version, Owner, Health, DescribeRepo { repo: RepoAlias, }, GetDefaultBranch { repo: RepoAlias, }, Branch { repo: RepoAlias, name: BranchName, }, Branches { repo: RepoAlias, limit: usize, cursor: usize, }, Tag { repo: RepoAlias, name: TagName, }, Tags { repo: RepoAlias, limit: usize, cursor: usize, }, ListRefs { repo: RepoAlias, limit: usize, cursor: usize, }, ListRepos { limit: usize, cursor: usize, descending: bool, }, // echo-bearing endpoints carry the refspec SPELLING: knot2 wire bodies echo // the requested ref, so ref=main and ref=<oid> are distinct bytes. Tree { repo: RepoAlias, refspec: RefAlias, path: Option<String>, }, Blob { repo: RepoAlias, refspec: RefAlias, path: String, raw: bool, }, Languages { repo: RepoAlias, refspec: RefAlias, }, Diff { repo: RepoAlias, refspec: RefAlias, }, Compare { repo: RepoAlias, rev1: RefAlias, rev2: RefAlias, }, Log { repo: RepoAlias, refspec: RefAlias, limit: usize, cursor: usize, }, Archive { repo: RepoAlias, refspec: RefAlias, kind: ArchiveKind, }, Blame { repo: RepoAlias, refspec: RefAlias, path: String, }, /// a known NSID or shape the static knot intentionally does not serve. Unsupported { nsid: String, },}
impl RequestKey { /// cache lifetime class. drives both Cache-Control headers and whether the /// cross-generation response cache may satisfy this key. pub fn mutability(&self) -> Mutability { let is_immutable = match self { Self::Tree { refspec, .. } | Self::Blob { refspec, .. } | Self::Languages { refspec, .. } | Self::Diff { refspec, .. } | Self::Blame { refspec, .. } | Self::Log { refspec, .. } | Self::Archive { refspec, .. } => matches!(refspec, RefAlias::Commit(_)), Self::Compare { rev1, rev2, .. } => { matches!((rev1, rev2), (RefAlias::Commit(_), RefAlias::Commit(_))) } _ => false, }; if is_immutable { Mutability::Immutable } else { Mutability::Mutable } }
/// Cache-Control for this route, per the static knot's serving policy. pub fn cache_control(&self) -> &'static str { match self.mutability() { Mutability::Immutable => "public, max-age=31536000, immutable", Mutability::Mutable => match self { // identity/config documents change rarely; still short TTL. Self::DidDocument => "public, max-age=300", _ => "public, max-age=60, stale-while-revalidate=300", }, } }
/// alias-insensitive identity: what dedups into one CAS object. pub fn cas_key(&self) -> String { #![allow(clippy::match_same_arms)] match self { Self::DidDocument => "did.json".into(), Self::Version => "version".into(), Self::Owner => "owner".into(), Self::Health => "health".into(), Self::DescribeRepo { repo } => format!("describe-repo:{repo:?}"), Self::GetDefaultBranch { repo } => format!("default-branch:{repo:?}"), Self::Branch { repo, name } => format!("branch:{repo:?}:{name}"), Self::Branches { repo, limit, cursor, } => { format!("branches:{repo:?}:{limit}:{cursor}") } Self::Tag { repo, name } => format!("tag:{repo:?}:{name}"), Self::Tags { repo, limit, cursor, } => format!("tags:{repo:?}:{limit}:{cursor}"), Self::ListRefs { repo, limit, cursor, } => { format!("list-refs:{repo:?}:{limit}:{cursor}") } Self::ListRepos { limit, cursor, descending, } => { format!("list-repos:{limit}:{cursor}:{descending}") } Self::Tree { repo, refspec, path, } => { format!("tree:{repo:?}:{refspec:?}:{path:?}") } Self::Blob { repo, refspec, path, raw, } => { format!("blob:{repo:?}:{refspec:?}:{path}:{raw}") } Self::Languages { repo, refspec } => format!("languages:{repo:?}:{refspec:?}"), Self::Diff { repo, refspec } => format!("diff:{repo:?}:{refspec:?}"), Self::Compare { repo, rev1, rev2 } => format!("compare:{repo:?}:{rev1:?}:{rev2:?}"), Self::Log { repo, refspec, limit, cursor, } => { format!("log:{repo:?}:{refspec:?}:{limit}:{cursor}") } Self::Archive { repo, refspec, kind, } => { format!("archive:{repo:?}:{refspec:?}:{kind:?}") } Self::Blame { repo, refspec, path, } => { format!("blame:{repo:?}:{refspec:?}:{path}") } Self::Unsupported { nsid } => format!("unsupported:{nsid}"), } }
/// wire spelling: repo alias param + endpoint + query, as Tangled clients send them, /// with the limit pair optionally replaced by a raw wire spelling (e.g. "500", or "" for /// the default). used verbatim as the nginx map key fragment. pub fn route_key_spelled(&self, nsid: &str, limit_override: Option<&str>) -> String { let mut pairs = self.query_pairs(); if let (Some(raw), Some(slot)) = ( limit_override, pairs.iter_mut().find(|pair| pair.starts_with("limit=")), ) { *slot = format!("limit={raw}"); } format!("{nsid}?{}", pairs.join("&")) }
pub fn query_pairs(&self) -> Vec<String> { match self { Self::DidDocument | Self::Version | Self::Owner | Self::Health => vec![], // NOTE: describeRepo's wire param is repoDid (not repo) — this is // what Tangled's generated Go client and repoverify send. Self::DescribeRepo { repo } => vec![format!("repoDid={}", repo.to_param())], Self::GetDefaultBranch { repo } => vec![format!("repo={}", repo.to_param())], Self::Branch { repo, name } => { vec![format!("repo={}", repo.to_param()), format!("name={name}")] } Self::Branches { repo, limit, cursor, } => vec![ format!("repo={}", repo.to_param()), format!("limit={limit}"), format!("cursor={cursor}"), ], Self::Tag { repo, name } => { vec![format!("repo={}", repo.to_param()), format!("tag={name}")] } Self::Tags { repo, limit, cursor, } => vec![ format!("repo={}", repo.to_param()), format!("limit={limit}"), format!("cursor={cursor}"), ], Self::ListRefs { repo, limit, cursor, } => vec![ format!("repo={}", repo.to_param()), format!("limit={limit}"), format!("cursor={cursor}"), ], Self::ListRepos { limit, cursor, descending, } => vec![ format!("limit={limit}"), format!("cursor={cursor}"), format!("order={}", if *descending { "desc" } else { "asc" }), ], Self::Tree { repo, refspec, path, } => { let mut pairs = vec![ format!("repo={}", repo.to_param()), format!("ref={}", refspec.to_param()), ]; if let Some(path) = path { pairs.push(format!("path={path}")); } pairs } Self::Blob { repo, refspec, path, raw, } => { let mut pairs = vec![ format!("repo={}", repo.to_param()), format!("ref={}", refspec.to_param()), format!("path={path}"), ]; if *raw { pairs.push("raw=true".into()); } pairs } Self::Languages { repo, refspec } => { vec![ format!("repo={}", repo.to_param()), format!("ref={}", refspec.to_param()), ] } Self::Diff { repo, refspec } => { vec![ format!("repo={}", repo.to_param()), format!("ref={}", refspec.to_param()), ] } Self::Compare { repo, rev1, rev2 } => vec![ format!("repo={}", repo.to_param()), format!("rev1={}", rev1.to_param()), format!("rev2={}", rev2.to_param()), ], Self::Log { repo, refspec, limit, cursor, } => { vec![ format!("repo={}", repo.to_param()), format!("ref={}", refspec.to_param()), format!("limit={limit}"), format!("cursor={cursor}"), ] } Self::Archive { repo, refspec, kind, } => { let mut pairs = vec![ format!("repo={}", repo.to_param()), format!("ref={}", refspec.to_param()), ]; match kind { ArchiveKind::TarGz => pairs.push("format=tar.gz".into()), ArchiveKind::Zip => pairs.push("format=zip".into()), } pairs } Self::Blame { repo, refspec, path, } => vec![ format!("repo={}", repo.to_param()), format!("ref={}", refspec.to_param()), format!("path={path}"), ], Self::Unsupported { nsid } => vec![nsid.clone()], } }}