use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fmt, str::FromStr};
pub use serde_json::{Map, Number};
pub type Json = serde_json::Value;
pub type ObjectMatcher = BTreeMap<String, MatchValue>;
pub type Assignments = Vec<(String, MutationValue)>;
pub type BoundObject = BTreeMap<String, BoundValue>;
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub enum KipValue {
#[default]
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<KipValue>),
Object(BTreeMap<String, KipValue>),
}
impl fmt::Display for KipValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", Json::from(self.clone()))
}
}
impl From<KipValue> for Json {
fn from(value: KipValue) -> Self {
match value {
KipValue::Null => Json::Null,
KipValue::Bool(b) => Json::Bool(b),
KipValue::Number(n) => Json::Number(n),
KipValue::String(s) => Json::String(s),
KipValue::Array(items) => Json::Array(items.into_iter().map(Json::from).collect()),
KipValue::Object(fields) => Json::Object(
fields
.into_iter()
.map(|(k, v)| (k, Json::from(v)))
.collect(),
),
}
}
}
impl TryFrom<Json> for KipValue {
type Error = String;
fn try_from(value: Json) -> Result<Self, Self::Error> {
Ok(match value {
Json::Null => KipValue::Null,
Json::Bool(b) => KipValue::Bool(b),
Json::Number(n) => {
if n.as_f64().is_some_and(|f| !f.is_finite()) {
return Err(format!("{n} is not a finite KIP number"));
}
KipValue::Number(n)
}
Json::String(s) => KipValue::String(s),
Json::Array(items) => KipValue::Array(
items
.into_iter()
.map(KipValue::try_from)
.collect::<Result<_, _>>()?,
),
Json::Object(fields) => KipValue::Object(
fields
.into_iter()
.map(|(k, v)| KipValue::try_from(v).map(|v| (k, v)))
.collect::<Result<_, _>>()?,
),
})
}
}
impl From<&str> for KipValue {
fn from(s: &str) -> Self {
KipValue::String(s.to_string())
}
}
impl From<String> for KipValue {
fn from(s: String) -> Self {
KipValue::String(s)
}
}
impl From<bool> for KipValue {
fn from(b: bool) -> Self {
KipValue::Bool(b)
}
}
impl From<i64> for KipValue {
fn from(n: i64) -> Self {
KipValue::Number(Number::from(n))
}
}
impl From<u64> for KipValue {
fn from(n: u64) -> Self {
KipValue::Number(Number::from(n))
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum BoundValue {
Value(KipValue),
Param(String),
Handle(String),
Variable(DotPathVar),
Array(Vec<BoundValue>),
Object(Vec<(String, BoundValue)>),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Scalar {
Literal(KipValue),
Param(String),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum SymbolRef {
Name(String),
Param(String),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum ElementRef {
Handle(String),
Param(String),
Id(String),
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct DotPathVar {
pub var: String,
pub path: Vec<PathStep>,
}
impl fmt::Display for DotPathVar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "?{}", self.var)?;
for step in &self.path {
match step {
PathStep::Field(name) => write!(f, ".{name}")?,
PathStep::Key(key) => write!(f, "[{}]", Json::String(key.clone()))?,
}
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum PathStep {
Field(String),
Key(String),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum PredAtom {
Variable(String),
Literal(String),
Param(String),
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct HopRange {
pub min: u32,
pub max: Option<u32>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct PredPathAtom {
pub predicate: PredAtom,
pub hops: Option<HopRange>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum PredTerm {
Atom(PredAtom),
Path(Vec<PredPathAtom>),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Term {
Variable(String),
Param(String),
Literal(KipValue),
Match(ObjectMatcher),
Proposition(Box<PropositionMatcher>),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum MatchValue {
Variable(String),
Param(String),
Literal(KipValue),
Array(Vec<MatchValue>),
Match(ObjectMatcher),
Proposition(PropositionMatcher),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct PropositionTriple {
pub subject: Term,
pub predicate: PredTerm,
pub object: Term,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum PropositionMatcher {
Tuple(PropositionTriple),
Id(Scalar),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Command {
Kql(KqlQuery),
Kml(KmlStatement),
Meta(MetaCommand),
}
impl Command {
pub fn is_mutation(&self) -> bool {
matches!(self, Command::Kml(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CommandType {
Kql,
Kml,
Meta,
Unknown,
}
impl CommandType {
pub fn from(val: &Command) -> CommandType {
match val {
Command::Kql(_) => CommandType::Kql,
Command::Kml(_) => CommandType::Kml,
Command::Meta(_) => CommandType::Meta,
}
}
}
impl fmt::Display for CommandType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CommandType::Kql => write!(f, "KQL"),
CommandType::Kml => write!(f, "KML"),
CommandType::Meta => write!(f, "META"),
CommandType::Unknown => write!(f, "UNKNOWN"),
}
}
}
impl FromStr for CommandType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_uppercase().as_str() {
"KQL" => Ok(CommandType::Kql),
"KML" => Ok(CommandType::Kml),
"META" => Ok(CommandType::Meta),
_ => Ok(CommandType::Unknown),
}
}
}
impl Serialize for CommandType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for CommandType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
CommandType::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct KqlQuery {
pub find_clause: FindClause,
pub where_clauses: Vec<WhereClause>,
pub as_of: Option<AsOf>,
pub for_time: Option<Scalar>,
pub epistemic: Option<BoundObject>,
pub order_by: Option<Vec<OrderByItem>>,
pub limit: Option<Scalar>,
pub cursor: Option<Scalar>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum AsOf {
Seq(Scalar),
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct FindClause {
pub expressions: Vec<FindExpression>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum FindExpression {
Variable(DotPathVar),
Aggregation {
func: AggregationFunction,
var: DotPathVar,
distinct: bool,
},
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum AggregationFunction {
Count,
Sum,
Avg,
Min,
Max,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct OrderByItem {
pub variable: DotPathVar,
pub direction: OrderDirection,
pub aggregation: Option<AggregationFunction>,
#[serde(default, skip_serializing_if = "order_distinct_is_false")]
pub distinct: bool,
}
fn order_distinct_is_false(value: &bool) -> bool {
!value
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum OrderDirection {
#[default]
Asc,
Desc,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum WhereClause {
Concept {
variable: String,
matcher: ObjectMatcher,
},
Proposition {
variable: Option<String>,
matcher: PropositionMatcher,
},
Assertion {
variable: String,
matcher: ObjectMatcher,
},
Evidence {
variable: String,
matcher: ObjectMatcher,
},
Activity {
variable: String,
matcher: ObjectMatcher,
},
Structural {
variable: Option<String>,
subject: Term,
field: SymbolRef,
object: Term,
},
Belief {
variable: String,
target: BeliefTarget,
},
BeliefSlot {
variable: String,
subject: Term,
predicate: PredAtom,
},
Filter {
expression: FilterExpression,
},
Not(Vec<WhereClause>),
Optional(Vec<WhereClause>),
Union(Vec<WhereClause>),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum BeliefTarget {
Proposition(String),
Id(Scalar),
Tuple(PropositionTriple),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum FilterExpression {
Comparison {
left: FilterOperand,
operator: ComparisonOperator,
right: FilterOperand,
},
Logical {
left: Box<FilterExpression>,
operator: LogicalOperator,
right: Box<FilterExpression>,
},
Not(Box<FilterExpression>),
Function {
func: FilterFunction,
args: Vec<FilterOperand>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum FilterOperand {
Variable(DotPathVar),
Literal(KipValue),
Param(String),
List(Vec<FilterOperand>),
Negate(Box<FilterOperand>),
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum ComparisonOperator {
Equal,
NotEqual,
LessThan,
GreaterThan,
LessEqual,
GreaterEqual,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum LogicalOperator {
And,
Or,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum FilterFunction {
Contains,
StartsWith,
EndsWith,
Regex,
In,
IsNull,
IsNotNull,
IsLiteral,
IsElement,
IsKind,
LiteralType,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct KmlStatement {
pub explicit_transaction: bool,
pub clauses: Vec<MutationClause>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum MutationClause {
CreateConcept(ConceptCreate),
UpsertConcept(ConceptUpsert),
EnsureProposition(EnsureProposition),
CreateEvidence(RecordCreate),
CreateAssertion(RecordCreate),
CreateActivity(RecordCreate),
Update(UpdateStatement),
Transition(Transition),
SetRetention(SetRetention),
Purge(PurgeStatement),
PurgePayload(PurgePayloadStatement),
MergeConcept(MergeConcept),
}
impl MutationClause {
pub fn handle(&self) -> Option<&str> {
match self {
MutationClause::CreateConcept(c) => Some(c.handle.as_str()),
MutationClause::UpsertConcept(c) => Some(c.handle.as_str()),
MutationClause::CreateEvidence(c)
| MutationClause::CreateAssertion(c)
| MutationClause::CreateActivity(c) => Some(c.handle.as_str()),
MutationClause::EnsureProposition(c) => c.handle.as_deref(),
_ => None,
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct ConceptCreate {
pub handle: String,
pub r#type: Option<SymbolRef>,
pub client_key: Option<Scalar>,
pub name: Option<Scalar>,
pub set_fields: Option<Assignments>,
pub set_attributes: Option<Assignments>,
pub set_facets: Vec<FacetAssignment>,
pub set_structural: Option<Vec<StructuralEdge>>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct ConceptUpsert {
pub handle: String,
pub r#match: Option<ObjectMatcher>,
pub expect_versions: Vec<ExpectVersion>,
pub set_fields: Option<Assignments>,
pub set_attributes: Option<Assignments>,
pub set_facets: Vec<FacetAssignment>,
pub unset_attributes: Option<Vec<String>>,
pub unset_facets: Vec<FacetUnset>,
pub set_structural: Option<Vec<StructuralEdge>>,
pub unset_structural: Option<Vec<StructuralRemoval>>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct RecordCreate {
pub handle: String,
pub client_key: Option<Scalar>,
pub set_fields: Option<Assignments>,
pub set_facets: Vec<FacetAssignment>,
pub set_structural: Option<Vec<StructuralEdge>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct EnsureProposition {
pub handle: Option<String>,
pub subject: Term,
pub predicate: PredAtom,
pub object: Term,
pub expect_versions: Vec<ExpectVersion>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ExpectVersion {
pub version: Scalar,
pub plane: Option<VersionPlane>,
}
impl ExpectVersion {
pub fn element(version: Scalar) -> Self {
Self {
version,
plane: None,
}
}
pub fn plane_key(&self) -> String {
match &self.plane {
None => "element".to_string(),
Some(VersionPlane::Attributes) => "attributes".to_string(),
Some(VersionPlane::Structural) => "structural".to_string(),
Some(VersionPlane::Retention) => "retention".to_string(),
Some(VersionPlane::Facet(SymbolRef::Name(name))) => format!("facet:{name}"),
Some(VersionPlane::Facet(SymbolRef::Param(name))) => format!("facet::{name}"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum VersionPlane {
Attributes,
Structural,
Retention,
Facet(SymbolRef),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct FacetAssignment {
pub facet: SymbolRef,
pub values: Assignments,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct FacetUnset {
pub facet: SymbolRef,
pub fields: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct StructuralEdge {
pub field: SymbolRef,
pub value: MutationValue,
pub options: Option<BoundObject>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct StructuralRemoval {
pub field: SymbolRef,
pub value: MutationValue,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum MutationValue {
Value(KipValue),
Param(String),
Handle(String),
Variable(DotPathVar),
Array(Vec<BoundValue>),
Object(Vec<(String, BoundValue)>),
Expr(UpdateExpr),
}
impl From<BoundValue> for MutationValue {
fn from(value: BoundValue) -> Self {
match value {
BoundValue::Value(v) => MutationValue::Value(v),
BoundValue::Param(p) => MutationValue::Param(p),
BoundValue::Handle(h) => MutationValue::Handle(h),
BoundValue::Variable(v) => MutationValue::Variable(v),
BoundValue::Array(items) => MutationValue::Array(items),
BoundValue::Object(fields) => MutationValue::Object(fields),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum UpdateExpr {
Variable(DotPathVar),
Number(Number),
Param(String),
Function {
func: UpdateFunction,
args: Vec<UpdateExpr>,
},
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum UpdateFunction {
Add,
Mul,
Clamp,
Coalesce,
}
impl UpdateFunction {
pub fn arity(&self) -> usize {
match self {
UpdateFunction::Add | UpdateFunction::Mul | UpdateFunction::Coalesce => 2,
UpdateFunction::Clamp => 3,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct UpdateStatement {
pub target: ElementRef,
pub actions: Vec<UpdateAction>,
pub where_clauses: Option<Vec<WhereClause>>,
pub limit: Option<Scalar>,
pub expect_versions: Vec<ExpectVersion>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum UpdateAction {
SetFields(Assignments),
SetAttributes(Assignments),
SetFacet(FacetAssignment),
UnsetAttributes(Vec<String>),
UnsetFacet(FacetUnset),
SetStructural(Vec<StructuralEdge>),
UnsetStructural(Vec<StructuralRemoval>),
}
pub mod transition_state {
pub const RETRACTED: &str = "retracted";
pub const SUPERSEDED: &str = "superseded";
pub const CORRECTED: &str = "corrected";
pub const RUNNING: &str = "running";
pub const COMPLETED: &str = "completed";
pub const FAILED: &str = "failed";
pub const CANCELLED: &str = "cancelled";
pub const ARCHIVED: &str = "archived";
pub const TOMBSTONED: &str = "tombstoned";
pub const ALL: &[&str] = &[
RETRACTED, SUPERSEDED, CORRECTED, RUNNING, COMPLETED, FAILED, CANCELLED, ARCHIVED,
TOMBSTONED,
];
pub const WITH_BY: &[&str] = &[SUPERSEDED, CORRECTED];
pub const ACTIVITY: &[&str] = &[RUNNING, COMPLETED, FAILED, CANCELLED];
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Transition {
pub target: ElementRef,
pub to: Scalar,
pub by: Option<ElementRef>,
pub set_fields: Option<Assignments>,
pub set_structural: Option<Vec<StructuralEdge>>,
pub where_clauses: Option<Vec<WhereClause>>,
pub limit: Option<Scalar>,
pub expect_versions: Vec<ExpectVersion>,
}
impl Transition {
pub fn state(&self) -> Option<&str> {
match &self.to {
Scalar::Literal(KipValue::String(state)) => Some(state.as_str()),
_ => None,
}
}
pub fn finalizes(&self) -> bool {
self.set_fields.is_some() || self.set_structural.is_some()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct SetRetention {
pub target: ElementRef,
pub values: Assignments,
pub where_clauses: Option<Vec<WhereClause>>,
pub limit: Option<Scalar>,
pub expect_versions: Vec<ExpectVersion>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct PurgeStatement {
pub target: ElementRef,
pub where_clauses: Option<Vec<WhereClause>>,
pub limit: Option<Scalar>,
pub expect_versions: Vec<ExpectVersion>,
pub reference_policy: Option<Scalar>,
pub confirm: String,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct PurgePayloadStatement {
pub target: ElementRef,
pub where_clauses: Option<Vec<WhereClause>>,
pub limit: Option<Scalar>,
pub expect_versions: Vec<ExpectVersion>,
pub confirm: String,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct MergeConcept {
pub source: ElementRef,
pub into: ElementRef,
pub where_clauses: Option<Vec<WhereClause>>,
pub expect_versions: Vec<ExpectVersion>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum MetaCommand {
Describe(DescribeTarget),
List(ListCommand),
Search(SearchCommand),
Verify {
target: VerifyTarget,
value: Scalar,
},
Validate(ValidateCommand),
Preview(PreviewCommand),
History(HistoryCommand),
Changes(ChangesCommand),
ExportCapsule(ExportCapsuleCommand),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum DescribeTarget {
Primer {
mode: Option<Scalar>,
},
Protocol,
Capabilities,
Space {
value: Option<Scalar>,
},
SchemaEnvironment {
as_of: Option<AsOf>,
},
Package(Scalar),
Type(Scalar),
Predicate(Scalar),
Facet(Scalar),
StructuralField(Scalar),
Compatibility {
from: Scalar,
to: Scalar,
},
Error(Scalar),
Transaction(Scalar),
TransactionByIdempotencyKey(Scalar),
Snapshot {
as_of: Option<AsOf>,
at_time: Option<Scalar>,
},
Capsule(Scalar),
EpistemicPolicy {
value: Option<Scalar>,
},
Trust {
value: Option<Scalar>,
},
Access {
with: Option<BoundObject>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ListCommand {
pub target: ListTarget,
pub status: Option<Scalar>,
pub element: Option<Scalar>,
pub depth: Option<Scalar>,
pub limit: Option<Scalar>,
pub cursor: Option<Scalar>,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum ListTarget {
Spaces,
SchemaPackages,
Types,
Predicates,
Facets,
StructuralFields,
EpistemicPolicies,
Dependents,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct SearchCommand {
pub target: SearchTarget,
pub term: Scalar,
pub with_type: Option<Scalar>,
pub with_predicate: Option<Scalar>,
pub mode: Option<Scalar>,
pub threshold: Option<Scalar>,
pub as_of_seq: Option<Scalar>,
pub limit: Option<Scalar>,
pub cursor: Option<Scalar>,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum SearchTarget {
Concept,
Proposition,
Assertion,
Evidence,
Activity,
Cognition,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum VerifyTarget {
Capsule,
SchemaPackage,
Receipt,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ValidateCommand {
pub target: ValidateTarget,
pub value: Scalar,
pub options: Option<BoundObject>,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum ValidateTarget {
Kql,
Kml,
Capsule,
SchemaPackage,
ImportPlan,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum PreviewCommand {
Kml(Scalar),
ImportCapsule {
capsule: Scalar,
into: Scalar,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum HistoryCommand {
Element {
value: Scalar,
from_seq: Option<Scalar>,
to_seq: Option<Scalar>,
limit: Option<Scalar>,
cursor: Option<Scalar>,
},
Space {
from_seq: Option<Scalar>,
to_seq: Option<Scalar>,
limit: Option<Scalar>,
cursor: Option<Scalar>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum ChangesCommand {
Since {
cursor: Scalar,
limit: Option<Scalar>,
},
AfterSeq {
seq: Scalar,
limit: Option<Scalar>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ExportCapsuleCommand {
pub target: ElementRef,
pub where_clauses: Vec<WhereClause>,
pub options: Option<BoundObject>,
pub as_of: Option<AsOf>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kip_value_encodes_externally_tagged() {
assert_eq!(serde_json::to_string(&KipValue::Null).unwrap(), r#""Null""#);
assert_eq!(
serde_json::to_string(&KipValue::Bool(true)).unwrap(),
r#"{"Bool":true}"#
);
assert_eq!(
serde_json::to_string(&KipValue::String("a".into())).unwrap(),
r#"{"String":"a"}"#
);
assert_eq!(
serde_json::to_string(&KipValue::Number(Number::from(3))).unwrap(),
r#"{"Number":3}"#
);
}
#[test]
fn scalar_and_refs_match_the_reference_encoding() {
assert_eq!(
serde_json::to_string(&Scalar::Param("limit".into())).unwrap(),
r#"{"Param":"limit"}"#
);
assert_eq!(
serde_json::to_string(&Scalar::Literal(KipValue::Number(Number::from(10)))).unwrap(),
r#"{"Literal":{"Number":10}}"#
);
assert_eq!(
serde_json::to_string(&SymbolRef::Name("has_step".into())).unwrap(),
r#"{"Name":"has_step"}"#
);
assert_eq!(
serde_json::to_string(&ElementRef::Id("E-1".into())).unwrap(),
r#"{"Id":"E-1"}"#
);
}
#[test]
fn unit_enums_encode_as_bare_strings() {
assert_eq!(
serde_json::to_string(&DescribeTarget::Protocol).unwrap(),
r#""Protocol""#
);
assert_eq!(
serde_json::to_string(&AggregationFunction::Count).unwrap(),
r#""Count""#
);
assert_eq!(
serde_json::to_string(&OrderDirection::Desc).unwrap(),
r#""Desc""#
);
}
#[test]
fn assignments_encode_as_ordered_pairs() {
let assignments: Assignments = vec![
(
"stance".to_string(),
MutationValue::Value(KipValue::String("support".into())),
),
("evidence".to_string(), MutationValue::Handle("e1".into())),
];
assert_eq!(
serde_json::to_string(&assignments).unwrap(),
r#"[["stance",{"Value":{"String":"support"}}],["evidence",{"Handle":"e1"}]]"#
);
}
#[test]
fn command_round_trips_through_json() {
let command = Command::Kml(KmlStatement {
explicit_transaction: true,
clauses: vec![MutationClause::EnsureProposition(EnsureProposition {
handle: Some("p".into()),
subject: Term::Param("alice".into()),
predicate: PredAtom::Literal("prefers".into()),
object: Term::Param("dark_mode".into()),
expect_versions: Vec::new(),
})],
});
let encoded = serde_json::to_string(&command).unwrap();
let decoded: Command = serde_json::from_str(&encoded).unwrap();
assert_eq!(decoded, command);
assert!(decoded.is_mutation());
}
#[test]
fn version_planes_encode_the_way_the_reference_toolkit_does() {
assert_eq!(
serde_json::to_string(&ExpectVersion::element(Scalar::Param("v".into()))).unwrap(),
r#"{"version":{"Param":"v"},"plane":null}"#
);
assert_eq!(
serde_json::to_string(&ExpectVersion {
version: Scalar::Literal(KipValue::Number(Number::from(3))),
plane: Some(VersionPlane::Attributes),
})
.unwrap(),
r#"{"version":{"Literal":{"Number":3}},"plane":"Attributes"}"#
);
assert_eq!(
serde_json::to_string(&VersionPlane::Facet(SymbolRef::Name(
"MnemonicState".into()
)))
.unwrap(),
r#"{"Facet":{"Name":"MnemonicState"}}"#
);
}
#[test]
fn a_transition_knows_its_literal_state() {
let transition = Transition {
target: ElementRef::Param("a".into()),
to: Scalar::Literal(KipValue::String("retracted".into())),
by: None,
set_fields: None,
set_structural: None,
where_clauses: None,
limit: None,
expect_versions: Vec::new(),
};
assert_eq!(transition.state(), Some("retracted"));
assert!(!transition.finalizes());
let bound = Transition {
to: Scalar::Param("state".into()),
..transition
};
assert_eq!(bound.state(), None);
assert!(transition_state::ALL.contains(&"tombstoned"));
}
#[test]
fn kip_value_rejects_nothing_finite_and_converts_to_json() {
let value = KipValue::try_from(serde_json::json!({"a": [1, "b", null]})).unwrap();
assert_eq!(Json::from(value), serde_json::json!({"a": [1, "b", null]}));
}
#[test]
fn dot_path_var_displays_both_step_kinds() {
let path = DotPathVar {
var: "x".into(),
path: vec![
PathStep::Field("facets".into()),
PathStep::Key("MnemonicState".into()),
PathStep::Field("salience".into()),
],
};
assert_eq!(path.to_string(), r#"?x.facets["MnemonicState"].salience"#);
}
}