#![allow(clippy::doc_overindented_list_items)]
#[cfg(test)]
mod test;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use nonempty::NonEmpty;
use serde::{Deserialize, Serialize};
use serde_json as json;
use thiserror::Error;
use crate::git;
use crate::git::canonical;
use crate::git::canonical::Canonical;
use crate::git::fmt::Qualified;
use crate::git::fmt::refspec::QualifiedPattern;
use crate::identity::{Did, doc};
use super::protect;
use super::protect::Unprotected;
const ASTERISK: char = '*';
trait Sealed {}
impl Sealed for Allowed {}
impl Sealed for usize {}
pub(super) type Pattern = Unprotected<QualifiedPattern<'static>>;
pub type RawPattern = QualifiedPattern<'static>;
fn matches(pattern: &RawPattern, refname: &Qualified) -> bool {
match pattern.as_str().split_once(ASTERISK) {
None => pattern.as_str() == refname.as_str(),
Some((prefix, "")) => refname.as_str().starts_with(prefix),
Some((prefix, suffix)) => {
let mut spec = prefix.to_string();
spec.push_str("**");
spec.push_str(suffix);
fast_glob::glob_match(&spec, refname.as_str())
}
}
}
impl Ord for Pattern {
fn cmp(&self, other: &Self) -> Ordering {
#[derive(Debug, Clone, Copy)]
#[repr(i8)]
enum ComponentOrdering {
MatchLength(Ordering),
Lexicographic(Ordering),
}
impl ComponentOrdering {
fn merge(&mut self, other: Self) {
*self = match (*self, other) {
(Self::Lexicographic(Ordering::Equal), Self::Lexicographic(other)) => {
Self::Lexicographic(other)
}
(Self::Lexicographic(_), Self::MatchLength(other)) => Self::MatchLength(other),
(Self::MatchLength(Ordering::Equal), Self::MatchLength(other)) => {
Self::MatchLength(other)
}
(clone, _) => clone,
}
}
}
impl From<ComponentOrdering> for Ordering {
fn from(value: ComponentOrdering) -> Self {
match value {
ComponentOrdering::MatchLength(ordering) => ordering,
ComponentOrdering::Lexicographic(ordering) => ordering,
}
}
}
impl Default for ComponentOrdering {
fn default() -> Self {
Self::Lexicographic(Ordering::Equal)
}
}
use git::fmt::refspec::Component;
fn cmp_component(lhs: Component<'_>, rhs: Component<'_>) -> ComponentOrdering {
let (l, r) = (lhs.as_str(), rhs.as_str());
match (l.find(ASTERISK), r.find(ASTERISK)) {
(Some(_), None) => ComponentOrdering::MatchLength(Ordering::Greater),
(None, Some(_)) => ComponentOrdering::MatchLength(Ordering::Less),
(Some(li), Some(ri)) => {
if li != ri {
ComponentOrdering::MatchLength(li.cmp(&ri).reverse())
} else if l.len() != r.len() {
ComponentOrdering::MatchLength(l.len().cmp(&r.len()).reverse())
} else {
ComponentOrdering::Lexicographic(l.cmp(r))
}
}
(None, None) => ComponentOrdering::Lexicographic(l.cmp(r)),
}
}
let mut result = ComponentOrdering::default();
let mut lhs = self.as_ref().components();
let mut rhs = other.as_ref().components();
loop {
match (lhs.next(), rhs.next()) {
(None, Some(_)) => return Ordering::Greater, (Some(_), None) => return Ordering::Less, (Some(lhs), Some(rhs)) => {
result.merge(cmp_component(lhs, rhs));
}
(None, None) => return result.into(),
}
}
}
}
impl PartialOrd for Pattern {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub type RawRule = Rule<Allowed, usize>;
impl RawRule {
pub fn validate<R>(self, resolve: &mut R) -> Result<ValidRule, ValidationError>
where
R: Fn() -> doc::Delegates,
{
let Self {
allow: delegates,
threshold,
..
} = self;
let allow = match &delegates {
Allowed::Delegates => ResolvedDelegates::Delegates(resolve()),
Allowed::Set(delegates) => {
let valid =
doc::Delegates::new(delegates.clone()).map_err(ValidationError::from)?;
ResolvedDelegates::Set(valid)
}
};
let threshold = doc::Threshold::new(threshold, &allow)?;
Ok(Rule {
allow,
threshold,
extensions: self.extensions,
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RawRules {
#[serde(flatten)]
pub rules: BTreeMap<RawPattern, RawRule>,
}
impl RawRules {
pub fn iter(&self) -> impl Iterator<Item = (&RawPattern, &RawRule)> {
self.rules.iter()
}
pub fn insert(&mut self, pattern: RawPattern, rule: RawRule) -> Option<RawRule> {
self.rules.insert(pattern, rule)
}
pub fn remove(&mut self, pattern: &RawPattern) -> Option<RawRule> {
self.rules.remove(pattern)
}
pub fn exact_match(&self, refname: &Qualified) -> bool {
let refname = refname.as_str();
self.rules
.iter()
.any(|(pattern, _)| pattern.as_str() == refname)
}
pub fn matches<'a, 'b>(
&self,
refname: &Qualified<'b>,
) -> impl Iterator<Item = (&RawPattern, &RawRule)> + use<'a, '_, 'b> {
let refname = refname.clone();
self.rules
.iter()
.filter(move |(pattern, _)| matches(pattern, &refname))
}
}
impl Extend<(RawPattern, RawRule)> for RawRules {
fn extend<T: IntoIterator<Item = (RawPattern, RawRule)>>(&mut self, iter: T) {
self.rules.extend(iter)
}
}
impl From<BTreeMap<RawPattern, RawRule>> for RawRules {
fn from(rules: BTreeMap<RawPattern, RawRule>) -> Self {
RawRules { rules }
}
}
impl FromIterator<(RawPattern, RawRule)> for RawRules {
fn from_iter<T: IntoIterator<Item = (RawPattern, RawRule)>>(iter: T) -> Self {
iter.into_iter().collect::<BTreeMap<_, _>>().into()
}
}
impl IntoIterator for RawRules {
type Item = (RawPattern, RawRule);
type IntoIter = std::collections::btree_map::IntoIter<RawPattern, RawRule>;
fn into_iter(self) -> Self::IntoIter {
self.rules.into_iter()
}
}
pub type ValidRule = Rule<ResolvedDelegates, doc::Threshold>;
impl From<ValidRule> for RawRule {
fn from(rule: ValidRule) -> Self {
let Rule {
allow,
threshold,
extensions,
} = rule;
Self {
allow: allow.into(),
threshold: threshold.into(),
extensions,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Allowed {
#[serde(rename = "delegates")]
#[default]
Delegates,
#[serde(untagged)]
Set(NonEmpty<Did>),
}
impl From<NonEmpty<Did>> for Allowed {
fn from(dids: NonEmpty<Did>) -> Self {
Self::Set(dids)
}
}
impl From<Did> for Allowed {
fn from(did: Did) -> Self {
Self::Set(NonEmpty::new(did))
}
}
impl std::fmt::Display for Allowed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Allowed::Delegates => f.write_str("\"delegates\""),
Allowed::Set(dids) => {
let dids = dids
.iter()
.map(|did| did.to_string())
.collect::<Vec<_>>()
.join("\", \"");
f.write_fmt(format_args!("[\"{dids}\"]"))
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(into = "Allowed")]
pub enum ResolvedDelegates {
Delegates(doc::Delegates),
Set(doc::Delegates),
}
impl From<ResolvedDelegates> for Allowed {
fn from(ds: ResolvedDelegates) -> Self {
match ds {
ResolvedDelegates::Delegates(_) => Self::Delegates,
ResolvedDelegates::Set(ds) => Self::Set(ds.into()),
}
}
}
impl std::ops::Deref for ResolvedDelegates {
type Target = doc::Delegates;
fn deref(&self) -> &Self::Target {
match self {
ResolvedDelegates::Delegates(ds) => ds,
ResolvedDelegates::Set(ds) => ds,
}
}
}
#[derive(Debug)]
pub struct MatchedRule<'a> {
refname: Qualified<'a>,
rule: ValidRule,
}
impl MatchedRule<'_> {
pub fn refname(&self) -> &Qualified<'_> {
&self.refname
}
pub fn rule(&self) -> &ValidRule {
&self.rule
}
pub fn allowed(&self) -> &doc::Delegates {
self.rule().allowed()
}
pub fn threshold(&self) -> &doc::Threshold {
self.rule().threshold()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub struct Rules {
#[serde(flatten)]
rules: BTreeMap<Pattern, ValidRule>,
}
impl FromIterator<(Pattern, ValidRule)> for Rules {
fn from_iter<T: IntoIterator<Item = (Pattern, ValidRule)>>(iter: T) -> Self {
Self {
rules: iter.into_iter().collect(),
}
}
}
impl From<Rules> for RawRules {
fn from(Rules { rules }: Rules) -> Self {
Self {
rules: rules
.into_iter()
.map(|(pattern, rule)| (pattern.into_inner(), rule.into()))
.collect(),
}
}
}
impl Rules {
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn from_raw<R>(
rules: impl IntoIterator<Item = (RawPattern, RawRule)>,
resolve: &mut R,
) -> Result<Self, ValidationError>
where
R: Fn() -> doc::Delegates,
{
let valid = rules
.into_iter()
.map(|(pattern, rule)| {
let pattern = Unprotected::new(pattern)?;
rule.validate(resolve).map(|rule| (pattern, rule))
})
.collect::<Result<_, _>>()?;
Ok(Self { rules: valid })
}
pub fn matches<'a>(
&self,
refname: &Qualified<'a>,
) -> impl Iterator<Item = (&RawPattern, &ValidRule)> + use<'a, '_> {
let refname_cloned = refname.clone();
self.rules
.iter()
.filter(move |(pattern, _)| matches(pattern.as_ref(), &refname_cloned))
.map(|(pattern, rule)| (pattern.as_ref(), rule))
}
pub fn canonical<'a, 'b, 'r, R>(
&'a self,
refname: Qualified<'b>,
repo: &'r R,
) -> Option<Canonical<'b, 'a, 'r, R, canonical::Initial>>
where
R: canonical::effects::Ancestry
+ canonical::effects::FindMergeBase
+ canonical::effects::FindObjects,
{
self.matches(&refname)
.next()
.map(|(_, rule)| Canonical::new(refname, rule, repo))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound(deserialize = "D: Sealed + Deserialize<'de>, T: Sealed + Deserialize<'de>"))]
pub struct Rule<D, T> {
allow: D,
threshold: T,
#[serde(skip_serializing_if = "json::Map::is_empty")]
#[serde(flatten)]
extensions: json::Map<String, json::Value>,
}
impl<D, T> Rule<D, T> {
pub fn new(allow: D, threshold: T) -> Self {
Self {
allow,
threshold,
extensions: json::Map::new(),
}
}
pub fn allowed(&self) -> &D {
&self.allow
}
pub fn threshold(&self) -> &T {
&self.threshold
}
pub fn extensions(&self) -> &json::Map<String, json::Value> {
&self.extensions
}
pub fn add_extensions(&mut self, extensions: impl Into<json::Map<String, json::Value>>) {
self.extensions.extend(extensions.into());
}
}
#[derive(Debug, Error)]
pub enum ValidationError {
#[error(transparent)]
Threshold(#[from] doc::ThresholdError),
#[error(transparent)]
Delegates(#[from] doc::DelegatesError),
#[error(transparent)]
Protected(#[from] protect::Error),
}
#[derive(Debug, Error)]
pub enum CanonicalError {
#[error(transparent)]
Git(#[from] crate::git::raw::Error),
}