thing-contract: the gamecult.thing.* kinds as CultCache entries and unions (first commit, build not yet verified)

Does not build-verified at this commit; tests follow.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-12 03:51:58 +02:00
commit 852c34b60a
12 changed files with 1933 additions and 0 deletions

View file

@ -0,0 +1,14 @@
[package]
name = "thing-contract"
version = "0.1.0"
edition = "2024"
license = "MIT"
publish = false
[dependencies]
anyhow = { workspace = true }
cultcache-rs = { workspace = true }
serde = { workspace = true }
[dev-dependencies]
rmp-serde = { workspace = true }

View file

@ -0,0 +1,38 @@
//! An asset catalog: the images a provider's surfaces reference by catalog id and asset id.
use cultcache_rs::{CultSlots, DatabaseEntry};
use crate::keys::{ProviderId, asset_catalog_key};
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct AssetEntry {
#[cultcache(key = 0)]
pub id: String,
#[cultcache(key = 1)]
pub media_type: String,
#[cultcache(key = 2)]
pub uri: String,
#[cultcache(key = 3)]
pub width: i32,
#[cultcache(key = 4)]
pub height: i32,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(type = "gamecult.thing.asset_catalog", schema = "gamecult.thing.asset_catalog.v1")]
pub struct AssetCatalog {
#[cultcache(key = 0)]
pub provider_id: ProviderId,
#[cultcache(key = 1)]
pub catalog_id: String,
#[cultcache(key = 2)]
pub version: i64,
#[cultcache(key = 3)]
pub assets: Vec<AssetEntry>,
}
impl AssetCatalog {
pub fn record_key(&self) -> String {
asset_catalog_key(&self.provider_id, &self.catalog_id)
}
}

View file

