use std::iter;
use derive_deftly::define_derive_deftly;
use itertools::{EitherOrBoth, Itertools, izip};
use super::*;
use crate::DENOTATOR_SEP;
pub use crate::KeyPathInfoBuilder;
pub use tor_error::{Bug, internal, into_internal};
pub trait RawKeySpecifierComponent {
fn append_to(&self, s: &mut String) -> Result<(), Bug>;
}
impl<T: KeySpecifierComponent> RawKeySpecifierComponent for T {
fn append_to(&self, s: &mut String) -> Result<(), Bug> {
self.to_slug()?.as_str().append_to(s)
}
}
impl<T: KeySpecifierComponent> RawKeySpecifierComponent for Option<T> {
fn append_to(&self, s: &mut String) -> Result<(), Bug> {
let v: &dyn RawKeySpecifierComponent = match self.as_ref() {
Some(v) => v,
None => &"*",
};
v.append_to(s)
}
}
impl<'s> RawKeySpecifierComponent for &'s str {
fn append_to(&self, s: &mut String) -> Result<(), Bug> {
s.push_str(self);
Ok(())
}
}
fn arti_path_string_from_components(
path_comps: &[&dyn RawKeySpecifierComponent],
leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<String, Bug> {
let mut path = String::new();
for comp in path_comps {
comp.append_to(&mut path)?;
path.push('/');
}
for (delim, comp) in izip!(
iter::once(None).chain(iter::repeat(Some(DENOTATOR_SEP))),
leaf_comps,
) {
if let Some(delim) = delim {
path.push(delim);
}
comp.append_to(&mut path)?;
}
Ok(path)
}
fn cert_arti_path_string_from_components(
subj_comp: &str,
leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<String, Bug> {
if leaf_comps.is_empty() {
return Ok(subj_comp.to_string());
}
let mut path = if subj_comp.contains('+') {
format!("{subj_comp}@")
} else {
format!("{subj_comp}+@")
};
for (delim, comp) in izip!(
iter::once(None).chain(iter::repeat(Some(DENOTATOR_SEP))),
leaf_comps,
) {
if let Some(delim) = delim {
path.push(delim);
}
comp.append_to(&mut path)?;
}
Ok(path)
}
pub fn arti_path_from_components(
path_comps: &[&dyn RawKeySpecifierComponent],
leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<ArtiPath, ArtiPathUnavailableError> {
Ok(arti_path_string_from_components(path_comps, leaf_comps)?
.try_into()
.map_err(into_internal!("bad ArtiPath from good components"))?)
}
pub fn arti_pattern_from_components(
path_comps: &[&dyn RawKeySpecifierComponent],
leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<KeyPathPattern, Bug> {
Ok(KeyPathPattern::Arti(arti_path_string_from_components(
path_comps, leaf_comps,
)?))
}
pub fn cert_arti_pattern_from_components(
subj_path: &str,
leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<KeyPathPattern, Bug> {
Ok(KeyPathPattern::Arti(cert_arti_path_string_from_components(
subj_path, leaf_comps,
)?))
}
#[derive(Debug)]
#[allow(clippy::exhaustive_enums)] pub enum RawComponentParseResult {
ParsedField,
MatchedLiteral,
PatternNotMatched,
Invalid(InvalidKeyPathComponentValue),
}
use RawComponentParseResult as RCPR;
pub trait RawKeySpecifierComponentParser {
fn parse(&mut self, comp: &Slug) -> RawComponentParseResult;
}
impl<T: KeySpecifierComponent> RawKeySpecifierComponentParser for Option<T> {
fn parse(&mut self, comp: &Slug) -> RawComponentParseResult {
let v = match T::from_slug(comp) {
Ok(v) => v,
Err(e) => return RCPR::Invalid(e),
};
*self = Some(v);
RCPR::ParsedField
}
}
impl<'s> RawKeySpecifierComponentParser for &'s str {
fn parse(&mut self, comp: &Slug) -> RawComponentParseResult {
if comp.as_str() == *self {
RCPR::MatchedLiteral
} else {
RCPR::PatternNotMatched
}
}
}
type Parsers<'p> = [&'p mut dyn RawKeySpecifierComponentParser];
fn extract(
input: Option<&str>,
delim: char,
parsers: &mut Parsers,
keys: &mut &[&str],
) -> Result<(), ArtiPathError> {
for ent in Itertools::zip_longest(
input.map(|input| input.split(delim)).into_iter().flatten(),
parsers,
) {
let EitherOrBoth::Both(comp, parser) = ent else {
return Err(ArtiPathError::PatternNotMatched);
};
let comp = Slug::new(comp.to_owned())
.map_err(ArtiPathSyntaxError::Slug)
.map_err(ArtiPathError::InvalidArtiPath)?;
let missing_keys = || internal!("keys list too short, bad args to parse_arti_path");
match parser.parse(&comp) {
RCPR::PatternNotMatched => Err(ArtiPathError::PatternNotMatched),
RCPR::Invalid(error) => Err(ArtiPathError::InvalidKeyPathComponentValue {
error,
key: keys.first().ok_or_else(missing_keys)?.to_string(),
value: comp,
}),
RCPR::ParsedField => {
*keys = keys.split_first().ok_or_else(missing_keys)?.1;
Ok(())
}
RCPR::MatchedLiteral => Ok(()),
}?;
}
Ok(())
}
pub fn parse_arti_path(
arti_path: &ArtiPath,
keys: &&[&str],
path_parsers: &mut Parsers,
leaf_parsers: &mut Parsers,
) -> Result<(), ArtiPathError> {
let path = arti_path.as_str();
let (path, leaf) = match path.rsplit_once('/') {
Some((path, leaf)) => (Some(path), leaf),
None => (None, path),
};
let mut keys: &[&str] = keys;
extract(path, '/', path_parsers, &mut keys)?;
extract(Some(leaf), DENOTATOR_SEP, leaf_parsers, &mut keys)?;
Ok(())
}
pub fn parse_cert_denotators(
cert_denos: &str,
keys: &&[&str],
leaf_parsers: &mut Parsers,
) -> Result<(), ArtiPathError> {
let mut keys: &[&str] = keys;
extract(Some(cert_denos), DENOTATOR_SEP, leaf_parsers, &mut keys)?;
Ok(())
}
pub fn describe_via_components(
summary: &&str,
role: &dyn RawKeySpecifierComponent,
extra_keys: &&[&str],
extra_info: &[&dyn KeySpecifierComponent],
) -> Result<KeyPathInfo, Bug> {
let mut info = KeyPathInfoBuilder::default();
info.summary(summary.to_string());
info.role({
let mut s = String::new();
role.append_to(&mut s)?;
s
});
for (key, value) in izip!(*extra_keys, extra_info) {
let value = KeySpecifierComponentPrettyHelper(*value).to_string();
info.extra_info(*key, value);
}
info.build()
.map_err(into_internal!("failed to build KeyPathInfo"))
}
define_derive_deftly! {
export KeySpecifier for struct, beta_deftly, meta_quoted rigorous:
${defcond F_IS_PATH not(any(fmeta(denotator), fmeta(role)))}
${defcond F_IS_ROLE all(fmeta(role), not(tmeta(role)))}
#[doc = concat!("Pattern matching some or all [`", stringify!($tname), "`]")]
#[allow(dead_code)] #[non_exhaustive]
$tvis struct $<$tname Pattern><$tdefgens>
where $twheres
${vdefbody $vname $(
${fattrs doc}
$fvis $fname: Option<$ftype>,
) }
${define ARTI_PATH_COMPONENTS {
${define LIT ${tmeta(prefix) as str}}
$DO_LITERAL
${for fields {
${if fmeta(fixed_path_component) {
${define LIT ${fmeta(fixed_path_component) as str}}
$DO_LITERAL
}}
${if F_IS_PATH { $DO_FIELD }}
}}
}}
${define ARTI_LEAF_COMPONENTS {
${if tmeta(role) {
${define LIT { stringify!(${snake_case ${tmeta(role)}}) }}
$DO_ROLE_LITERAL
}}
${for fields {
${if F_IS_ROLE { $DO_ROLE_FIELD }}
}}
${for fields {
${if fmeta(denotator) { $DO_FIELD }}
}}
}}
${define DO_FIELD { &self.$fname, }}
${define DO_LITERAL { &$LIT, }}
${define DO_ROLE_FIELD { $DO_FIELD }}
${define DO_ROLE_LITERAL { $DO_LITERAL }}
impl<$tgens> $crate::KeySpecifier for $ttype
where $twheres
{
fn arti_path(
&self,
) -> std::result::Result<$crate::ArtiPath, $crate::ArtiPathUnavailableError> {
use $crate::key_specifier_derive::*;
arti_path_from_components(
&[ $ARTI_PATH_COMPONENTS ],
&[ $ARTI_LEAF_COMPONENTS ],
)
}
fn ctor_path(&self) -> Option<$crate::CTorPath> {
<Self as $crate::CTorKeySpecifier>::ctor_path(self)
}
fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
${if tmeta(keypair_specifier) {
Some(Box::new(std::convert::Into::<
${tmeta(keypair_specifier) as ty}
>::into(self)))
} else {
None
}}
}
}
impl<$tgens> $crate::KeySpecifierPattern for $<$tname Pattern><$tdefgens>
where $twheres
{
fn arti_pattern(
&self,
) -> std::result::Result<$crate::KeyPathPattern, $crate::key_specifier_derive::Bug> {
use $crate::key_specifier_derive::*;
arti_pattern_from_components(
&[ $ARTI_PATH_COMPONENTS ],
&[ $ARTI_LEAF_COMPONENTS ],
)
}
fn new_any() -> Self {
$< $tname Pattern > {
$( $fname: None, )
}
}
}
struct $< $tname InfoExtractor >;
impl<$tgens> $crate::KeyPathInfoExtractor for $< $tname InfoExtractor >
where $twheres
{
fn describe(
&self,
path: &$crate::KeyPath,
) -> std::result::Result<$crate::KeyPathInfo, $crate::KeyPathError> {
use $crate::key_specifier_derive::*;
#[allow(unused_variables)] let spec = $ttype::try_from(path)?;
${define DO_LITERAL {}}
static NON_ROLE_FIELD_KEYS: &[&str] = &[
${define DO_FIELD { stringify!($fname), }}
${define DO_ROLE_FIELD {}}
${define DO_ROLE_LITERAL {}}
$ARTI_PATH_COMPONENTS
$ARTI_LEAF_COMPONENTS
];
describe_via_components(
&${tmeta(summary) as str},
${define DO_FIELD {}}
${define DO_ROLE_FIELD { &spec.$fname, }}
${define DO_ROLE_LITERAL { &$LIT, }}
$ARTI_LEAF_COMPONENTS
&NON_ROLE_FIELD_KEYS,
&[
${define DO_FIELD { &spec.$fname, }}
${define DO_ROLE_FIELD {}}
${define DO_ROLE_LITERAL {}}
$ARTI_PATH_COMPONENTS
$ARTI_LEAF_COMPONENTS
],
).map_err($crate::KeyPathError::Bug)
}
}
impl<$tgens> TryFrom<&$crate::KeyPath> for $tname
where $twheres
{
type Error = $crate::KeyPathError;
fn try_from(path: &$crate::KeyPath) -> std::result::Result<$tname, Self::Error> {
use $crate::key_specifier_derive::*;
static FIELD_KEYS: &[&str] = &[
${define DO_LITERAL {}}
${define DO_FIELD { stringify!($fname), }}
$ARTI_PATH_COMPONENTS
$ARTI_LEAF_COMPONENTS
];
#[allow(unused_mut)] #[allow(unused_variables)] let mut builder =
<$<$tname Pattern>::<$tgens> as $crate::KeySpecifierPattern>::new_any();
${define DO_FIELD { &mut builder.$fname, }}
${define DO_LITERAL { &mut $LIT, }}
#[allow(unused_variables)] match path {
$crate::KeyPath::Arti(path) => {
parse_arti_path(
path,
&FIELD_KEYS,
&mut [ $ARTI_PATH_COMPONENTS ],
&mut [ $ARTI_LEAF_COMPONENTS ],
).map_err(|err| $crate::KeyPathError::Arti { path: path.clone(), err })?;
},
$crate::KeyPath::CTor(path) => {
return <Self as $crate::CTorKeySpecifier>::from_ctor_path(path.clone())
.map_err(|err| $crate::KeyPathError::CTor { path: path.clone(), err });
},
#[allow(unreachable_patterns)] &_ => {
return Err(internal!("unrecognized key path?!").into());
}
};
#[allow(unused_variables)] let handle_none = || internal!("bad RawKeySpecifierComponentParser impl");
Ok($tname { $(
$fname: builder.$fname.ok_or_else(handle_none)?,
) })
}
}
${if tmeta(ctor_path) {
${define CTOR_PATH_VARIANT ${tmeta(ctor_path) as path}}
impl<$tgens> $crate::CTorKeySpecifier for $ttype
where $twheres
{
fn ctor_path(&self) -> Option<$crate::CTorPath> {
Some($crate::CTorPath :: $CTOR_PATH_VARIANT {
$( $fname: self.$fname.clone(), )
})
}
fn from_ctor_path(
path: $crate::CTorPath
) -> std::result::Result<Self, $crate::CTorPathError> {
match path {
$crate::CTorPath :: $CTOR_PATH_VARIANT { $( $fname, )} => {
Ok( Self { $( $fname, ) })
},
_ => Err($crate::CTorPathError::KeySpecifierMismatch(stringify!($tname).into())),
}
}
}
} else {
impl<$tgens> $crate::CTorKeySpecifier for $ttype
where $twheres
{
fn ctor_path(&self) -> Option<$crate::CTorPath> {
None
}
fn from_ctor_path(
_: $crate::CTorPath
) -> std::result::Result<Self, $crate::CTorPathError> {
Err($crate::CTorPathError::MissingCTorPath(stringify!($tname).to_string()))
}
}
}}
$crate::inventory::submit!(&$< $tname InfoExtractor > as &dyn $crate::KeyPathInfoExtractor);
}
#[cfg(feature = "experimental-api")]
define_derive_deftly! {
export CertSpecifier beta_deftly, for struct:
${if not(approx_equal(${for fields { ${when fmeta(subject)} 1 }}, 1))
{ ${error "Exactly one field must be #[deftly(subject)]"} }
}
$(
${when not(any(
fmeta(subject),
fmeta(denotator),
))}
${error
message=${concat $fname " must be #[deftly(subject)] or #[deftly(denotator)]"}
}
)
${define SUBJ_FNAME
${for fields {
${if fmeta(subject) {
&self.$fname
}}
}}
}
${define SUBJ_FTYPE
${for fields {
${if fmeta(subject) {
$ftype
}}
}}
}
${define SUBJ_PATTERN_FTYPE
${for fields {
${if fmeta(subject) {
$<$ftype Pattern>
}}
}}
}
impl<$tgens> $crate::KeyCertificateSpecifier for $tname<$tdefgens>
where $twheres
{
fn cert_denotators(&self) -> Vec<&dyn $crate::KeySpecifierComponent> {
vec![
${for fields {
${if fmeta(denotator) { &self.$fname, }}
}}
]
}
fn subject_key_specifier(&self) -> &dyn $crate::KeySpecifier {
${SUBJ_FNAME}
}
}
#[doc = concat!("Pattern matching some or all [`", stringify!($tname), "`]")]
#[allow(dead_code)] #[non_exhaustive]
$tvis struct $<$tname Pattern><$tdefgens>
where $twheres
{
${for fields {
${if fmeta(subject) {
${fattrs doc}
$fvis $fname: $<$ftype Pattern>,
}}
${if fmeta(denotator) {
${fattrs doc}
$fvis $fname: Option<$ftype>,
}}
}}
}
${define DO_FIELD { &self.$fname, }}
${define ARTI_LEAF_COMPONENTS {
${for fields {
${if fmeta(denotator) { $DO_FIELD }}
}}
}}
impl<$tgens> $crate::CertSpecifierPattern for $<$tname Pattern><$tdefgens>
where $twheres
{
type SubjectKeySpecifierPattern = ${SUBJ_PATTERN_FTYPE};
fn arti_pattern(
&self,
) -> std::result::Result<$crate::KeyPathPattern, $crate::key_specifier_derive::Bug> {
use $crate::key_specifier_derive::*;
use $crate::KeyPathPattern::*;
use $crate::KeySpecifierPattern as _;
let subj_path_pat = ${SUBJ_FNAME}.arti_pattern()?;
let subj_path = match subj_path_pat {
Arti(path) => path,
_ => {
return Err(
tor_error::internal!("subject key pattern is not an Arti pattern?!").into()
);
}
};
cert_arti_pattern_from_components(
&subj_path,
&[ $ARTI_LEAF_COMPONENTS ],
)
}
fn new_any() -> Self {
let spec =
< <$<$tname Pattern>::<$tgens> as $crate::CertSpecifierPattern>::SubjectKeySpecifierPattern
as $crate::KeySpecifierPattern>::new_any();
$< $tname Pattern > {
$(
${if fmeta(subject) { $fname: spec, }}
)
$(
${if fmeta(denotator) { $fname: None, }}
)
}
}
}
impl<$tgens> TryFrom<&$crate::KeyPath> for $tname
where $twheres
{
type Error = $crate::KeyPathError;
fn try_from(path: &$crate::KeyPath) -> std::result::Result<$tname, Self::Error> {
use $crate::key_specifier_derive::*;
let arti_path = match path {
$crate::KeyPath::Arti(path) => path,
&_ => {
return Err(tor_error::bad_api_usage!("Cert specifiers never have non-ArtiPaths").into());
}
};
#[allow(unused_mut)] #[allow(unused_variables)] let mut builder =
<$<$tname Pattern>::<$tgens> as $crate::CertSpecifierPattern>::new_any();
let subj_key = if let Some((key_path, cert_denos)) = arti_path.split_once($crate::DENOTATOR_GROUP_SEP) {
let key_path = match key_path.strip_suffix('+') {
Some(p) => p,
None => key_path,
};
let key_arti_path = $crate::ArtiPath::new(key_path.to_string())
.map_err(tor_error::into_internal!("cert path contains invalid key ArtiPath?!"))?;
static FIELD_KEYS: &[&str] = &[
${for fields {
${if fmeta(denotator) { stringify!($fname), }}
}}
];
parse_cert_denotators(
cert_denos,
&FIELD_KEYS,
${define DO_FIELD { &mut builder.$fname, }}
&mut [ $ARTI_LEAF_COMPONENTS ],
).map_err(|err| $crate::KeyPathError::Arti { path: arti_path.clone(), err })?;
let key_spec = $crate::KeyPath::Arti(key_arti_path);
$SUBJ_FTYPE::try_from(&key_spec)?
} else {
$SUBJ_FTYPE::try_from(path)?
};
#[allow(unused_variables)] let handle_none = || internal!("bad RawKeySpecifierComponentParser impl");
Ok($tname {
${for fields {
${if fmeta(denotator) {
$fname: builder.$fname.ok_or_else(handle_none)?,
}}
${if fmeta(subject) {
$fname: subj_key,
}}
}}
})
}
}
}
pub use derive_deftly_template_KeySpecifier;
#[cfg(feature = "experimental-api")]
pub use derive_deftly_template_CertSpecifier;