use crate::core::{Checkout, CheckoutFrame};
use crate::operation::{DescriptorValidationError, OperationDescriptor, OperationInput};
use crate::operation_name::{OperationName, OperationNameError};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RegisterValidationError {
DuplicateOperationName {
name: String,
},
InvalidDescriptor {
name: String,
source: DescriptorValidationError,
},
InvalidModuleName {
name: String,
message: &'static str,
},
}
impl fmt::Display for RegisterValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateOperationName { name } => {
write!(f, "duplicate operation name `{name}`")
}
Self::InvalidDescriptor { name, source } => {
write!(f, "operation `{name}` has invalid descriptor: {source}")
}
Self::InvalidModuleName { name, message } => {
write!(f, "module name `{name}` is invalid: {message}")
}
}
}
}
impl std::error::Error for RegisterValidationError {}
#[derive(Default)]
pub struct Register {
operations: BTreeMap<String, OperationDescriptor>,
}
impl Register {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn from_operations<I>(operations: I) -> Result<Self, RegisterValidationError>
where
I: IntoIterator<Item = OperationDescriptor>,
{
let mut register = Self::new();
for descriptor in operations {
register.insert_operation(descriptor)?;
}
Ok(register)
}
pub fn insert_operation(
&mut self,
descriptor: OperationDescriptor,
) -> Result<(), RegisterValidationError> {
let name = descriptor.name().to_owned();
descriptor
.validate()
.map_err(|source| RegisterValidationError::InvalidDescriptor {
name: name.clone(),
source,
})?;
if self.operations.contains_key(&name) {
return Err(RegisterValidationError::DuplicateOperationName { name });
}
self.operations.insert(name, descriptor);
Ok(())
}
#[must_use]
pub fn operation(&self, name: &str) -> Option<&OperationDescriptor> {
self.operations.get(name)
}
#[must_use]
pub fn descriptor(&self, name: &str) -> Option<&OperationDescriptor> {
self.operation(name)
}
#[must_use]
pub fn contains_operation(&self, name: &str) -> bool {
self.operations.contains_key(name)
}
#[must_use]
pub fn len(&self) -> usize {
self.operations.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.operations.is_empty()
}
pub fn names(&self) -> impl Iterator<Item = &str> + '_ {
self.operations.keys().map(String::as_str)
}
pub fn operations(&self) -> impl Iterator<Item = (&str, &OperationDescriptor)> + '_ {
self.operations
.iter()
.map(|(name, descriptor)| (name.as_str(), descriptor))
}
pub fn descriptors(&self) -> impl Iterator<Item = (&str, &OperationDescriptor)> + '_ {
self.operations()
}
#[must_use]
pub fn checkout(&self, name: impl AsRef<str>, input: OperationInput) -> Option<Checkout> {
let name = name.as_ref();
let descriptor = self.operation(name)?.clone();
Some(Checkout::new(descriptor, input))
}
#[must_use]
pub fn checkout_frame(name: impl Into<String>, input: OperationInput) -> CheckoutFrame {
CheckoutFrame::new(name, input)
}
#[must_use]
pub fn as_map(&self) -> &BTreeMap<String, OperationDescriptor> {
&self.operations
}
#[must_use]
pub fn into_map(self) -> BTreeMap<String, OperationDescriptor> {
self.operations
}
}
pub(crate) fn validate_module_name(name: &str) -> Result<(), RegisterValidationError> {
OperationName::new(name).map(|_| ()).map_err(|error| {
let message: &'static str = match error {
OperationNameError::Empty => "empty",
OperationNameError::TooLong { .. } => "too long",
OperationNameError::LeadingOrTrailingDot | OperationNameError::ConsecutiveDots => {
"dot-separated tokens must be non-empty"
}
OperationNameError::IllegalCharacter { .. } => {
"expected ASCII letters, digits, '.', '_' or '-'"
}
};
RegisterValidationError::InvalidModuleName {
name: name.to_owned(),
message,
}
})
}
#[derive(Default)]
pub struct CacheRegister<'a> {
operations: BTreeMap<&'a str, &'a OperationDescriptor>,
}
impl<'a> CacheRegister<'a> {
#[must_use]
pub fn from_register(register: &'a Register) -> Self {
let operations = register.operations().collect();
Self { operations }
}
#[must_use]
pub fn operation(&self, name: &str) -> Option<&'a OperationDescriptor> {
self.operations.get(name).copied()
}
#[must_use]
pub fn descriptor(&self, name: &str) -> Option<&'a OperationDescriptor> {
self.operation(name)
}
#[must_use]
pub fn contains_operation(&self, name: &str) -> bool {
self.operations.contains_key(name)
}
#[must_use]
pub fn len(&self) -> usize {
self.operations.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.operations.is_empty()
}
pub fn names(&self) -> impl Iterator<Item = &'a str> + '_ {
self.operations.keys().copied()
}
pub fn operations(&self) -> impl Iterator<Item = (&'a str, &'a OperationDescriptor)> + '_ {
self.operations
.iter()
.map(|(name, descriptor)| (*name, *descriptor))
}
pub fn descriptors(&self) -> impl Iterator<Item = (&'a str, &'a OperationDescriptor)> + '_ {
self.operations()
}
}