@ -0,0 +1,120 @@
//! Commands: what a surface declares, and the one envelope a consumer sends. The caller's
//! identity has no field here: the transport sets it.
use cultcache_rs::{CultSlots, CultUnion, DatabaseEntry};
use crate::keys::{CommandId, NodeId, SchemaId, invocation_key};
use crate::source::ParamName;
/// A command a surface declares. A node may reference only a declared command.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct CommandDecl {
#[cultcache(key = 0)]
pub id: CommandId,
#[cultcache(key = 1)]
pub payload_schema: SchemaId,
/// The input nodes whose current values travel with an invocation.
#[cultcache(key = 2)]
pub captures: Vec<NodeId>,
#[cultcache(key = 3)]
pub confirm: Option<String>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct CommandIdRef {
#[cultcache(key = 0)]
pub id: CommandId,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum CommandRef {
#[cultcache(key = 0)]
Id(CommandIdRef),
#[cultcache(key = 1)]
Param(ParamName),
}
/// A typed record of the declared `payload_schema`, as its MessagePack bytes.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct RecordPayload {
#[cultcache(key = 0)]
pub schema: SchemaId,
#[cultcache(key = 1, bytes)]
pub bytes: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct TextValue {
#[cultcache(key = 0)]
pub value: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct NumberValue {
#[cultcache(key = 0)]
pub value: f64,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct BoolValue {
#[cultcache(key = 0)]
pub value: bool,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct ChoiceValue {
#[cultcache(key = 0)]
pub value: String,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum InputValue {
#[cultcache(key = 0)]
Text(TextValue),
#[cultcache(key = 1)]
Number(NumberValue),
#[cultcache(key = 2)]
Bool(BoolValue),
#[cultcache(key = 3)]
Choice(ChoiceValue),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct CapturedInput {
#[cultcache(key = 0)]
pub node: NodeId,
#[cultcache(key = 1)]
pub value: InputValue,
}
/// A consumer's request that a provider run a declared command.
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(type = "gamecult.thing.command_invocation", schema = "gamecult.thing.command_invocation.v1")]
pub struct CommandInvocation {
/// A UUID string, unique per invocation.
#[cultcache(key = 0)]
pub invocation_id: String,
#[cultcache(key = 1)]
pub surface_key: String,
#[cultcache(key = 2)]
pub surface_version: i64,
#[cultcache(key = 3)]
pub command: CommandId,
#[cultcache(key = 4)]
pub payload: RecordPayload,
#[cultcache(key = 5)]
pub captured: Vec<CapturedInput>,
#[cultcache(key = 6)]
pub client: String,
/// RFC 3339, UTC.
#[cultcache(key = 7)]
pub issued_at: String,
#[cultcache(key = 8)]
pub delegation: Option<String>,
}
impl CommandInvocation {
pub fn record_key(&self) -> String {
invocation_key(&self.invocation_id)
}
}

View file

@ -0,0 +1,63 @@
//! Identifier newtypes and the record key of each kind. No caller spells a key as a free string.
use serde::{Deserialize, Serialize};
macro_rules! id_type {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
};
}
id_type!(
/// A provider of surfaces and state.
ProviderId
);
id_type!(
/// The schema id of a CultCache entry type, for example `gamecult.thing.surface.v1`.
SchemaId
);
id_type!(
/// The record key of a state record.
RecordKey
);
id_type!(
/// A node id, unique within its document after composition expansion.
NodeId
);
id_type!(
/// A command declared by a surface.
CommandId
);
pub(crate) fn surface_key(provider: &ProviderId, label: &str) -> String {
format!("thing:surface:{}.{}", provider.as_str(), label)
}
pub(crate) fn composition_key(publisher: &str, name: &str, version: i32) -> String {
format!("thing:composition:{publisher}.{name}.v{version}")
}
pub(crate) fn invocation_key(invocation_id: &str) -> String {
format!("thing:invocation:{invocation_id}")
}
pub(crate) fn result_key(invocation_id: &str, seq: i32) -> String {
format!("thing:result:{invocation_id}.{seq}")
}
pub(crate) fn asset_catalog_key(provider: &ProviderId, catalog_id: &str) -> String {
format!("thing:assets:{}.{}", provider.as_str(), catalog_id)
}

View file

@ -0,0 +1,37 @@
//! The `gamecult.thing.*` wire types and their bytes. This crate owns the contract: the
//! golden payloads it blesses are what every other language package and core are held to.
//!
//! Every kind is a CultCache `DatabaseEntry`; every closed set is a `CultUnion` written
//! `[armKey, [slots...]]`, the shape the C# reference reads and writes.
mod asset;
mod command;
mod keys;
mod node;
mod result;
mod source;
mod style;
pub use asset::*;
pub use command::*;
pub use keys::{CommandId, NodeId, ProviderId, RecordKey, SchemaId};
pub use node::*;
pub use result::*;
pub use source::*;
pub use style::*;
use cultcache_rs::{CultCache, CultCacheRegistry};
/// Registers the five kinds with a cache.
pub struct ThingContract;
impl CultCacheRegistry for ThingContract {
fn register_entries(&self, cache: &mut CultCache) -> anyhow::Result<()> {
cache.register_entry_type::<SurfaceDocument>()?;
cache.register_entry_type::<CompositionDocument>()?;
cache.register_entry_type::<CommandInvocation>()?;
cache.register_entry_type::<CommandResult>()?;
cache.register_entry_type::<AssetCatalog>()?;
Ok(())
}
}

View file

@ -0,0 +1,454 @@
//! Thing Script: the closed node union, the surface document that holds a tree of nodes, and the
//! composition document that names a reusable subtree. Arm keys are fixed for v1.
use cultcache_rs::{CultSlots, CultUnion, DatabaseEntry};
use crate::command::{CommandDecl, CommandRef};
use crate::keys::{NodeId, ProviderId, composition_key, surface_key};
use crate::source::{Bind, BoolSource, CollectionSource, MemberPath, NumberSource, TextSource};
use crate::style::{BoxStyle, Paint, Role};
/// What every node carries.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct NodeCommon {
#[cultcache(key = 0)]
pub id: NodeId,
#[cultcache(key = 1)]
pub visible: BoolSource,
#[cultcache(key = 2)]
pub a11y_label: Option<TextSource>,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Node {
#[cultcache(key = 0)]
Box(BoxNode),
#[cultcache(key = 1)]
Text(TextNode),
#[cultcache(key = 2)]
Image(ImageNode),
#[cultcache(key = 3)]
Button(ButtonNode),
#[cultcache(key = 4)]
Toggle(ToggleNode),
#[cultcache(key = 5)]
Slider(SliderNode),
#[cultcache(key = 6)]
TextInput(TextInputNode),
#[cultcache(key = 7)]
Select(SelectNode),
#[cultcache(key = 8)]
Repeat(RepeatNode),
#[cultcache(key = 9)]
Overlay(OverlayNode),
#[cultcache(key = 10)]
Plot(PlotNode),
#[cultcache(key = 11)]
Embed(EmbedNode),
#[cultcache(key = 12)]
Use(UseNode),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct BoxNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub style: BoxStyle,
#[cultcache(key = 2)]
pub paint: Paint,
#[cultcache(key = 3)]
pub role: Role,
#[cultcache(key = 4)]
pub children: Vec<Node>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct TextNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub content: TextSource,
#[cultcache(key = 2)]
pub style_token: String,
#[cultcache(key = 3)]
pub wrap: bool,
#[cultcache(key = 4)]
pub max_lines: Option<i32>,
#[cultcache(key = 5)]
pub selectable: bool,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum ImageFit {
#[cultcache(key = 0)]
Contain,
#[cultcache(key = 1)]
Cover,
#[cultcache(key = 2)]
Fill,
#[cultcache(key = 3)]
Unscaled,
}
/// An asset of a catalog, with the alternative text a reader without the picture needs.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct ImageNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub catalog: String,
#[cultcache(key = 2)]
pub asset: String,
#[cultcache(key = 3)]
pub fit: ImageFit,
#[cultcache(key = 4)]
pub alt: TextSource,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct ButtonNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub content: Vec<Node>,
#[cultcache(key = 2)]
pub on_activate: CommandRef,
#[cultcache(key = 3)]
pub enabled: BoolSource,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct ToggleNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub checked: BoolSource,
#[cultcache(key = 2)]
pub on_change: CommandRef,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct SliderNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub min: f64,
#[cultcache(key = 2)]
pub max: f64,
#[cultcache(key = 3)]
pub step: f64,
#[cultcache(key = 4)]
pub value: NumberSource,
#[cultcache(key = 5)]
pub commit: CommandRef,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum InputKind {
#[cultcache(key = 0)]
Text,
#[cultcache(key = 1)]
Number,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct TextInputNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub multiline: bool,
#[cultcache(key = 2)]
pub kind: InputKind,
#[cultcache(key = 3)]
pub placeholder: TextSource,
#[cultcache(key = 4)]
pub initial: TextSource,
#[cultcache(key = 5)]
pub submit: CommandRef,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct SelectOption {
#[cultcache(key = 0)]
pub value: String,
#[cultcache(key = 1)]
pub label: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct LiteralOptions {
#[cultcache(key = 0)]
pub options: Vec<SelectOption>,
}
/// Options read from a collection: each item's value and label are members of the item.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct BoundOptions {
#[cultcache(key = 0)]
pub collection: CollectionSource,
#[cultcache(key = 1)]
pub value_member: MemberPath,
#[cultcache(key = 2)]
pub label_member: MemberPath,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum OptionList {
#[cultcache(key = 0)]
Literal(LiteralOptions),
#[cultcache(key = 1)]
Bound(BoundOptions),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct SelectNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub options: OptionList,
#[cultcache(key = 2)]
pub value: Bind,
#[cultcache(key = 3)]
pub on_change: CommandRef,
}
/// The template is instantiated once per item of the collection, keyed by `key_member` of the item.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct RepeatNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub collection: CollectionSource,
#[cultcache(key = 2)]
pub key_member: MemberPath,
#[cultcache(key = 3)]
pub template: std::boxed::Box<Node>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct AnchorNode {
#[cultcache(key = 0)]
pub node: NodeId,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Anchor {
#[cultcache(key = 0)]
Viewport,
#[cultcache(key = 1)]
Node(AnchorNode),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct OverlayNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub anchor: Anchor,
#[cultcache(key = 2)]
pub modal: bool,
#[cultcache(key = 3)]
pub dismiss: CommandRef,
#[cultcache(key = 4)]
pub children: Vec<Node>,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum PlotMark {
#[cultcache(key = 0)]
Line,
#[cultcache(key = 1)]
Bar,
#[cultcache(key = 2)]
Area,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct PlotNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
/// A numeric list member of a record.
#[cultcache(key = 1)]
pub series: Bind,
#[cultcache(key = 2)]
pub mark: PlotMark,
#[cultcache(key = 3)]
pub x_min: Option<f64>,
#[cultcache(key = 4)]
pub x_max: Option<f64>,
#[cultcache(key = 5)]
pub y_min: Option<f64>,
#[cultcache(key = 6)]
pub y_max: Option<f64>,
}
/// A leaf the host draws: `slot_kind` is a name the host declares it supports.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct EmbedNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub slot_kind: String,
#[cultcache(key = 2)]
pub slot_id: String,
#[cultcache(key = 3)]
pub width: f64,
#[cultcache(key = 4)]
pub height: f64,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct CompositionRef {
#[cultcache(key = 0)]
pub publisher: String,
#[cultcache(key = 1)]
pub name: String,
#[cultcache(key = 2)]
pub version: i32,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct UseNode {
#[cultcache(key = 0)]
pub common: NodeCommon,
#[cultcache(key = 1)]
pub composition: CompositionRef,
#[cultcache(key = 2)]
pub args: Vec<Arg>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Arg {
#[cultcache(key = 0)]
pub name: String,
#[cultcache(key = 1)]
pub value: ArgValue,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct TextArg {
#[cultcache(key = 0)]
pub value: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct NumberArg {
#[cultcache(key = 0)]
pub value: f64,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct BoolArg {
#[cultcache(key = 0)]
pub value: bool,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct CollectionArg {
#[cultcache(key = 0)]
pub source: CollectionSource,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct CommandArg {
#[cultcache(key = 0)]
pub command: CommandRef,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct SlotArg {
#[cultcache(key = 0)]
pub nodes: Vec<Node>,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum ArgValue {
#[cultcache(key = 0)]
Text(TextArg),
#[cultcache(key = 1)]
Number(NumberArg),
#[cultcache(key = 2)]
Bool(BoolArg),
#[cultcache(key = 3)]
Bind(Bind),
#[cultcache(key = 4)]
Collection(CollectionArg),
#[cultcache(key = 5)]
Command(CommandArg),
#[cultcache(key = 6)]
Slot(SlotArg),
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum ParamKind {
#[cultcache(key = 0)]
Text,
#[cultcache(key = 1)]
Number,
#[cultcache(key = 2)]
Bool,
#[cultcache(key = 3)]
Bind,
#[cultcache(key = 4)]
Collection,
#[cultcache(key = 5)]
Command,
#[cultcache(key = 6)]
Slot,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct ParamDecl {
#[cultcache(key = 0)]
pub name: String,
#[cultcache(key = 1)]
pub kind: ParamKind,
}
/// A provider's surface: a tree of nodes and the commands it declares. A change of structure
/// republishes the record with a higher `version`.
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(type = "gamecult.thing.surface", schema = "gamecult.thing.surface.v1")]
pub struct SurfaceDocument {
#[cultcache(key = 0)]
pub provider_id: ProviderId,
#[cultcache(key = 1)]
pub label: String,
#[cultcache(key = 2)]
pub version: i64,
#[cultcache(key = 3)]
pub title: String,
#[cultcache(key = 4)]
pub root: Node,
#[cultcache(key = 5)]
pub commands: Vec<CommandDecl>,
}
impl SurfaceDocument {
pub fn record_key(&self) -> String {
surface_key(&self.provider_id, &self.label)
}
}
/// A reusable subtree with typed parameters. Compositions never own commands.
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(type = "gamecult.thing.composition", schema = "gamecult.thing.composition.v1")]
pub struct CompositionDocument {
#[cultcache(key = 0)]
pub publisher: String,
#[cultcache(key = 1)]
pub name: String,
#[cultcache(key = 2)]
pub version: i32,
#[cultcache(key = 3)]
pub params: Vec<ParamDecl>,
#[cultcache(key = 4)]
pub body: Node,
}
impl CompositionDocument {
pub fn record_key(&self) -> String {
composition_key(&self.publisher, &self.name, self.version)
}
}

View file

@ -0,0 +1,185 @@
//! The answer to a command invocation. A `CommandResult` is built only by its four
//! constructors, each of which copies the invocation's id, and is read through accessors.
//! Nothing here takes a state as a string, and nothing builds a result from a map.
use cultcache_rs::{CultSlots, CultUnion, DatabaseEntry};
use crate::command::{CommandInvocation, CommandRef};
use crate::keys::{NodeId, result_key};
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct PendingOutcome {
#[cultcache(key = 0)]
pub message: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct CompletedOutcome {
#[cultcache(key = 0)]
pub source_version: Option<i64>,
/// Whether the captured drafts of the invocation clear.
#[cultcache(key = 1)]
pub clear_drafts: bool,
#[cultcache(key = 2)]
pub effect: Option<Effect>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct DeniedOutcome {
#[cultcache(key = 0)]
pub reason: String,
#[cultcache(key = 1)]
pub node: Option<NodeId>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct FailedOutcome {
#[cultcache(key = 0)]
pub reason: String,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Outcome {
#[cultcache(key = 0)]
Pending(PendingOutcome),
#[cultcache(key = 1)]
Completed(CompletedOutcome),
#[cultcache(key = 2)]
Denied(DeniedOutcome),
#[cultcache(key = 3)]
Failed(FailedOutcome),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct OpenSurfaceEffect {
#[cultcache(key = 0)]
pub key: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct OpenExternalEffect {
#[cultcache(key = 0)]
pub url: String,
/// The command run when the external step returns.
#[cultcache(key = 1)]
pub resume: CommandRef,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Effect {
#[cultcache(key = 0)]
OpenSurface(OpenSurfaceEffect),
#[cultcache(key = 1)]
OpenExternal(OpenExternalEffect),
}
/// A provider's answer to one invocation. `seq` counts from 1 per invocation.
///
/// The positive case, a result built by a constructor from the invocation it answers:
///
/// ```
/// use thing_contract::{CommandInvocation, CommandResult};
/// fn answer(invocation: &CommandInvocation) -> CommandResult {
/// CommandResult::failed(invocation, 1, "refused")
/// }
/// ```
///
/// A struct literal does not compile, because the fields are private:
///
/// ```compile_fail,E0451
/// use thing_contract::{CommandResult, Outcome, FailedOutcome};
/// let _ = CommandResult {
/// invocation_id: String::new(),
/// seq: 1,
/// outcome: Outcome::Failed(FailedOutcome { reason: String::new() }),
/// };
/// ```
///
/// Neither does assigning the outcome:
///
/// ```compile_fail,E0616
/// use thing_contract::{CommandInvocation, CommandResult, Outcome, FailedOutcome};
/// fn tamper(invocation: &CommandInvocation) {
/// let mut result = CommandResult::failed(invocation, 1, "refused");
/// result.outcome = Outcome::Failed(FailedOutcome { reason: String::new() });
/// }
/// ```
///
/// Nothing converts a map into a result:
///
/// ```compile_fail,E0277
/// use std::collections::BTreeMap;
/// use thing_contract::CommandResult;
/// let flat: BTreeMap<String, String> = BTreeMap::new();
/// let _ = CommandResult::from(flat);
/// ```
///
/// Nor a string state:
///
/// ```compile_fail,E0277
/// use thing_contract::CommandResult;
/// let _ = CommandResult::from("completed");
/// ```
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(type = "gamecult.thing.command_result", schema = "gamecult.thing.command_result.v1")]
pub struct CommandResult {
#[cultcache(key = 0)]
invocation_id: String,
#[cultcache(key = 1)]
seq: i32,
#[cultcache(key = 2)]
outcome: Outcome,
}
impl CommandResult {
fn answering(invocation: &CommandInvocation, seq: i32, outcome: Outcome) -> Self {
Self { invocation_id: invocation.invocation_id.clone(), seq, outcome }
}
pub fn pending(invocation: &CommandInvocation, seq: i32, message: impl Into<String>) -> Self {
Self::answering(invocation, seq, Outcome::Pending(PendingOutcome { message: message.into() }))
}
pub fn completed(
invocation: &CommandInvocation,
seq: i32,
source_version: Option<i64>,
clear_drafts: bool,
effect: Option<Effect>,
) -> Self {
Self::answering(
invocation,
seq,
Outcome::Completed(CompletedOutcome { source_version, clear_drafts, effect }),
)
}
pub fn denied(
invocation: &CommandInvocation,
seq: i32,
reason: impl Into<String>,
node: Option<NodeId>,
) -> Self {
Self::answering(invocation, seq, Outcome::Denied(DeniedOutcome { reason: reason.into(), node }))
}
pub fn failed(invocation: &CommandInvocation, seq: i32, reason: impl Into<String>) -> Self {
Self::answering(invocation, seq, Outcome::Failed(FailedOutcome { reason: reason.into() }))
}
pub fn invocation_id(&self) -> &str {
&self.invocation_id
}
pub fn seq(&self) -> i32 {
self.seq
}
pub fn outcome(&self) -> &Outcome {
&self.outcome
}
pub fn record_key(&self) -> String {
result_key(&self.invocation_id, self.seq)
}
}

View file

@ -0,0 +1,169 @@
//! Where a value comes from: a literal, a binding to a state record, or a composition parameter.
//! A member path is a list of names and indices, never an expression.
use cultcache_rs::{CultSlots, CultUnion};
use crate::keys::{ProviderId, RecordKey, SchemaId};
/// A composition parameter, named.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct ParamName {
#[cultcache(key = 0)]
pub name: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct TextLiteral {
#[cultcache(key = 0)]
pub value: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct NumberLiteral {
#[cultcache(key = 0)]
pub value: f64,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct BoolLiteral {
#[cultcache(key = 0)]
pub value: bool,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum TextSource {
#[cultcache(key = 0)]
Literal(TextLiteral),
#[cultcache(key = 1)]
Bound(Bind),
#[cultcache(key = 2)]
Param(ParamName),
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum NumberSource {
#[cultcache(key = 0)]
Literal(NumberLiteral),
#[cultcache(key = 1)]
Bound(Bind),
#[cultcache(key = 2)]
Param(ParamName),
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum BoolSource {
#[cultcache(key = 0)]
Literal(BoolLiteral),
#[cultcache(key = 1)]
Bound(Bind),
#[cultcache(key = 2)]
Param(ParamName),
}
/// One member of a record, read through a path, optionally formatted.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Bind {
#[cultcache(key = 0)]
pub source: StateSource,
#[cultcache(key = 1)]
pub member: MemberPath,
#[cultcache(key = 2)]
pub format: Option<Format>,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum StateSource {
#[cultcache(key = 0)]
Record(StateRef),
/// The current item inside a `Repeat` template.
#[cultcache(key = 1)]
Item,
}
/// A state record. An absent provider is the publisher of the document.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct StateRef {
#[cultcache(key = 0)]
pub provider: Option<ProviderId>,
#[cultcache(key = 1)]
pub schema: SchemaId,
#[cultcache(key = 2)]
pub key: RecordKey,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct MemberPath {
#[cultcache(key = 0)]
pub steps: Vec<MemberStep>,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum MemberStep {
#[cultcache(key = 0)]
Name(MemberName),
#[cultcache(key = 1)]
Index(MemberIndex),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct MemberName {
#[cultcache(key = 0)]
pub name: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct MemberIndex {
#[cultcache(key = 0)]
pub index: i32,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Format {
#[cultcache(key = 0)]
Number(NumberFormat),
#[cultcache(key = 1)]
Duration,
#[cultcache(key = 2)]
Timestamp,
#[cultcache(key = 3)]
Affix(AffixFormat),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct NumberFormat {
#[cultcache(key = 0)]
pub precision: i32,
#[cultcache(key = 1)]
pub percent: bool,
#[cultcache(key = 2)]
pub unit: Option<String>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct AffixFormat {
#[cultcache(key = 0)]
pub prefix: String,
#[cultcache(key = 1)]
pub suffix: String,
}
/// The records of one type, or a list member of one record.
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum CollectionSource {
#[cultcache(key = 0)]
Records(RecordsSource),
#[cultcache(key = 1)]
Member(Bind),
#[cultcache(key = 2)]
Param(ParamName),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct RecordsSource {
#[cultcache(key = 0)]
pub provider: Option<ProviderId>,
#[cultcache(key = 1)]
pub schema: SchemaId,
#[cultcache(key = 2)]
pub key_prefix: String,
}

View file

@ -0,0 +1,241 @@
//! Box layout and paint as plain data. The crate holds no layout engine: core maps `BoxStyle`
//! onto its solver.
use cultcache_rs::{CultSlots, CultUnion};
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Scalar {
#[cultcache(key = 0)]
pub value: f64,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Dimension {
#[cultcache(key = 0)]
Px(Scalar),
#[cultcache(key = 1)]
Percent(Scalar),
#[cultcache(key = 2)]
Fr(Scalar),
#[cultcache(key = 3)]
Auto,
#[cultcache(key = 4)]
Content,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Size2 {
#[cultcache(key = 0)]
pub width: Dimension,
#[cultcache(key = 1)]
pub height: Dimension,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Edges {
#[cultcache(key = 0)]
pub top: Dimension,
#[cultcache(key = 1)]
pub right: Dimension,
#[cultcache(key = 2)]
pub bottom: Dimension,
#[cultcache(key = 3)]
pub left: Dimension,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Display {
#[cultcache(key = 0)]
Flex,
#[cultcache(key = 1)]
Grid,
#[cultcache(key = 2)]
Block,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Direction {
#[cultcache(key = 0)]
Row,
#[cultcache(key = 1)]
Column,
#[cultcache(key = 2)]
RowReverse,
#[cultcache(key = 3)]
ColumnReverse,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Wrap {
#[cultcache(key = 0)]
NoWrap,
#[cultcache(key = 1)]
Wrap,
#[cultcache(key = 2)]
WrapReverse,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Position {
#[cultcache(key = 0)]
Relative,
#[cultcache(key = 1)]
Absolute,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Overflow {
#[cultcache(key = 0)]
Visible,
#[cultcache(key = 1)]
Clip,
#[cultcache(key = 2)]
Scroll,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum ItemAlign {
#[cultcache(key = 0)]
Auto,
#[cultcache(key = 1)]
Start,
#[cultcache(key = 2)]
End,
#[cultcache(key = 3)]
Center,
#[cultcache(key = 4)]
Stretch,
#[cultcache(key = 5)]
Baseline,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum ContentAlign {
#[cultcache(key = 0)]
Auto,
#[cultcache(key = 1)]
Start,
#[cultcache(key = 2)]
End,
#[cultcache(key = 3)]
Center,
#[cultcache(key = 4)]
Stretch,
#[cultcache(key = 5)]
SpaceBetween,
#[cultcache(key = 6)]
SpaceAround,
#[cultcache(key = 7)]
SpaceEvenly,
}
/// The layout of a box. `gap` is the column gap in `width` and the row gap in `height`.
/// `grid_columns` and `grid_rows` are the grid tracks and are read only when `display` is Grid.
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct BoxStyle {
#[cultcache(key = 0)]
pub display: Display,
#[cultcache(key = 1)]
pub direction: Direction,
#[cultcache(key = 2)]
pub wrap: Wrap,
#[cultcache(key = 3)]
pub gap: Size2,
#[cultcache(key = 4)]
pub padding: Edges,
#[cultcache(key = 5)]
pub margin: Edges,
#[cultcache(key = 6)]
pub size: Size2,
#[cultcache(key = 7)]
pub min_size: Size2,
#[cultcache(key = 8)]
pub max_size: Size2,
#[cultcache(key = 9)]
pub position: Position,
#[cultcache(key = 10)]
pub inset: Edges,
#[cultcache(key = 11)]
pub overflow: Overflow,
#[cultcache(key = 12)]
pub align_items: ItemAlign,
#[cultcache(key = 13)]
pub align_self: ItemAlign,
#[cultcache(key = 14)]
pub align_content: ContentAlign,
#[cultcache(key = 15)]
pub justify_content: ContentAlign,
#[cultcache(key = 16)]
pub grow: f64,
#[cultcache(key = 17)]
pub shrink: f64,
#[cultcache(key = 18)]
pub basis: Dimension,
#[cultcache(key = 19)]
pub grid_columns: Vec<Dimension>,
#[cultcache(key = 20)]
pub grid_rows: Vec<Dimension>,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct ThemeToken {
#[cultcache(key = 0)]
pub name: String,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Rgba {
#[cultcache(key = 0)]
pub r: u8,
#[cultcache(key = 1)]
pub g: u8,
#[cultcache(key = 2)]
pub b: u8,
#[cultcache(key = 3)]
pub a: u8,
}
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum ColorRef {
#[cultcache(key = 0)]
Token(ThemeToken),
#[cultcache(key = 1)]
Rgba(Rgba),
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Border {
#[cultcache(key = 0)]
pub color: ColorRef,
#[cultcache(key = 1)]
pub width: f64,
}
#[derive(Clone, Debug, PartialEq, CultSlots)]
pub struct Paint {
#[cultcache(key = 0)]
pub background: Option<ColorRef>,
#[cultcache(key = 1)]
pub border: Option<Border>,
#[cultcache(key = 2)]
pub radius: f64,
}
/// The accessibility role of a box. The accessibility tree itself is core's.
#[derive(Clone, Debug, PartialEq, CultUnion)]
pub enum Role {
#[cultcache(key = 0)]
Group,
#[cultcache(key = 1)]
Region,
#[cultcache(key = 2)]
List,
#[cultcache(key = 3)]
ListItem,
#[cultcache(key = 4)]
Heading,
#[cultcache(key = 5)]
Dialog,
#[cultcache(key = 6)]
Toolbar,
}