use std::collections::BTreeMap;
use super::artifact::ContractArtifact;
use super::client_data::ClientData;
use super::error::ContractError;
use super::page_contract::{PageContract, PageContractEntry};
use super::page_schema::PageSchema;
#[derive(Debug, Default)]
pub struct PageContracts {
pages: BTreeMap<String, PageSchema>,
}
impl PageContracts {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register<P: ClientData>(self, page: PageContract<P>) -> Result<Self, ContractError> {
self.insert(page.name(), P::exposure_schema)
}
pub fn register_entry(self, entry: &PageContractEntry) -> Result<Self, ContractError> {
self.insert(entry.name, entry.schema)
}
#[must_use]
pub fn artifact(&self) -> ContractArtifact {
ContractArtifact::new(self.pages.clone())
}
fn insert(
mut self,
name: &str,
schema: fn() -> super::props_schema::PropsSchema,
) -> Result<Self, ContractError> {
if self.pages.contains_key(name) {
return Err(ContractError::DuplicatePage(name.to_owned()));
}
if let Some(existing) = self
.pages
.keys()
.find(|existing| existing.eq_ignore_ascii_case(name))
{
return Err(ContractError::CaseConflict {
existing: existing.clone(),
attempted: name.to_owned(),
});
}
self.pages
.insert(name.to_owned(), PageSchema::new(schema()));
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inertia::contracts::{ContractType, PropsSchema};
#[derive(serde::Serialize)]
struct Home;
impl ClientData for Home {
fn exposure_schema() -> PropsSchema {
PropsSchema::new().required("name", ContractType::string())
}
}
#[derive(serde::Serialize)]
struct Dashboard;
impl ClientData for Dashboard {
fn exposure_schema() -> PropsSchema {
PropsSchema::new()
}
}
const HOME: PageContract<Home> = PageContract::new("Home");
const HOME_LOWER: PageContract<Dashboard> = PageContract::new("home");
const DASH: PageContract<Dashboard> = PageContract::new("Dashboard");
const HOME_ENTRY: PageContractEntry = PageContractEntry::new("Home", Home::exposure_schema);
const DASH_ENTRY: PageContractEntry =
PageContractEntry::new("Dashboard", Dashboard::exposure_schema);
#[test]
fn artifact_is_deterministic() {
let registry = PageContracts::new().register(HOME).unwrap();
assert_eq!(
registry.artifact().to_json().unwrap(),
registry.artifact().to_json().unwrap()
);
}
#[test]
fn duplicate_identities_are_rejected() {
let error = PageContracts::new()
.register(HOME)
.and_then(|registry| registry.register(HOME))
.unwrap_err();
assert!(matches!(error, ContractError::DuplicatePage(_)));
}
#[test]
fn case_conflicts_are_rejected() {
let error = PageContracts::new()
.register(HOME)
.and_then(|registry| registry.register(HOME_LOWER))
.unwrap_err();
assert!(matches!(error, ContractError::CaseConflict { .. }));
}
#[test]
fn register_entry_builds_the_same_registry_as_register() {
let typed = PageContracts::new()
.register(HOME)
.unwrap()
.register(DASH)
.unwrap();
let entries = PageContracts::new()
.register_entry(&HOME_ENTRY)
.unwrap()
.register_entry(&DASH_ENTRY)
.unwrap();
assert_eq!(
typed.artifact().to_json().unwrap(),
entries.artifact().to_json().unwrap()
);
}
#[test]
fn register_entry_rejects_duplicates() {
let error = PageContracts::new()
.register_entry(&HOME_ENTRY)
.and_then(|registry| registry.register_entry(&HOME_ENTRY))
.unwrap_err();
assert!(matches!(error, ContractError::DuplicatePage(_)));
}
#[test]
fn register_entry_rejects_case_conflicts() {
const HOME_LOWER_ENTRY: PageContractEntry =
PageContractEntry::new("home", Home::exposure_schema);
let error = PageContracts::new()
.register_entry(&HOME_ENTRY)
.and_then(|registry| registry.register_entry(&HOME_LOWER_ENTRY))
.unwrap_err();
assert!(matches!(error, ContractError::CaseConflict { .. }));
}
}