omnitrack 0.3.0

Universal issue-tracker provider contracts and clients (Linear, Jira, ...) for Rust, in one crate.
Documentation
use std::collections::BTreeSet;

use super::IssueResult;
use super::capabilities::Capability;
use super::errors::{ErrorKind, error};

#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ProviderId(String);

impl ProviderId {
    pub fn make(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Clone, Debug)]
pub struct ProviderDescriptor {
    id: ProviderId,
    display_name: String,
    capabilities: BTreeSet<Capability>,
}

impl ProviderDescriptor {
    pub fn id(&self) -> &ProviderId {
        &self.id
    }

    pub fn display_name(&self) -> &str {
        &self.display_name
    }

    pub fn capabilities(&self) -> &BTreeSet<Capability> {
        &self.capabilities
    }

    pub fn supports(&self, capability: Capability) -> bool {
        self.capabilities.contains(&capability)
    }
}

pub struct ProviderDescriptorBuilder {
    id: ProviderId,
    display_name: String,
    capabilities: BTreeSet<Capability>,
}

impl ProviderDescriptorBuilder {
    pub fn make(id: impl Into<String>, display_name: impl Into<String>) -> Self {
        Self {
            id: ProviderId::make(id),
            display_name: display_name.into(),
            capabilities: BTreeSet::new(),
        }
    }

    #[must_use]
    pub fn capability(mut self, capability: Capability) -> Self {
        self.capabilities.insert(capability);
        self
    }

    pub fn build(self) -> ProviderDescriptor {
        ProviderDescriptor {
            id: self.id,
            display_name: self.display_name,
            capabilities: self.capabilities,
        }
    }
}

pub trait Provider: Send + Sync {
    fn descriptor(&self) -> ProviderDescriptor;
}

#[derive(Default)]
pub struct ProviderRegistry {
    providers: Vec<Box<dyn Provider>>,
}

impl ProviderRegistry {
    pub fn descriptors(&self) -> Vec<ProviderDescriptor> {
        self.providers
            .iter()
            .map(|provider| provider.descriptor())
            .collect()
    }
}

#[derive(Default)]
pub struct ProviderRegistryBuilder {
    providers: Vec<Box<dyn Provider>>,
    ids: BTreeSet<ProviderId>,
}

impl ProviderRegistryBuilder {
    pub fn register(mut self, provider: impl Provider + 'static) -> IssueResult<Self> {
        let id = provider.descriptor().id().clone();
        if !self.ids.insert(id.clone()) {
            return Err(error().of(
                ErrorKind::Provider,
                format!("provider already registered: {}", id.as_str()),
            ));
        }
        self.providers.push(Box::new(provider));
        Ok(self)
    }

    pub fn build(self) -> ProviderRegistry {
        ProviderRegistry {
            providers: self.providers,
        }
    }
}

pub fn provider() -> ProviderRegistryBuilder {
    ProviderRegistryBuilder::default()
}