use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RightsClass {
Read,
Create,
Write,
Delete,
Send,
}
impl RightsClass {
pub const ALL: [RightsClass; 5] = [
RightsClass::Read,
RightsClass::Create,
RightsClass::Write,
RightsClass::Delete,
RightsClass::Send,
];
pub fn as_str(self) -> &'static str {
match self {
RightsClass::Read => "read",
RightsClass::Create => "create",
RightsClass::Write => "write",
RightsClass::Delete => "delete",
RightsClass::Send => "send",
}
}
pub fn from_token(token: &str) -> Option<Self> {
Self::ALL.into_iter().find(|class| class.as_str() == token)
}
}
impl fmt::Display for RightsClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LifecycleClass {
Primary,
Corpus { store: String },
Blob,
}
impl LifecycleClass {
pub fn from_token(token: &str, store: Option<&str>) -> Option<Self> {
match token {
"primary" => Some(LifecycleClass::Primary),
"corpus" | "representation" => Some(LifecycleClass::Corpus {
store: store.unwrap_or_default().into(),
}),
"blob" => Some(LifecycleClass::Blob),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
LifecycleClass::Primary => "primary",
LifecycleClass::Corpus { .. } => "corpus",
LifecycleClass::Blob => "blob",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Residency {
None,
Linked,
Process,
}
impl Residency {
pub fn as_str(self) -> &'static str {
match self {
Residency::None => "none",
Residency::Linked => "linked",
Residency::Process => "process",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RowSubstrate {
Note,
Entity,
}
impl RowSubstrate {
pub fn as_str(self) -> &'static str {
match self {
RowSubstrate::Note => "note",
RowSubstrate::Entity => "entity",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppVerb {
pub name: String,
pub rights: RightsClass,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RowTypeDecl {
pub substrate: RowSubstrate,
pub kind: String,
pub lifecycle: LifecycleClass,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppManifest {
pub name: String,
pub version: String,
pub signer: String,
pub verbs: Vec<AppVerb>,
pub entity_kinds: Vec<String>,
pub note_kinds: Vec<String>,
pub row_types: Vec<RowTypeDecl>,
pub capabilities: Vec<String>,
pub residency: Residency,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OwnedNames<'a> {
pub owner: &'a str,
pub verbs: Vec<&'a str>,
pub entity_kinds: Vec<&'a str>,
pub note_kinds: Vec<&'a str>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InstallRefusal {
EmptyIdentity { field: &'static str },
DuplicateVerb { verb: String },
UndeclaredRowType {
substrate: RowSubstrate,
kind: String,
},
RowTypeOutsideSlice {
substrate: RowSubstrate,
kind: String,
},
CorpusWithoutStore {
substrate: RowSubstrate,
kind: String,
},
VerbOwned {
verb: String,
owner: String,
candidate: String,
},
KindOwned {
substrate: RowSubstrate,
kind: String,
owner: String,
candidate: String,
},
}
impl fmt::Display for InstallRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InstallRefusal::EmptyIdentity { field } => {
write!(f, "manifest field {field:?} is empty")
}
InstallRefusal::DuplicateVerb { verb } => {
write!(f, "manifest declares verb {verb:?} twice")
}
InstallRefusal::UndeclaredRowType { substrate, kind } => write!(
f,
"{} kind {kind:?} is in the ontology slice but has no lifecycle class; every row type the app writes declares primary or corpus",
substrate.as_str()
),
InstallRefusal::RowTypeOutsideSlice { substrate, kind } => write!(
f,
"{} kind {kind:?} has a lifecycle declaration but is not in the ontology slice",
substrate.as_str()
),
InstallRefusal::CorpusWithoutStore { substrate, kind } => write!(
f,
"{} kind {kind:?} is a corpus row type and names no store",
substrate.as_str()
),
InstallRefusal::VerbOwned {
verb,
owner,
candidate,
} => write!(
f,
"verb {verb:?} is already registered by {owner:?}; app {candidate:?} cannot register it"
),
InstallRefusal::KindOwned {
substrate,
kind,
owner,
candidate,
} => write!(
f,
"{} kind {kind:?} is already contributed by {owner:?}; app {candidate:?} cannot contribute it",
substrate.as_str()
),
}
}
}
impl AppManifest {
pub fn validate(&self) -> Result<(), InstallRefusal> {
for (field, value) in [
("name", &self.name),
("version", &self.version),
("signer", &self.signer),
] {
if value.is_empty() {
return Err(InstallRefusal::EmptyIdentity { field });
}
}
for (index, verb) in self.verbs.iter().enumerate() {
if self.verbs[..index]
.iter()
.any(|seen| seen.name == verb.name)
{
return Err(InstallRefusal::DuplicateVerb {
verb: verb.name.clone(),
});
}
}
for (substrate, kinds) in [
(RowSubstrate::Entity, &self.entity_kinds),
(RowSubstrate::Note, &self.note_kinds),
] {
for kind in kinds {
if !self
.row_types
.iter()
.any(|row| row.substrate == substrate && row.kind == *kind)
{
return Err(InstallRefusal::UndeclaredRowType {
substrate,
kind: kind.clone(),
});
}
}
}
for row in &self.row_types {
let slice = match row.substrate {
RowSubstrate::Entity => &self.entity_kinds,
RowSubstrate::Note => &self.note_kinds,
};
if !slice.contains(&row.kind) {
return Err(InstallRefusal::RowTypeOutsideSlice {
substrate: row.substrate,
kind: row.kind.clone(),
});
}
if matches!(&row.lifecycle, LifecycleClass::Corpus { store } if store.is_empty()) {
return Err(InstallRefusal::CorpusWithoutStore {
substrate: row.substrate,
kind: row.kind.clone(),
});
}
}
Ok(())
}
pub fn owned_names(&self) -> OwnedNames<'_> {
OwnedNames {
owner: &self.name,
verbs: self.verbs.iter().map(|verb| verb.name.as_str()).collect(),
entity_kinds: self.entity_kinds.iter().map(String::as_str).collect(),
note_kinds: self.note_kinds.iter().map(String::as_str).collect(),
}
}
pub fn admit(&self, owners: &[OwnedNames<'_>]) -> Result<(), InstallRefusal> {
self.validate()?;
for owner in owners {
if let Some(verb) = self
.verbs
.iter()
.find(|verb| owner.verbs.iter().any(|held| *held == verb.name))
{
return Err(InstallRefusal::VerbOwned {
verb: verb.name.clone(),
owner: owner.owner.into(),
candidate: self.name.clone(),
});
}
for (substrate, kinds, held) in [
(
RowSubstrate::Entity,
&self.entity_kinds,
&owner.entity_kinds,
),
(RowSubstrate::Note, &self.note_kinds, &owner.note_kinds),
] {
if let Some(kind) = kinds.iter().find(|kind| held.contains(&kind.as_str())) {
return Err(InstallRefusal::KindOwned {
substrate,
kind: kind.clone(),
owner: owner.owner.into(),
candidate: self.name.clone(),
});
}
}
}
Ok(())
}
}
#[cfg(test)]
#[path = "app_tests.rs"]
mod tests;