use std::fmt;
use super::ModuleIdentity;
pub const MAX_SPECIFIER_CANDIDATES: usize = 16;
pub const MAX_SPECIFIER_BYTES: usize = 4_096;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SpecifierPolicyRequest {
importer: Option<ModuleIdentity>,
candidates: Vec<String>,
}
impl SpecifierPolicyRequest {
pub fn new(
importer: Option<ModuleIdentity>,
candidates: Vec<String>,
) -> std::result::Result<Self, SpecifierRefusal> {
if candidates.is_empty() {
return Err(SpecifierRefusal::new(
SpecifierRefusalCode::NoCandidates,
"module specifier policy requires at least one candidate",
));
}
if candidates.len() > MAX_SPECIFIER_CANDIDATES {
return Err(SpecifierRefusal::new(
SpecifierRefusalCode::TooManyCandidates,
format!(
"module specifier policy candidate count {} exceeds {}",
candidates.len(),
MAX_SPECIFIER_CANDIDATES
),
));
}
if let Some(candidate) = candidates
.iter()
.find(|candidate| candidate.len() > MAX_SPECIFIER_BYTES)
{
return Err(SpecifierRefusal::new(
SpecifierRefusalCode::CandidateTooLong,
format!(
"module specifier policy candidate length {} exceeds {} bytes",
candidate.len(),
MAX_SPECIFIER_BYTES
),
));
}
Ok(Self {
importer,
candidates,
})
}
pub fn importer(&self) -> Option<&ModuleIdentity> {
self.importer.as_ref()
}
pub fn candidates(&self) -> &[String] {
&self.candidates
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NormalizedSpecifier(String);
impl NormalizedSpecifier {
pub fn new(text: String) -> std::result::Result<Self, SpecifierRefusal> {
if text.len() > MAX_SPECIFIER_BYTES {
return Err(SpecifierRefusal::new(
SpecifierRefusalCode::CandidateTooLong,
format!(
"normalized module specifier length {} exceeds {} bytes",
text.len(),
MAX_SPECIFIER_BYTES
),
));
}
Ok(Self(text))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SpecifierRefusalCode {
NoCandidates,
TooManyCandidates,
CandidateTooLong,
IdentityRequiresOneCandidate,
PolicyRefused,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SpecifierRefusal {
code: SpecifierRefusalCode,
detail: String,
}
impl SpecifierRefusal {
pub fn new(code: SpecifierRefusalCode, detail: impl Into<String>) -> Self {
Self {
code,
detail: detail.into(),
}
}
pub fn code(&self) -> SpecifierRefusalCode {
self.code
}
pub fn detail(&self) -> &str {
&self.detail
}
}
impl fmt::Display for SpecifierRefusal {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.detail)
}
}
pub trait ModuleSpecifierPolicy: Send + Sync {
fn resolve(
&self,
request: &SpecifierPolicyRequest,
) -> std::result::Result<NormalizedSpecifier, SpecifierRefusal>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct IdentitySpecifierPolicy;
impl ModuleSpecifierPolicy for IdentitySpecifierPolicy {
fn resolve(
&self,
request: &SpecifierPolicyRequest,
) -> std::result::Result<NormalizedSpecifier, SpecifierRefusal> {
if request.candidates.len() != 1 {
return Err(SpecifierRefusal::new(
SpecifierRefusalCode::IdentityRequiresOneCandidate,
format!(
"identity module specifier policy requires exactly one candidate, got {}",
request.candidates.len()
),
));
}
NormalizedSpecifier::new(request.candidates[0].clone())
}
}