use std::collections::BTreeMap;
use super::command_manifest::validate_command_manifest;
use super::manifest::{
hash_bytes, ManifestCommand, ManifestCommandConsistency, ManifestCommandExtensions,
ManifestCommandShape, ManifestConfirmationKind, ManifestConfirmations, ManifestConsistencyKind,
ManifestEffects, ManifestField, ManifestFilterField, ManifestFilterInput, ManifestInputDefault,
ManifestInputDefaultGenerator, ManifestInputDefaults, ManifestKeyField, ManifestModel,
ManifestNormalization, ManifestProjector, ManifestProtocolOperation,
ManifestProtocolOperations, ManifestRevalidationFallback, ManifestRowPolicy,
ManifestTrustedPresetDescriptor, ManifestTypeDef, ManifestTypeField,
};
fn field(
name: &str,
type_name: &str,
codec: Option<&str>,
nested: Option<ManifestTypeDef>,
) -> ManifestTypeField {
ManifestTypeField {
name: name.into(),
type_name: type_name.into(),
nullable: false,
list: false,
item_nullable: false,
codec: codec.map(str::to_string),
nested: nested.map(Box::new),
}
}
fn model() -> ManifestModel {
ManifestModel {
id: "Todo".into(),
typename: "Todo".into(),
source_table: "todos".into(),
dependencies: vec!["todos".into()],
normalization: ManifestNormalization::Normalized {
fields: vec![ManifestKeyField {
name: "id".into(),
codec: "string".into(),
}],
encoding: "length_prefixed_v1".into(),
},
fields: vec![
ManifestField {
name: "id".into(),
scalar: "ID".into(),
codec: "string".into(),
nullable: false,
},
ManifestField {
name: "payload".into(),
scalar: "Bytea".into(),
codec: "base64".into(),
nullable: false,
},
ManifestField {
name: "title".into(),
scalar: "String".into(),
codec: "string".into(),
nullable: false,
},
],
relationships: Vec::new(),
filter_input: ManifestFilterInput {
type_name: "todos_bool_exp".into(),
fields: ["id", "payload", "title"]
.into_iter()
.map(|name| ManifestFilterField {
name: name.into(),
operators: vec!["_eq".into()],
})
.collect(),
relationships: Vec::new(),
},
row_policy: ManifestRowPolicy::Unrestricted,
record_revisions: true,
tombstones: true,
}
}
fn codecs() -> BTreeMap<String, String> {
[
("Bytea".into(), "base64".into()),
("ID".into(), "string".into()),
("JSON".into(), "json".into()),
("String".into(), "string".into()),
]
.into_iter()
.collect()
}
fn input() -> ManifestTypeDef {
ManifestTypeDef {
name: "CreateTodoInput".into(),
fields: vec![
field("id", "ID", Some("string"), None),
field(
"metadata",
"TodoMetadataInput",
None,
Some(ManifestTypeDef {
name: "TodoMetadataInput".into(),
fields: vec![field("title", "String", Some("string"), None)],
}),
),
],
}
}
fn output() -> ManifestTypeDef {
ManifestTypeDef {
name: "CreateTodoPayload".into(),
fields: vec![
field("id", "ID", Some("string"), None),
field(
"todo",
"TodoResult",
None,
Some(ManifestTypeDef {
name: "TodoResult".into(),
fields: vec![
field("id", "ID", Some("string"), None),
field("title", "String", Some("string"), None),
],
}),
),
],
}
}
fn command() -> ManifestCommand {
let operation = "mutation Client_createTodo($commandId: ID!, $input: CreateTodoInput!) { createTodo(commandId: $commandId, input: $input) { id todo { id title } } }";
ManifestCommand {
version: 1,
name: "CreateTodo".into(),
mutation_field: "createTodo".into(),
grants: vec!["user".into()],
input: ManifestCommandShape::Object {
definition: input(),
},
output: ManifestCommandShape::Object {
definition: output(),
},
operation: operation.into(),
operation_hash: hash_bytes(operation.as_bytes()),
extensions: ManifestCommandExtensions {
version: 2,
consistency: ManifestCommandConsistency {
version: 1,
kind: ManifestConsistencyKind::Eventual,
},
direct_projection: None,
input_defaults: Some(ManifestInputDefaults {
version: 1,
defaults: vec![ManifestInputDefault {
path: vec!["id".into()],
generator: ManifestInputDefaultGenerator::UuidV7,
}],
}),
effects: None,
confirmations: None,
projection: None,
trusted_presets: Vec::new(),
},
}
}
fn projector() -> ManifestProjector {
ManifestProjector {
version: 1,
name: "todos".into(),
facts: vec!["TodoCreated".into()],
models: vec!["Todo".into()],
dependencies: vec!["todos".into()],
causal_confirmation: true,
}
}
fn protocol() -> ManifestProtocolOperations {
let operation = "query Distributed_CommandStatus($commandId: ID!) { commandStatus(commandId: $commandId) { state } }";
ManifestProtocolOperations {
version: 1,
command_status: Some(ManifestProtocolOperation {
name: "Distributed_CommandStatus".into(),
operation: operation.into(),
operation_hash: hash_bytes(operation.as_bytes()),
}),
}
}
fn validate(
command: &ManifestCommand,
) -> Result<super::command_manifest::CommandManifestValidation, super::ClientCompileError> {
validate_commands(std::slice::from_ref(command))
}
fn validate_commands(
commands: &[ManifestCommand],
) -> Result<super::command_manifest::CommandManifestValidation, super::ClientCompileError> {
validate_command_manifest(
commands,
&[("Todo".into(), model())].into_iter().collect(),
&BTreeMap::new(),
&codecs(),
&[projector()],
true,
&protocol(),
)
}
fn command_with_identity(name: &str, mutation_field: &str) -> ManifestCommand {
let mut command = command();
command.name = name.into();
command.mutation_field = mutation_field.into();
command.operation = format!(
"mutation Client_{mutation_field}($commandId: ID!, $input: CreateTodoInput!) {{ {mutation_field}(commandId: $commandId, input: $input) {{ id todo {{ id title }} }} }}"
);
command.operation_hash = hash_bytes(command.operation.as_bytes());
command
}
#[test]
fn validates_recursive_shapes_under_command_authority_v2() {
let report = validate(&command()).expect("valid typed command");
assert!(report.commands_requiring_revalidation.is_empty());
}
#[test]
fn requires_byte_exact_canonical_operation_and_recursive_codecs() {
let mut drifted = command();
drifted.operation.push(' ');
drifted.operation_hash = hash_bytes(drifted.operation.as_bytes());
let error = validate(&drifted).expect_err("whitespace drift must fail");
assert_eq!(error.code, "client.manifest.command_operation");
let mut wrong_codec = command();
let ManifestCommandShape::Object { definition } = &mut wrong_codec.input else {
unreachable!()
};
definition.fields[1].nested.as_mut().expect("nested").fields[0].codec = Some("json".into());
let error = validate(&wrong_codec).expect_err("recursive codec drift must fail");
assert_eq!(error.code, "client.manifest.command_type_codec");
}
#[test]
fn unreferenced_trusted_preset_descriptors_fail_closed() {
let mut trusted = command();
trusted
.extensions
.trusted_presets
.push(ManifestTrustedPresetDescriptor {
name: "subject".into(),
codec: "string".into(),
});
let error = validate(&trusted).expect_err("unreferenced descriptor must fail closed");
assert_eq!(error.code, "client.manifest.trusted_preset_inventory");
}
#[test]
fn legacy_command_authority_has_no_v2_decoder() {
let mut old_version = command();
old_version.extensions.version = 1;
let error = validate(&old_version).expect_err("extensions v1 must fail closed");
assert_eq!(error.code, "client.manifest.command_extensions");
let mut effects = command();
effects.extensions.effects = Some(ManifestEffects {
version: 1,
operations: Vec::new(),
fallback: ManifestRevalidationFallback::Revalidate,
});
let error = validate(&effects).expect_err("legacy effects must fail closed");
assert_eq!(error.code, "client.manifest.legacy_command_authority");
let mut confirmations = command();
confirmations.extensions.confirmations = Some(ManifestConfirmations {
version: 1,
kind: ManifestConfirmationKind::Unavailable,
expected: Vec::new(),
fallback: ManifestRevalidationFallback::Revalidate,
});
let error = validate(&confirmations).expect_err("legacy confirmations must fail closed");
assert_eq!(error.code, "client.manifest.legacy_command_authority");
}
#[test]
fn rejects_empty_outputs_and_ambiguous_command_type_namespaces() {
let mut empty = command();
empty.output = ManifestCommandShape::None;
let error = validate(&empty).expect_err("GraphQL mutation output cannot be empty");
assert_eq!(error.code, "client.manifest.command_output");
let mut overlaps_input = command();
overlaps_input.output = ManifestCommandShape::Object {
definition: input(),
};
let error = validate(&overlaps_input).expect_err("input/output type IDs cannot overlap");
assert_eq!(error.code, "client.manifest.command_type_reference");
let first = command();
let second = command_with_identity("CreateTodoAgain", "createTodoAgain");
validate_commands(&[first.clone(), second.clone()])
.expect("identical output definitions may be shared across commands");
let mut conflicting = second;
let ManifestCommandShape::Object { definition } = &mut conflicting.output else {
unreachable!()
};
definition.fields[0].nullable = true;
let error = validate_commands(&[first, conflicting])
.expect_err("cross-command type definitions must be globally unambiguous");
assert_eq!(error.code, "client.manifest.command_type_reference");
let mut model_collision = command();
let ManifestCommandShape::Object { definition } = &mut model_collision.input else {
unreachable!()
};
definition.name = "Todo".into();
let error = validate(&model_collision)
.expect_err("command input types cannot shadow an authorized model object");
assert_eq!(error.code, "client.manifest.command_type_namespace");
let mut scalar_collision = command();
let ManifestCommandShape::Object { definition } = &mut scalar_collision.input else {
unreachable!()
};
definition.name = "String".into();
let error = validate(&scalar_collision)
.expect_err("command input types cannot shadow built-in scalar types");
assert_eq!(error.code, "client.manifest.command_type_namespace");
let mut non_projected_model_output = command();
let ManifestCommandShape::Object { definition } = &mut non_projected_model_output.output else {
unreachable!()
};
definition.name = "Todo".into();
let error = validate(&non_projected_model_output)
.expect_err("only an exact Atomic<T> output may reuse its model object type");
assert_eq!(error.code, "client.manifest.command_type_namespace");
}
#[test]
fn accepts_shapes_beyond_removed_compiler_only_size_limits() {
let mut deep = command();
let mut nested = ManifestTypeDef {
name: "Deep40".into(),
fields: vec![field("value", "String", Some("string"), None)],
};
for depth in (0..40).rev() {
let type_name = format!("Deep{depth}");
let child_name = nested.name.clone();
nested = ManifestTypeDef {
name: type_name,
fields: vec![field("child", &child_name, None, Some(nested))],
};
}
let ManifestCommandShape::Object { definition } = &mut deep.input else {
unreachable!()
};
let nested_name = nested.name.clone();
definition
.fields
.push(field("zdeep", &nested_name, None, Some(nested)));
validate(&deep).expect("Surface has no 32-level command type limit");
let mut wide = command();
let ManifestCommandShape::Object { definition } = &mut wide.input else {
unreachable!()
};
let mut fields = (0..4_097)
.map(|index| field(&format!("a{index:04}"), "String", Some("string"), None))
.collect::<Vec<_>>();
fields.append(&mut definition.fields);
definition.fields = fields;
validate(&wide).expect("Surface has no 4,096-field command type limit");
}
#[test]
fn query_only_protocol_has_no_command_status_or_causal_capability() {
let report = validate_command_manifest(
&[],
&BTreeMap::new(),
&BTreeMap::new(),
&codecs(),
&[],
false,
&ManifestProtocolOperations {
version: 1,
command_status: None,
},
)
.expect("query-only contract");
assert!(report.commands_requiring_revalidation.is_empty());
}