use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteLimits {
pub sites: NonZeroUsize,
pub subjects: NonZeroUsize,
pub schema_bytes: NonZeroUsize,
pub text_bytes: NonZeroUsize,
pub steps: NonZeroUsize,
pub transitions: NonZeroUsize,
pub drafts: NonZeroUsize,
pub approvers: NonZeroUsize,
pub snapshot_bytes: NonZeroUsize,
pub request_bytes: NonZeroUsize,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConstructionConfig {
pub limits: SiteLimits,
pub grants: super::effects::GrantSources,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Contract {
#[serde(default)]
pub input_schema_json: String,
#[serde(default)]
pub output_schema_json: String,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteSpec {
pub name: String,
pub purpose: String,
pub subjects: Vec<String>,
#[serde(default)]
pub contracts: BTreeMap<String, Contract>,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BuilderGrants {
pub plan: String,
pub serve: String,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Approval {
pub draft: String,
pub digest: String,
pub approved_by: Vec<String>,
pub executed_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub receipt: Option<lys_pass::drafts::Execution>,
}
impl From<crate::auth_lys::live::drafts::ApprovalTransition> for Approval {
fn from(value: crate::auth_lys::live::drafts::ApprovalTransition) -> Self {
Self {
draft: value.draft,
digest: value.digest,
approved_by: value.approved_by,
executed_at_ms: value.executed_at_ms,
receipt: None,
}
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(
tag = "act",
content = "detail",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum SiteChange {
CreateSite(SiteSpec),
BindBuilder { agent: String },
UnbindBuilder,
CreateTemplate(super::super::templates::TemplateProposal),
UpdateTemplate(super::super::templates::TemplateProposal),
RetireTemplate,
Complete,
}
impl SiteChange {
#[must_use]
pub const fn action(&self) -> &'static str {
match self {
Self::CreateSite(_) => "create_site",
Self::BindBuilder { .. } => "bind_builder",
Self::UnbindBuilder => "unbind_builder",
Self::CreateTemplate(_) => "create",
Self::UpdateTemplate(_) => "update",
Self::RetireTemplate => "retire",
Self::Complete => "complete",
}
}
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::CreateSite(_) => "liminal.network",
Self::CreateTemplate(_) | Self::UpdateTemplate(_) | Self::RetireTemplate => {
"liminal.template"
}
_ => "liminal.service",
}
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteAct {
pub target: String,
pub holder: String,
pub responsible: String,
pub change: SiteChange,
}
impl SiteAct {
pub fn prepare(
target: String,
change: SiteChange,
pass: &lys_pass::VerifiedPass,
) -> Result<Self, super::SiteError> {
let holder = &pass.claims().holder;
let responsible = match holder.kind.as_str() {
"person" => holder.id.clone(),
"agent" => holder
.responsible
.clone()
.ok_or(super::SiteError::Invalid("responsible person"))?,
_ => return Err(super::SiteError::Invalid("construction holder kind")),
};
if responsible.is_empty() {
return Err(super::SiteError::Invalid("responsible person"));
}
Ok(Self {
target,
holder: holder.id.clone(),
responsible,
change,
})
}
}
impl SiteAct {
#[must_use]
pub fn permission_id<'a>(&'a self, network: &'a str) -> &'a str {
if matches!(self.change, SiteChange::CreateSite(_)) {
network
} else {
&self.target
}
}
}