use crate::bounded_deserialize::{
BudgetedCappedSequenceSeed, CappedSequence, RowBudget, consume_ignored_tail,
deserialize_capped_sequence,
};
use crate::{InputIdentity, SourceFormatV1};
use serde::de::{DeserializeSeed, IgnoredAny, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
use std::collections::BTreeSet;
use std::fmt;
use std::marker::PhantomData;
pub const ENGINE_PROFILE_FACTS_V1_ID: &str = "urn:animsmith:engine-profile-facts:1";
pub const RESOLVED_ENGINE_SETTINGS_V1_ID: &str = "urn:animsmith:resolved-engine-settings:1";
pub const ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS: usize = 4_096;
pub const ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS: usize = 65_536;
pub const ENGINE_CONTRACT_V1_MAX_TEXT_BYTES: usize = 4_096;
pub const ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES: usize = 8 * 1024 * 1024;
const ENGINE_FACTS_PREIMAGE_DOMAIN: &str = "animsmith-engine-facts-v1";
const ENGINE_SETTINGS_PREIMAGE_DOMAIN: &str = "animsmith-engine-settings-v1";
fn deserialize_collection_rows<'de, D, T>(deserializer: D) -> Result<CappedSequence<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
deserialize_capped_sequence(deserializer, ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
}
#[derive(Debug)]
struct ProfileRows {
local: RowBudget,
provenance: Option<RowBudget>,
}
impl ProfileRows {
fn new(provenance_limit: Option<usize>) -> Self {
Self {
local: RowBudget::new(ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS),
provenance: provenance_limit.map(RowBudget::new),
}
}
fn admit_top_level(&mut self) -> bool {
if !self.local.admit() {
return false;
}
self.provenance.as_mut().is_none_or(RowBudget::admit)
}
fn provenance_overflowed(&self) -> bool {
self.provenance.as_ref().is_some_and(RowBudget::overflowed)
}
}
#[derive(Debug)]
struct SettingsRows {
local: RowBudget,
provenance: Option<RowBudget>,
}
impl SettingsRows {
fn new(provenance_limit: Option<usize>) -> Self {
Self {
local: RowBudget::new(ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS),
provenance: provenance_limit.map(RowBudget::new),
}
}
fn admit_clip(&mut self) -> bool {
self.local.admit()
}
fn admit_setting(&mut self) -> bool {
if !self.local.admit() {
return false;
}
self.provenance.as_mut().is_none_or(RowBudget::admit)
}
fn provenance_overflowed(&self) -> bool {
self.provenance.as_ref().is_some_and(RowBudget::overflowed)
}
}
#[derive(Debug, Default)]
pub(crate) struct CanonicalEncoder(Vec<u8>);
impl CanonicalEncoder {
pub(crate) fn new(domain: &str) -> Self {
let mut encoder = Self::default();
encoder.token(domain);
encoder
}
pub(crate) fn token(&mut self, token: impl AsRef<str>) {
let bytes = token.as_ref().as_bytes();
self.0
.extend_from_slice(&(bytes.len() as u64).to_be_bytes());
self.0.extend_from_slice(bytes);
}
pub(crate) fn field(&mut self, field: &'static str) {
self.token(field);
}
pub(crate) fn count(&mut self, count: usize) {
self.token(count.to_string());
}
pub(crate) fn identity(self) -> InputIdentity {
InputIdentity::from_bytes(&self.0)
}
pub(crate) fn into_bytes(self) -> Vec<u8> {
self.0
}
}
pub(crate) fn encode_input_identity(encoder: &mut CanonicalEncoder, identity: &InputIdentity) {
encoder.token("sha256");
encoder.token(identity.sha256());
encoder.token("bytes");
encoder.token(identity.bytes().to_string());
}
impl Serialize for SourceFormatV1 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(source_format_name(*self))
}
}
impl<'de> Deserialize<'de> for SourceFormatV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match String::deserialize(deserializer)?.as_str() {
"gltf_json" => Ok(Self::GltfJson),
"glb" => Ok(Self::Glb),
"fbx" => Ok(Self::Fbx),
other => Err(D::Error::custom(format!(
"unknown V1 source format {other:?}"
))),
}
}
}
impl<'de> Deserialize<'de> for InputIdentity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WireIdentity {
sha256: String,
bytes: u64,
}
let wire = WireIdentity::deserialize(deserializer)?;
if wire.sha256.len() != 64
|| !wire
.sha256
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
return Err(D::Error::custom(
"input identity sha256 must be exactly 64 lowercase hexadecimal digits",
));
}
let mut digest = [0_u8; 32];
for (index, pair) in wire.sha256.as_bytes().as_chunks::<2>().0.iter().enumerate() {
digest[index] = (hex_nibble(pair[0]).expect("validated hexadecimal") << 4)
| hex_nibble(pair[1]).expect("validated hexadecimal");
}
Ok(InputIdentity::from_sha256_digest(digest, wire.bytes))
}
}
fn hex_nibble(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EngineProfileSelectionV1 {
family: String,
profile_revision: u32,
engine_version: String,
importer: String,
}
impl EngineProfileSelectionV1 {
pub fn new(
family: impl Into<String>,
profile_revision: u32,
engine_version: impl Into<String>,
importer: impl Into<String>,
) -> Result<Self, EngineContractError> {
let selection = Self {
family: family.into(),
profile_revision,
engine_version: engine_version.into(),
importer: importer.into(),
};
selection.validate()?;
Ok(selection)
}
pub fn family(&self) -> &str {
&self.family
}
pub const fn profile_revision(&self) -> u32 {
self.profile_revision
}
pub fn engine_version(&self) -> &str {
&self.engine_version
}
pub fn importer(&self) -> &str {
&self.importer
}
fn validate(&self) -> Result<(), EngineContractError> {
validate_required_text("selection.family", &self.family)?;
validate_required_text("selection.engine_version", &self.engine_version)?;
validate_required_text("selection.importer", &self.importer)
}
fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
checked_sum(
"profile retained text",
[
self.family.len(),
self.engine_version.len(),
self.importer.len(),
],
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineFactIdV1 {
AcceptedInputs,
AnimationAddressability,
TargetCoordinateBasis,
TargetLinearUnit,
UnitConversionControl,
AxisConversionControl,
ExactAxisConversion,
ResultingHierarchyScale,
WholeEndFrameRequired,
AnimationChannelHandling,
ExtensionHandling,
ConstructHandling,
AnimationTargetAddressability,
RootMotionAddressability,
}
impl EngineFactIdV1 {
pub const fn as_str(self) -> &'static str {
match self {
Self::AcceptedInputs => "accepted_inputs",
Self::AnimationAddressability => "animation_addressability",
Self::TargetCoordinateBasis => "target_coordinate_basis",
Self::TargetLinearUnit => "target_linear_unit",
Self::UnitConversionControl => "unit_conversion_control",
Self::AxisConversionControl => "axis_conversion_control",
Self::ExactAxisConversion => "exact_axis_conversion",
Self::ResultingHierarchyScale => "resulting_hierarchy_scale",
Self::WholeEndFrameRequired => "whole_end_frame_required",
Self::AnimationChannelHandling => "animation_channel_handling",
Self::ExtensionHandling => "extension_handling",
Self::ConstructHandling => "construct_handling",
Self::AnimationTargetAddressability => "animation_target_addressability",
Self::RootMotionAddressability => "root_motion_addressability",
}
}
}
const ALL_FACT_IDS: [EngineFactIdV1; 14] = [
EngineFactIdV1::AcceptedInputs,
EngineFactIdV1::AnimationAddressability,
EngineFactIdV1::AnimationChannelHandling,
EngineFactIdV1::AnimationTargetAddressability,
EngineFactIdV1::AxisConversionControl,
EngineFactIdV1::ConstructHandling,
EngineFactIdV1::ExactAxisConversion,
EngineFactIdV1::ExtensionHandling,
EngineFactIdV1::ResultingHierarchyScale,
EngineFactIdV1::RootMotionAddressability,
EngineFactIdV1::TargetCoordinateBasis,
EngineFactIdV1::TargetLinearUnit,
EngineFactIdV1::UnitConversionControl,
EngineFactIdV1::WholeEndFrameRequired,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineHandednessV1 {
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineUpAxisV1 {
X,
Y,
Z,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineForwardAxisV1 {
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EngineCoordinateBasisV1 {
pub handedness: EngineHandednessV1,
pub up_axis: EngineUpAxisV1,
pub forward_axis: EngineForwardAxisV1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineLinearUnitV1 {
Metre,
Centimetre,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineSettingIdV1 {
ConvertUnits,
BakeAxisConversion,
RootMotionSource,
RootRotation,
RootPositionY,
RootPositionXz,
}
impl EngineSettingIdV1 {
pub const fn as_str(self) -> &'static str {
match self {
Self::ConvertUnits => "convert_units",
Self::BakeAxisConversion => "bake_axis_conversion",
Self::RootMotionSource => "root_motion_source",
Self::RootRotation => "root_rotation",
Self::RootPositionY => "root_position_y",
Self::RootPositionXz => "root_position_xz",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineConversionControlV1 {
ProfileSetting(EngineSettingIdV1),
ImporterOption,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineImportHandlingV1 {
Preserved,
Converted,
Discarded,
Unsupported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineTargetAddressabilityV1 {
NamePathDerivedId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineAnimationAddressabilityV1 {
GltfAssetLabel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineRootMotionAddressabilityV1 {
ExactSourceTransformPath,
HumanoidAvatarBody,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineFactValueV1 {
AcceptedFormats(Vec<SourceFormatV1>),
AnimationAddressability(EngineAnimationAddressabilityV1),
CoordinateBasis(EngineCoordinateBasisV1),
LinearUnit(EngineLinearUnitV1),
ConversionControl(EngineConversionControlV1),
Boolean(bool),
ImportHandling(EngineImportHandlingV1),
TargetAddressability(EngineTargetAddressabilityV1),
RootMotionAddressability(EngineRootMotionAddressabilityV1),
}
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
enum EngineFactValueWireV1 {
AcceptedFormats(
#[serde(deserialize_with = "deserialize_collection_rows")] CappedSequence<SourceFormatV1>,
),
AnimationAddressability(EngineAnimationAddressabilityV1),
CoordinateBasis(EngineCoordinateBasisV1),
LinearUnit(EngineLinearUnitV1),
ConversionControl(EngineConversionControlV1),
Boolean(bool),
ImportHandling(EngineImportHandlingV1),
TargetAddressability(EngineTargetAddressabilityV1),
RootMotionAddressability(EngineRootMotionAddressabilityV1),
}
impl TryFrom<EngineFactValueWireV1> for EngineFactValueV1 {
type Error = EngineContractError;
fn try_from(wire: EngineFactValueWireV1) -> Result<Self, Self::Error> {
Ok(match wire {
EngineFactValueWireV1::AcceptedFormats(formats) => {
if formats.overflowed {
return Err(EngineContractError::InvalidAcceptedInputs);
}
Self::AcceptedFormats(formats.values)
}
EngineFactValueWireV1::AnimationAddressability(value) => {
Self::AnimationAddressability(value)
}
EngineFactValueWireV1::CoordinateBasis(value) => Self::CoordinateBasis(value),
EngineFactValueWireV1::LinearUnit(value) => Self::LinearUnit(value),
EngineFactValueWireV1::ConversionControl(value) => Self::ConversionControl(value),
EngineFactValueWireV1::Boolean(value) => Self::Boolean(value),
EngineFactValueWireV1::ImportHandling(value) => Self::ImportHandling(value),
EngineFactValueWireV1::TargetAddressability(value) => Self::TargetAddressability(value),
EngineFactValueWireV1::RootMotionAddressability(value) => {
Self::RootMotionAddressability(value)
}
})
}
}
impl<'de> Deserialize<'de> for EngineFactValueV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
EngineFactValueWireV1::deserialize(deserializer)?
.try_into()
.map_err(D::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineFactStateV1 {
Known(EngineFactValueV1),
Unknown,
NotApplicable,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EngineProfileFactV1 {
id: EngineFactIdV1,
state: EngineFactStateV1,
}
impl EngineProfileFactV1 {
pub const fn new(id: EngineFactIdV1, state: EngineFactStateV1) -> Self {
Self { id, state }
}
pub const fn id(&self) -> EngineFactIdV1 {
self.id
}
pub const fn state(&self) -> &EngineFactStateV1 {
&self.state
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineSettingScopeV1 {
Document,
Clip,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineSettingDomainV1 {
Boolean,
BakeOrExtract,
SourceTransformPath,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineSettingApplicabilityV1 {
Applicable,
NotApplicable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineDefaultStatusV1 {
RequiredWithoutDefault,
NotApplicable,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EngineSettingDescriptorV1 {
id: EngineSettingIdV1,
scope: EngineSettingScopeV1,
domain: EngineSettingDomainV1,
applicability: EngineSettingApplicabilityV1,
default_status: EngineDefaultStatusV1,
}
impl EngineSettingDescriptorV1 {
pub const fn new(
id: EngineSettingIdV1,
scope: EngineSettingScopeV1,
domain: EngineSettingDomainV1,
applicability: EngineSettingApplicabilityV1,
default_status: EngineDefaultStatusV1,
) -> Self {
Self {
id,
scope,
domain,
applicability,
default_status,
}
}
pub const fn id(&self) -> EngineSettingIdV1 {
self.id
}
pub const fn scope(&self) -> EngineSettingScopeV1 {
self.scope
}
pub const fn domain(&self) -> EngineSettingDomainV1 {
self.domain
}
pub const fn applicability(&self) -> EngineSettingApplicabilityV1 {
self.applicability
}
pub const fn default_status(&self) -> EngineDefaultStatusV1 {
self.default_status
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EnginePrimarySourceV1 {
id: String,
target_version: String,
url: String,
verified_on: String,
supported_fact_ids: Vec<EngineFactIdV1>,
supported_setting_ids: Vec<EngineSettingIdV1>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct EnginePrimarySourceWireV1 {
id: String,
target_version: String,
url: String,
verified_on: String,
#[serde(deserialize_with = "deserialize_collection_rows")]
supported_fact_ids: CappedSequence<EngineFactIdV1>,
#[serde(deserialize_with = "deserialize_collection_rows")]
supported_setting_ids: CappedSequence<EngineSettingIdV1>,
}
struct EnginePrimarySourceSeed<'a> {
rows: &'a mut ProfileRows,
}
impl<'de> DeserializeSeed<'de> for EnginePrimarySourceSeed<'_> {
type Value = EnginePrimarySourceWireV1;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Id,
TargetVersion,
Url,
VerifiedOn,
SupportedFactIds,
SupportedSettingIds,
}
struct PrimarySourceVisitor<'a> {
rows: &'a mut ProfileRows,
}
impl<'de> Visitor<'de> for PrimarySourceVisitor<'_> {
type Value = EnginePrimarySourceWireV1;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an engine primary-source record")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut id = None;
let mut target_version = None;
let mut url = None;
let mut verified_on = None;
let mut supported_fact_ids = None;
let mut supported_setting_ids = None;
while let Some(field) = map.next_key()? {
match field {
Field::Id => set_once(&mut id, map.next_value()?, "id")?,
Field::TargetVersion => {
set_once(&mut target_version, map.next_value()?, "target_version")?
}
Field::Url => set_once(&mut url, map.next_value()?, "url")?,
Field::VerifiedOn => {
set_once(&mut verified_on, map.next_value()?, "verified_on")?
}
Field::SupportedFactIds => {
if supported_fact_ids.is_some() {
return Err(A::Error::duplicate_field("supported_fact_ids"));
}
supported_fact_ids =
Some(map.next_value_seed(BudgetedCappedSequenceSeed {
budget: &mut self.rows.local,
local_limit: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
element: PhantomData,
})?);
}
Field::SupportedSettingIds => {
if supported_setting_ids.is_some() {
return Err(A::Error::duplicate_field("supported_setting_ids"));
}
supported_setting_ids =
Some(map.next_value_seed(BudgetedCappedSequenceSeed {
budget: &mut self.rows.local,
local_limit: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
element: PhantomData,
})?);
}
}
}
Ok(EnginePrimarySourceWireV1 {
id: required(id, "id")?,
target_version: required(target_version, "target_version")?,
url: required(url, "url")?,
verified_on: required(verified_on, "verified_on")?,
supported_fact_ids: required(supported_fact_ids, "supported_fact_ids")?,
supported_setting_ids: required(
supported_setting_ids,
"supported_setting_ids",
)?,
})
}
}
deserializer.deserialize_struct(
"EnginePrimarySourceV1",
&[
"id",
"target_version",
"url",
"verified_on",
"supported_fact_ids",
"supported_setting_ids",
],
PrimarySourceVisitor { rows: self.rows },
)
}
}
fn set_once<E, T>(slot: &mut Option<T>, value: T, field: &'static str) -> Result<(), E>
where
E: serde::de::Error,
{
if slot.replace(value).is_some() {
return Err(E::duplicate_field(field));
}
Ok(())
}
fn required<E, T>(value: Option<T>, field: &'static str) -> Result<T, E>
where
E: serde::de::Error,
{
value.ok_or_else(|| E::missing_field(field))
}
impl EnginePrimarySourceV1 {
fn from_wire(wire: EnginePrimarySourceWireV1) -> Result<Self, EngineContractError> {
validate_required_text("primary_sources.id", &wire.id)?;
validate_required_text("primary_sources.target_version", &wire.target_version)?;
validate_required_text("primary_sources.url", &wire.url)?;
validate_required_text("primary_sources.verified_on", &wire.verified_on)?;
if wire.supported_fact_ids.overflowed {
return Err(EngineContractError::TooManyRows {
field: "primary_sources.supported_fact_ids",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
if wire.supported_setting_ids.overflowed {
return Err(EngineContractError::TooManyRows {
field: "primary_sources.supported_setting_ids",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
let source = Self {
id: wire.id,
target_version: wire.target_version,
url: wire.url,
verified_on: wire.verified_on,
supported_fact_ids: wire.supported_fact_ids.values,
supported_setting_ids: wire.supported_setting_ids.values,
};
source.validate(true)?;
Ok(source)
}
}
impl<'de> Deserialize<'de> for EnginePrimarySourceV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::from_wire(EnginePrimarySourceWireV1::deserialize(deserializer)?)
.map_err(D::Error::custom)
}
}
impl EnginePrimarySourceV1 {
pub fn new(
id: impl Into<String>,
target_version: impl Into<String>,
url: impl Into<String>,
verified_on: impl Into<String>,
mut supported_fact_ids: Vec<EngineFactIdV1>,
mut supported_setting_ids: Vec<EngineSettingIdV1>,
) -> Result<Self, EngineContractError> {
supported_fact_ids.sort_by_key(|id| id.as_str());
supported_setting_ids.sort_by_key(|id| id.as_str());
let source = Self {
id: id.into(),
target_version: target_version.into(),
url: url.into(),
verified_on: verified_on.into(),
supported_fact_ids,
supported_setting_ids,
};
source.validate(true)?;
Ok(source)
}
pub fn id(&self) -> &str {
&self.id
}
pub fn target_version(&self) -> &str {
&self.target_version
}
pub fn url(&self) -> &str {
&self.url
}
pub fn verified_on(&self) -> &str {
&self.verified_on
}
pub fn supported_fact_ids(&self) -> &[EngineFactIdV1] {
&self.supported_fact_ids
}
pub fn supported_setting_ids(&self) -> &[EngineSettingIdV1] {
&self.supported_setting_ids
}
fn validate(&self, require_order: bool) -> Result<(), EngineContractError> {
validate_required_text("primary_sources.id", &self.id)?;
validate_required_text("primary_sources.target_version", &self.target_version)?;
validate_required_text("primary_sources.url", &self.url)?;
validate_required_text("primary_sources.verified_on", &self.verified_on)?;
validate_collection_len(
"primary_sources.supported_fact_ids",
self.supported_fact_ids.len(),
)?;
validate_collection_len(
"primary_sources.supported_setting_ids",
self.supported_setting_ids.len(),
)?;
validate_unique_order(
"primary_sources.supported_fact_ids",
&self.supported_fact_ids,
|id| id.as_str(),
require_order,
)?;
validate_unique_order(
"primary_sources.supported_setting_ids",
&self.supported_setting_ids,
|id| id.as_str(),
require_order,
)
}
fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
checked_sum(
"profile retained text",
[
self.id.len(),
self.target_version.len(),
self.url.len(),
self.verified_on.len(),
],
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ResolvedEngineProfileV1 {
schema: String,
selection: EngineProfileSelectionV1,
fact_bundle_urn: String,
identity: InputIdentity,
facts: Vec<EngineProfileFactV1>,
setting_descriptors: Vec<EngineSettingDescriptorV1>,
primary_sources: Vec<EnginePrimarySourceV1>,
}
impl ResolvedEngineProfileV1 {
pub fn new(
selection: EngineProfileSelectionV1,
fact_bundle_urn: impl Into<String>,
mut facts: Vec<EngineProfileFactV1>,
mut setting_descriptors: Vec<EngineSettingDescriptorV1>,
mut primary_sources: Vec<EnginePrimarySourceV1>,
) -> Result<Self, EngineContractError> {
for fact in &mut facts {
if let EngineFactStateV1::Known(EngineFactValueV1::AcceptedFormats(formats)) =
&mut fact.state
{
formats.sort_by_key(|format| source_format_name(*format));
}
}
facts.sort_by_key(|fact| fact.id.as_str());
setting_descriptors.sort_by_key(|descriptor| descriptor.id.as_str());
primary_sources.sort_by(|left, right| left.id.cmp(&right.id));
let mut profile = Self {
schema: ENGINE_PROFILE_FACTS_V1_ID.to_owned(),
selection,
fact_bundle_urn: fact_bundle_urn.into(),
identity: InputIdentity::from_bytes(&[]),
facts,
setting_descriptors,
primary_sources,
};
profile.validate_semantics(true, false)?;
profile.identity = profile.computed_identity();
Ok(profile)
}
pub fn contract_id(&self) -> &str {
&self.schema
}
pub const fn selection(&self) -> &EngineProfileSelectionV1 {
&self.selection
}
pub fn fact_bundle_urn(&self) -> &str {
&self.fact_bundle_urn
}
pub const fn facts_identity(&self) -> &InputIdentity {
&self.identity
}
pub fn facts(&self) -> &[EngineProfileFactV1] {
&self.facts
}
pub fn setting_descriptors(&self) -> &[EngineSettingDescriptorV1] {
&self.setting_descriptors
}
pub fn primary_sources(&self) -> &[EnginePrimarySourceV1] {
&self.primary_sources
}
pub fn fact(&self, id: EngineFactIdV1) -> Option<&EngineProfileFactV1> {
self.facts.iter().find(|fact| fact.id == id)
}
pub fn accepts_format(&self, format: SourceFormatV1) -> bool {
matches!(
self.fact(EngineFactIdV1::AcceptedInputs)
.map(EngineProfileFactV1::state),
Some(EngineFactStateV1::Known(EngineFactValueV1::AcceptedFormats(formats)))
if formats.contains(&format)
)
}
pub fn setting_descriptor(&self, id: EngineSettingIdV1) -> Option<&EngineSettingDescriptorV1> {
self.setting_descriptors
.iter()
.find(|descriptor| descriptor.id == id)
}
pub fn source(&self, id: &str) -> Option<&EnginePrimarySourceV1> {
self.primary_sources.iter().find(|source| source.id == id)
}
pub fn validate(&self) -> Result<(), EngineContractError> {
self.validate_semantics(true, true)
}
pub(crate) fn encode_preimage(&self, encoder: &mut CanonicalEncoder) {
encoder.token(ENGINE_FACTS_PREIMAGE_DOMAIN);
encode_profile_key(encoder, &self.selection);
encoder.field("fact_bundle_urn");
encoder.token(&self.fact_bundle_urn);
encoder.field("facts");
encoder.count(self.facts.len());
for fact in &self.facts {
encoder.token(fact.id.as_str());
encode_fact_state(encoder, &fact.state);
}
encoder.field("setting_descriptors");
encoder.count(self.setting_descriptors.len());
for descriptor in &self.setting_descriptors {
encoder.token(descriptor.id.as_str());
encoder.token(setting_scope_name(descriptor.scope));
encoder.token(setting_domain_name(descriptor.domain));
encoder.token(match descriptor.applicability {
EngineSettingApplicabilityV1::Applicable => "applicable",
EngineSettingApplicabilityV1::NotApplicable => "not_applicable",
});
encoder.token(match descriptor.default_status {
EngineDefaultStatusV1::RequiredWithoutDefault => "required_without_default",
EngineDefaultStatusV1::NotApplicable => "not_applicable",
});
}
encoder.field("sources");
encoder.count(self.primary_sources.len());
for source in &self.primary_sources {
encoder.token(&source.id);
encoder.token(&source.target_version);
encoder.token(&source.url);
encoder.token(&source.verified_on);
encoder.count(source.supported_fact_ids.len());
for id in &source.supported_fact_ids {
encoder.token(id.as_str());
}
encoder.count(source.supported_setting_ids.len());
for id in &source.supported_setting_ids {
encoder.token(id.as_str());
}
}
}
pub(crate) fn retained_rows(&self) -> Result<usize, EngineContractError> {
let nested = self.primary_sources.iter().map(|source| {
source
.supported_fact_ids
.len()
.checked_add(source.supported_setting_ids.len())
.ok_or(EngineContractError::ArithmeticOverflow {
field: "profile retained rows",
})
});
checked_sum_results(
"profile retained rows",
[
self.facts.len(),
self.setting_descriptors.len(),
self.primary_sources.len(),
],
nested,
)
}
pub(crate) fn provenance_rows(&self) -> usize {
self.facts
.len()
.saturating_add(self.setting_descriptors.len())
.saturating_add(self.primary_sources.len())
}
pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
let base = self
.selection
.retained_text_bytes()?
.checked_add(self.fact_bundle_urn.len())
.ok_or(EngineContractError::ArithmeticOverflow {
field: "profile retained text",
})?;
checked_sum_results(
"profile retained text",
[base],
self.primary_sources
.iter()
.map(EnginePrimarySourceV1::retained_text_bytes),
)
}
fn computed_identity(&self) -> InputIdentity {
let mut encoder = CanonicalEncoder::default();
self.encode_preimage(&mut encoder);
encoder.identity()
}
fn validate_semantics(
&self,
require_order: bool,
verify_identity: bool,
) -> Result<(), EngineContractError> {
validate_schema("profile.schema", &self.schema, ENGINE_PROFILE_FACTS_V1_ID)?;
self.selection.validate()?;
validate_required_text("profile.fact_bundle_urn", &self.fact_bundle_urn)?;
validate_collection_len("profile.facts", self.facts.len())?;
validate_collection_len(
"profile.setting_descriptors",
self.setting_descriptors.len(),
)?;
validate_collection_len("profile.primary_sources", self.primary_sources.len())?;
validate_unique_order(
"profile.facts",
&self.facts,
|fact| fact.id.as_str(),
require_order,
)?;
if self.facts.len() != ALL_FACT_IDS.len()
|| !self
.facts
.iter()
.zip(ALL_FACT_IDS)
.all(|(fact, expected)| fact.id == expected)
{
return Err(EngineContractError::InvalidFactInventory);
}
for fact in &self.facts {
validate_fact_value(fact)?;
if let EngineFactStateV1::Known(EngineFactValueV1::ConversionControl(
EngineConversionControlV1::ProfileSetting(setting),
)) = &fact.state
&& self.setting_descriptor(*setting).is_none()
{
return Err(EngineContractError::InvalidFactValue { fact: fact.id });
}
}
if !matches!(
self.fact(EngineFactIdV1::AcceptedInputs)
.map(EngineProfileFactV1::state),
Some(EngineFactStateV1::Known(
EngineFactValueV1::AcceptedFormats(formats)
)) if !formats.is_empty()
) {
return Err(EngineContractError::InvalidAcceptedInputs);
}
validate_unique_order(
"profile.setting_descriptors",
&self.setting_descriptors,
|descriptor| descriptor.id.as_str(),
require_order,
)?;
for descriptor in &self.setting_descriptors {
if !matches!(
(descriptor.applicability, descriptor.default_status),
(
EngineSettingApplicabilityV1::Applicable,
EngineDefaultStatusV1::RequiredWithoutDefault
) | (
EngineSettingApplicabilityV1::NotApplicable,
EngineDefaultStatusV1::NotApplicable
)
) {
return Err(EngineContractError::InvalidDescriptorDefault {
setting: descriptor.id,
});
}
}
validate_unique_order(
"profile.primary_sources",
&self.primary_sources,
|source| source.id.as_str(),
require_order,
)?;
for source in &self.primary_sources {
source.validate(require_order)?;
for fact in &source.supported_fact_ids {
let Some(row) = self.fact(*fact) else {
return Err(EngineContractError::UnknownSourceFact {
source_id: source.id.clone(),
fact: *fact,
});
};
if !matches!(row.state, EngineFactStateV1::Known(_)) {
return Err(EngineContractError::SourceReferencesNonKnownFact {
source_id: source.id.clone(),
fact: *fact,
});
}
}
for setting in &source.supported_setting_ids {
if self.setting_descriptor(*setting).is_none() {
return Err(EngineContractError::UnknownSourceSetting {
source_id: source.id.clone(),
setting: *setting,
});
}
}
}
for fact in &self.facts {
if matches!(fact.state, EngineFactStateV1::Known(_))
&& !self
.primary_sources
.iter()
.any(|source| source.supported_fact_ids.contains(&fact.id))
{
return Err(EngineContractError::UnreferencedKnownFact { fact: fact.id });
}
}
for descriptor in &self.setting_descriptors {
if !self
.primary_sources
.iter()
.any(|source| source.supported_setting_ids.contains(&descriptor.id))
{
return Err(EngineContractError::UnreferencedSetting {
setting: descriptor.id,
});
}
}
let rows = self.retained_rows()?;
if rows > ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS {
return Err(EngineContractError::TooManyAggregateRows {
found: rows,
max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
});
}
let text = self.retained_text_bytes()?;
if text > ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES {
return Err(EngineContractError::TooMuchAggregateText {
found: text,
max: ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES,
});
}
if verify_identity && self.identity != self.computed_identity() {
return Err(EngineContractError::IdentityMismatch {
contract: ENGINE_PROFILE_FACTS_V1_ID,
});
}
Ok(())
}
}
struct ResolvedEngineProfileWireV1 {
schema: String,
selection: EngineProfileSelectionV1,
fact_bundle_urn: String,
identity: InputIdentity,
facts: CappedSequence<EngineProfileFactV1>,
setting_descriptors: CappedSequence<EngineSettingDescriptorV1>,
primary_sources: CappedSequence<EnginePrimarySourceWireV1>,
aggregate_rows: RowBudget,
provenance_rows_overflowed: bool,
}
enum ProfileTopLevelElement<T> {
Value(T),
Skipped,
}
struct ProfileTopLevelElementSeed<'a, T> {
rows: &'a mut ProfileRows,
element: PhantomData<fn() -> T>,
}
impl<'de, T> DeserializeSeed<'de> for ProfileTopLevelElementSeed<'_, T>
where
T: Deserialize<'de>,
{
type Value = ProfileTopLevelElement<T>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
if self.rows.admit_top_level() {
T::deserialize(deserializer).map(ProfileTopLevelElement::Value)
} else {
IgnoredAny::deserialize(deserializer).map(|_| ProfileTopLevelElement::Skipped)
}
}
}
struct ProfileTopLevelSequenceSeed<'a, T> {
rows: &'a mut ProfileRows,
element: PhantomData<fn() -> T>,
}
impl<'de, T> DeserializeSeed<'de> for ProfileTopLevelSequenceSeed<'_, T>
where
T: Deserialize<'de>,
{
type Value = CappedSequence<T>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ProfileTopLevelSequenceVisitor<'a, T> {
rows: &'a mut ProfileRows,
element: PhantomData<fn() -> T>,
}
impl<'de, T> Visitor<'de> for ProfileTopLevelSequenceVisitor<'_, T>
where
T: Deserialize<'de>,
{
type Value = CappedSequence<T>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a bounded sequence of engine profile rows")
}
fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Vec::with_capacity(
sequence
.size_hint()
.unwrap_or(0)
.min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
);
let mut seen = 0usize;
while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
let Some(element) = sequence.next_element_seed(ProfileTopLevelElementSeed {
rows: self.rows,
element: PhantomData,
})?
else {
return Ok(CappedSequence {
values,
overflowed: false,
});
};
seen += 1;
match element {
ProfileTopLevelElement::Value(value) => values.push(value),
ProfileTopLevelElement::Skipped => {
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
return Ok(CappedSequence { values, overflowed });
}
}
}
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
Ok(CappedSequence { values, overflowed })
}
}
deserializer.deserialize_seq(ProfileTopLevelSequenceVisitor {
rows: self.rows,
element: PhantomData,
})
}
}
enum PrimarySourceElement {
Value(EnginePrimarySourceWireV1),
Skipped,
}
struct PrimarySourceElementSeed<'a> {
rows: &'a mut ProfileRows,
}
impl<'de> DeserializeSeed<'de> for PrimarySourceElementSeed<'_> {
type Value = PrimarySourceElement;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
if self.rows.admit_top_level() {
EnginePrimarySourceSeed { rows: self.rows }
.deserialize(deserializer)
.map(PrimarySourceElement::Value)
} else {
IgnoredAny::deserialize(deserializer).map(|_| PrimarySourceElement::Skipped)
}
}
}
struct PrimarySourcesSeed<'a> {
rows: &'a mut ProfileRows,
}
impl<'de> DeserializeSeed<'de> for PrimarySourcesSeed<'_> {
type Value = CappedSequence<EnginePrimarySourceWireV1>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct PrimarySourcesVisitor<'a> {
rows: &'a mut ProfileRows,
}
impl<'de> Visitor<'de> for PrimarySourcesVisitor<'_> {
type Value = CappedSequence<EnginePrimarySourceWireV1>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a bounded sequence of engine primary sources")
}
fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Vec::with_capacity(
sequence
.size_hint()
.unwrap_or(0)
.min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
);
let mut seen = 0usize;
while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
let Some(element) =
sequence.next_element_seed(PrimarySourceElementSeed { rows: self.rows })?
else {
return Ok(CappedSequence {
values,
overflowed: false,
});
};
seen += 1;
match element {
PrimarySourceElement::Value(value) => values.push(value),
PrimarySourceElement::Skipped => {
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
return Ok(CappedSequence { values, overflowed });
}
}
}
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
Ok(CappedSequence { values, overflowed })
}
}
deserializer.deserialize_seq(PrimarySourcesVisitor { rows: self.rows })
}
}
struct ResolvedEngineProfileWireSeed {
provenance_limit: Option<usize>,
}
impl<'de> DeserializeSeed<'de> for ResolvedEngineProfileWireSeed {
type Value = ResolvedEngineProfileWireV1;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Schema,
Selection,
FactBundleUrn,
Identity,
Facts,
SettingDescriptors,
PrimarySources,
}
struct ProfileVisitor {
provenance_limit: Option<usize>,
}
impl<'de> Visitor<'de> for ProfileVisitor {
type Value = ResolvedEngineProfileWireV1;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a resolved engine profile")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut rows = ProfileRows::new(self.provenance_limit);
let mut schema = None;
let mut selection = None;
let mut fact_bundle_urn = None;
let mut identity = None;
let mut facts = None;
let mut setting_descriptors = None;
let mut primary_sources = None;
while let Some(field) = map.next_key()? {
match field {
Field::Schema => set_once(&mut schema, map.next_value()?, "schema")?,
Field::Selection => {
set_once(&mut selection, map.next_value()?, "selection")?
}
Field::FactBundleUrn => {
set_once(&mut fact_bundle_urn, map.next_value()?, "fact_bundle_urn")?
}
Field::Identity => set_once(&mut identity, map.next_value()?, "identity")?,
Field::Facts => {
if facts.is_some() {
return Err(A::Error::duplicate_field("facts"));
}
facts = Some(map.next_value_seed(ProfileTopLevelSequenceSeed {
rows: &mut rows,
element: PhantomData,
})?);
}
Field::SettingDescriptors => {
if setting_descriptors.is_some() {
return Err(A::Error::duplicate_field("setting_descriptors"));
}
setting_descriptors =
Some(map.next_value_seed(ProfileTopLevelSequenceSeed {
rows: &mut rows,
element: PhantomData,
})?);
}
Field::PrimarySources => {
if primary_sources.is_some() {
return Err(A::Error::duplicate_field("primary_sources"));
}
primary_sources =
Some(map.next_value_seed(PrimarySourcesSeed { rows: &mut rows })?);
}
}
}
Ok(ResolvedEngineProfileWireV1 {
schema: required(schema, "schema")?,
selection: required(selection, "selection")?,
fact_bundle_urn: required(fact_bundle_urn, "fact_bundle_urn")?,
identity: required(identity, "identity")?,
facts: required(facts, "facts")?,
setting_descriptors: required(setting_descriptors, "setting_descriptors")?,
primary_sources: required(primary_sources, "primary_sources")?,
provenance_rows_overflowed: rows.provenance_overflowed(),
aggregate_rows: rows.local,
})
}
}
deserializer.deserialize_struct(
"ResolvedEngineProfileV1",
&[
"schema",
"selection",
"fact_bundle_urn",
"identity",
"facts",
"setting_descriptors",
"primary_sources",
],
ProfileVisitor {
provenance_limit: self.provenance_limit,
},
)
}
}
impl<'de> Deserialize<'de> for ResolvedEngineProfileWireV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
ResolvedEngineProfileWireSeed {
provenance_limit: None,
}
.deserialize(deserializer)
}
}
#[derive(Debug)]
pub(crate) enum EngineContractDecodeError {
Shape(serde_json::Error),
Semantic(EngineContractError),
}
impl ResolvedEngineProfileV1 {
fn validate_wire_limits(wire: &ResolvedEngineProfileWireV1) -> Result<(), EngineContractError> {
validate_schema("profile.schema", &wire.schema, ENGINE_PROFILE_FACTS_V1_ID)?;
wire.selection.validate()?;
validate_required_text("profile.fact_bundle_urn", &wire.fact_bundle_urn)?;
for (field, overflowed) in [
("profile.facts", wire.facts.overflowed),
(
"profile.setting_descriptors",
wire.setting_descriptors.overflowed,
),
("profile.primary_sources", wire.primary_sources.overflowed),
] {
if overflowed {
return Err(EngineContractError::TooManyRows {
field,
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
}
for source in &wire.primary_sources.values {
if source.supported_fact_ids.overflowed {
return Err(EngineContractError::TooManyRows {
field: "primary_sources.supported_fact_ids",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
if source.supported_setting_ids.overflowed {
return Err(EngineContractError::TooManyRows {
field: "primary_sources.supported_setting_ids",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
}
if wire.aggregate_rows.overflowed() {
return Err(EngineContractError::TooManyAggregateRows {
found: wire.aggregate_rows.found(),
max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
});
}
Ok(())
}
fn from_wire(wire: ResolvedEngineProfileWireV1) -> Result<Self, EngineContractError> {
Self::validate_wire_limits(&wire)?;
let primary_sources = wire
.primary_sources
.values
.into_iter()
.map(EnginePrimarySourceV1::from_wire)
.collect::<Result<Vec<_>, _>>()?;
let profile = Self {
schema: wire.schema,
selection: wire.selection,
fact_bundle_urn: wire.fact_bundle_urn,
identity: wire.identity,
facts: wire.facts.values,
setting_descriptors: wire.setting_descriptors.values,
primary_sources,
};
profile.validate()?;
Ok(profile)
}
}
#[cfg(test)]
pub(crate) fn decode_resolved_engine_profile_v1(
raw: &str,
) -> Result<ResolvedEngineProfileV1, EngineContractDecodeError> {
let wire = serde_json::from_str(raw).map_err(|source| {
if source
.to_string()
.starts_with(&EngineContractError::InvalidAcceptedInputs.to_string())
{
EngineContractDecodeError::Semantic(EngineContractError::InvalidAcceptedInputs)
} else {
EngineContractDecodeError::Shape(source)
}
})?;
ResolvedEngineProfileV1::from_wire(wire).map_err(EngineContractDecodeError::Semantic)
}
pub(crate) enum EngineProfileLimitedDecodeError {
Contract(EngineContractDecodeError),
ProvenanceRowsOverflow,
}
pub(crate) fn decode_resolved_engine_profile_v1_with_provenance_limit(
raw: &str,
provenance_limit: usize,
) -> Result<ResolvedEngineProfileV1, EngineProfileLimitedDecodeError> {
let mut deserializer = serde_json::Deserializer::from_str(raw);
let wire = ResolvedEngineProfileWireSeed {
provenance_limit: Some(provenance_limit),
}
.deserialize(&mut deserializer)
.map_err(|source| {
if source
.to_string()
.starts_with(&EngineContractError::InvalidAcceptedInputs.to_string())
{
EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(
EngineContractError::InvalidAcceptedInputs,
))
} else {
EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
}
})?;
deserializer.end().map_err(|source| {
EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
})?;
ResolvedEngineProfileV1::validate_wire_limits(&wire).map_err(|source| {
EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
})?;
if wire.provenance_rows_overflowed {
return Err(EngineProfileLimitedDecodeError::ProvenanceRowsOverflow);
}
ResolvedEngineProfileV1::from_wire(wire).map_err(|source| {
EngineProfileLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
})
}
impl<'de> Deserialize<'de> for ResolvedEngineProfileV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::from_wire(ResolvedEngineProfileWireV1::deserialize(deserializer)?)
.map_err(D::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineBakeOrExtractV1 {
Bake,
Extract,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EngineSettingValueV1 {
Boolean(bool),
BakeOrExtract(EngineBakeOrExtractV1),
SourceTransformPath(String),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EngineSettingRowV1 {
id: EngineSettingIdV1,
value: EngineSettingValueV1,
}
impl EngineSettingRowV1 {
pub const fn new(id: EngineSettingIdV1, value: EngineSettingValueV1) -> Self {
Self { id, value }
}
pub const fn id(&self) -> EngineSettingIdV1 {
self.id
}
pub const fn value(&self) -> &EngineSettingValueV1 {
&self.value
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EngineClipSettingsV1 {
clip_name: String,
settings: Vec<EngineSettingRowV1>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct EngineClipSettingsWireV1 {
clip_name: String,
#[serde(deserialize_with = "deserialize_collection_rows")]
settings: CappedSequence<EngineSettingRowV1>,
}
struct EngineClipSettingsSeed<'a> {
rows: &'a mut SettingsRows,
}
enum SettingsElement<T> {
Value(T),
Skipped,
}
struct SettingsElementSeed<'a, T> {
rows: &'a mut SettingsRows,
element: PhantomData<fn() -> T>,
}
impl<'de, T> DeserializeSeed<'de> for SettingsElementSeed<'_, T>
where
T: Deserialize<'de>,
{
type Value = SettingsElement<T>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
if self.rows.admit_setting() {
T::deserialize(deserializer).map(SettingsElement::Value)
} else {
IgnoredAny::deserialize(deserializer).map(|_| SettingsElement::Skipped)
}
}
}
struct SettingsSequenceSeed<'a, T> {
rows: &'a mut SettingsRows,
element: PhantomData<fn() -> T>,
}
impl<'de, T> DeserializeSeed<'de> for SettingsSequenceSeed<'_, T>
where
T: Deserialize<'de>,
{
type Value = CappedSequence<T>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct SettingsSequenceVisitor<'a, T> {
rows: &'a mut SettingsRows,
element: PhantomData<fn() -> T>,
}
impl<'de, T> Visitor<'de> for SettingsSequenceVisitor<'_, T>
where
T: Deserialize<'de>,
{
type Value = CappedSequence<T>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a bounded sequence of engine setting rows")
}
fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Vec::with_capacity(
sequence
.size_hint()
.unwrap_or(0)
.min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
);
let mut seen = 0usize;
while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
let Some(element) = sequence.next_element_seed(SettingsElementSeed {
rows: self.rows,
element: PhantomData,
})?
else {
return Ok(CappedSequence {
values,
overflowed: false,
});
};
seen += 1;
match element {
SettingsElement::Value(value) => values.push(value),
SettingsElement::Skipped => {
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
return Ok(CappedSequence { values, overflowed });
}
}
}
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
Ok(CappedSequence { values, overflowed })
}
}
deserializer.deserialize_seq(SettingsSequenceVisitor {
rows: self.rows,
element: PhantomData,
})
}
}
impl<'de> DeserializeSeed<'de> for EngineClipSettingsSeed<'_> {
type Value = EngineClipSettingsWireV1;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
ClipName,
Settings,
}
struct ClipSettingsVisitor<'a> {
rows: &'a mut SettingsRows,
}
impl<'de> Visitor<'de> for ClipSettingsVisitor<'_> {
type Value = EngineClipSettingsWireV1;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an engine clip-settings record")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut clip_name = None;
let mut settings = None;
while let Some(field) = map.next_key()? {
match field {
Field::ClipName => {
set_once(&mut clip_name, map.next_value()?, "clip_name")?
}
Field::Settings => {
if settings.is_some() {
return Err(A::Error::duplicate_field("settings"));
}
settings = Some(map.next_value_seed(SettingsSequenceSeed {
rows: self.rows,
element: PhantomData,
})?);
}
}
}
Ok(EngineClipSettingsWireV1 {
clip_name: required(clip_name, "clip_name")?,
settings: required(settings, "settings")?,
})
}
}
deserializer.deserialize_struct(
"EngineClipSettingsV1",
&["clip_name", "settings"],
ClipSettingsVisitor { rows: self.rows },
)
}
}
impl EngineClipSettingsV1 {
fn from_wire(wire: EngineClipSettingsWireV1) -> Result<Self, EngineContractError> {
validate_text("settings.clips.clip_name", &wire.clip_name)?;
if wire.settings.overflowed {
return Err(EngineContractError::TooManyRows {
field: "settings.clips.settings",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
let clip = Self {
clip_name: wire.clip_name,
settings: wire.settings.values,
};
clip.validate(true)?;
Ok(clip)
}
}
impl<'de> Deserialize<'de> for EngineClipSettingsV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::from_wire(EngineClipSettingsWireV1::deserialize(deserializer)?)
.map_err(D::Error::custom)
}
}
impl EngineClipSettingsV1 {
pub fn new(
clip_name: impl Into<String>,
mut settings: Vec<EngineSettingRowV1>,
) -> Result<Self, EngineContractError> {
settings.sort_by_key(|row| row.id.as_str());
let row = Self {
clip_name: clip_name.into(),
settings,
};
row.validate(true)?;
Ok(row)
}
pub fn clip_name(&self) -> &str {
&self.clip_name
}
pub fn settings(&self) -> &[EngineSettingRowV1] {
&self.settings
}
pub fn setting(&self, id: EngineSettingIdV1) -> Option<&EngineSettingValueV1> {
self.settings
.iter()
.find(|row| row.id == id)
.map(|row| &row.value)
}
fn validate(&self, require_order: bool) -> Result<(), EngineContractError> {
validate_text("settings.clips.clip_name", &self.clip_name)?;
validate_collection_len("settings.clips.settings", self.settings.len())?;
validate_unique_order(
"settings.clips.settings",
&self.settings,
|row| row.id.as_str(),
require_order,
)?;
for row in &self.settings {
validate_setting_value(&row.value)?;
}
Ok(())
}
fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
let paths = self.settings.iter().filter_map(|row| match &row.value {
EngineSettingValueV1::SourceTransformPath(path) => Some(path.len()),
_ => None,
});
checked_sum(
"settings retained text",
[self.clip_name.len()].into_iter().chain(paths),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ResolvedEngineSettingsV1 {
schema: String,
identity: InputIdentity,
document_settings: Vec<EngineSettingRowV1>,
clips: Vec<EngineClipSettingsV1>,
}
impl ResolvedEngineSettingsV1 {
pub fn new(
profile: &ResolvedEngineProfileV1,
mut document_settings: Vec<EngineSettingRowV1>,
mut clips: Vec<EngineClipSettingsV1>,
) -> Result<Self, EngineContractError> {
profile.validate()?;
document_settings.sort_by_key(|row| row.id.as_str());
clips.sort_by(|left, right| left.clip_name.cmp(&right.clip_name));
let mut settings = Self {
schema: RESOLVED_ENGINE_SETTINGS_V1_ID.to_owned(),
identity: InputIdentity::from_bytes(&[]),
document_settings,
clips,
};
settings.validate_structure(true)?;
settings.validate_materialization(profile, false)?;
settings.identity = settings.computed_identity(profile);
Ok(settings)
}
pub fn contract_id(&self) -> &str {
&self.schema
}
pub const fn settings_identity(&self) -> &InputIdentity {
&self.identity
}
pub fn document_settings(&self) -> &[EngineSettingRowV1] {
&self.document_settings
}
pub fn clips(&self) -> &[EngineClipSettingsV1] {
&self.clips
}
pub fn document_setting(&self, id: EngineSettingIdV1) -> Option<&EngineSettingValueV1> {
self.document_settings
.iter()
.find(|row| row.id == id)
.map(|row| &row.value)
}
pub fn clip_row(&self, ordinal: usize, clip_name: &str) -> Option<&EngineClipSettingsV1> {
self.clips
.get(ordinal)
.filter(|row| row.clip_name == clip_name)
}
pub fn validate_against(
&self,
profile: &ResolvedEngineProfileV1,
) -> Result<(), EngineContractError> {
profile.validate()?;
self.validate_structure(true)?;
self.validate_materialization(profile, true)
}
pub(crate) fn encode_preimage(
&self,
profile: &ResolvedEngineProfileV1,
encoder: &mut CanonicalEncoder,
) {
encoder.token(ENGINE_SETTINGS_PREIMAGE_DOMAIN);
encode_profile_key(encoder, &profile.selection);
encoder.field("fact_bundle_urn");
encoder.token(&profile.fact_bundle_urn);
encoder.field("document_settings");
encoder.count(self.document_settings.len());
for row in &self.document_settings {
encoder.token(row.id.as_str());
encode_setting_value(encoder, &row.value);
}
encoder.field("clips");
encoder.count(self.clips.len());
for clip in &self.clips {
encoder.token(&clip.clip_name);
encoder.count(clip.settings.len());
for row in &clip.settings {
encoder.token(row.id.as_str());
encode_setting_value(encoder, &row.value);
}
}
}
pub(crate) fn retained_rows(&self) -> Result<usize, EngineContractError> {
checked_sum(
"settings retained rows",
[self.document_settings.len(), self.clips.len()]
.into_iter()
.chain(self.clips.iter().map(|clip| clip.settings.len())),
)
}
pub(crate) fn retained_text_bytes(&self) -> Result<usize, EngineContractError> {
let document_paths = self
.document_settings
.iter()
.filter_map(|row| match &row.value {
EngineSettingValueV1::SourceTransformPath(path) => Some(path.len()),
_ => None,
});
checked_sum_results(
"settings retained text",
document_paths,
self.clips
.iter()
.map(EngineClipSettingsV1::retained_text_bytes),
)
}
fn computed_identity(&self, profile: &ResolvedEngineProfileV1) -> InputIdentity {
let mut encoder = CanonicalEncoder::default();
self.encode_preimage(profile, &mut encoder);
encoder.identity()
}
fn validate_structure(&self, require_order: bool) -> Result<(), EngineContractError> {
validate_schema(
"settings.schema",
&self.schema,
RESOLVED_ENGINE_SETTINGS_V1_ID,
)?;
validate_collection_len("settings.document_settings", self.document_settings.len())?;
validate_collection_len("settings.clips", self.clips.len())?;
validate_unique_order(
"settings.document_settings",
&self.document_settings,
|row| row.id.as_str(),
require_order,
)?;
for row in &self.document_settings {
validate_setting_value(&row.value)?;
}
if require_order
&& !self
.clips
.windows(2)
.all(|pair| pair[0].clip_name <= pair[1].clip_name)
{
return Err(EngineContractError::NonCanonicalOrder {
field: "settings.clips",
});
}
for clip in &self.clips {
clip.validate(require_order)?;
}
let rows = self.retained_rows()?;
if rows > ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS {
return Err(EngineContractError::TooManyAggregateRows {
found: rows,
max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
});
}
let text = self.retained_text_bytes()?;
if text > ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES {
return Err(EngineContractError::TooMuchAggregateText {
found: text,
max: ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES,
});
}
Ok(())
}
fn validate_materialization(
&self,
profile: &ResolvedEngineProfileV1,
verify_identity: bool,
) -> Result<(), EngineContractError> {
validate_rows_for_scope(
profile,
&self.document_settings,
EngineSettingScopeV1::Document,
"document",
)?;
for (ordinal, clip) in self.clips.iter().enumerate() {
validate_rows_for_scope(
profile,
&clip.settings,
EngineSettingScopeV1::Clip,
&format!("clip[{ordinal}]"),
)?;
}
if verify_identity && self.identity != self.computed_identity(profile) {
return Err(EngineContractError::IdentityMismatch {
contract: RESOLVED_ENGINE_SETTINGS_V1_ID,
});
}
Ok(())
}
}
struct ResolvedEngineSettingsWireV1 {
schema: String,
identity: InputIdentity,
document_settings: CappedSequence<EngineSettingRowV1>,
clips: CappedSequence<EngineClipSettingsWireV1>,
aggregate_rows: RowBudget,
provenance_rows_overflowed: bool,
}
enum ClipSettingsElement {
Value(EngineClipSettingsWireV1),
Skipped,
}
struct ClipSettingsElementSeed<'a> {
rows: &'a mut SettingsRows,
}
impl<'de> DeserializeSeed<'de> for ClipSettingsElementSeed<'_> {
type Value = ClipSettingsElement;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
if self.rows.admit_clip() {
EngineClipSettingsSeed { rows: self.rows }
.deserialize(deserializer)
.map(ClipSettingsElement::Value)
} else {
IgnoredAny::deserialize(deserializer).map(|_| ClipSettingsElement::Skipped)
}
}
}
struct ClipSettingsSequenceSeed<'a> {
rows: &'a mut SettingsRows,
}
impl<'de> DeserializeSeed<'de> for ClipSettingsSequenceSeed<'_> {
type Value = CappedSequence<EngineClipSettingsWireV1>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ClipSettingsSequenceVisitor<'a> {
rows: &'a mut SettingsRows,
}
impl<'de> Visitor<'de> for ClipSettingsSequenceVisitor<'_> {
type Value = CappedSequence<EngineClipSettingsWireV1>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a bounded sequence of engine clip settings")
}
fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Vec::with_capacity(
sequence
.size_hint()
.unwrap_or(0)
.min(ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS),
);
let mut seen = 0usize;
while seen < ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
let Some(element) =
sequence.next_element_seed(ClipSettingsElementSeed { rows: self.rows })?
else {
return Ok(CappedSequence {
values,
overflowed: false,
});
};
seen += 1;
match element {
ClipSettingsElement::Value(value) => values.push(value),
ClipSettingsElement::Skipped => {
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
return Ok(CappedSequence { values, overflowed });
}
}
}
let overflowed = consume_ignored_tail(
&mut sequence,
seen,
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
)?;
Ok(CappedSequence { values, overflowed })
}
}
deserializer.deserialize_seq(ClipSettingsSequenceVisitor { rows: self.rows })
}
}
struct ResolvedEngineSettingsWireSeed {
provenance_limit: Option<usize>,
}
impl<'de> DeserializeSeed<'de> for ResolvedEngineSettingsWireSeed {
type Value = ResolvedEngineSettingsWireV1;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Schema,
Identity,
DocumentSettings,
Clips,
}
struct SettingsVisitor {
provenance_limit: Option<usize>,
}
impl<'de> Visitor<'de> for SettingsVisitor {
type Value = ResolvedEngineSettingsWireV1;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("resolved engine settings")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut rows = SettingsRows::new(self.provenance_limit);
let mut schema = None;
let mut identity = None;
let mut document_settings = None;
let mut clips = None;
while let Some(field) = map.next_key()? {
match field {
Field::Schema => set_once(&mut schema, map.next_value()?, "schema")?,
Field::Identity => set_once(&mut identity, map.next_value()?, "identity")?,
Field::DocumentSettings => {
if document_settings.is_some() {
return Err(A::Error::duplicate_field("document_settings"));
}
document_settings =
Some(map.next_value_seed(SettingsSequenceSeed {
rows: &mut rows,
element: PhantomData,
})?);
}
Field::Clips => {
if clips.is_some() {
return Err(A::Error::duplicate_field("clips"));
}
clips =
Some(map.next_value_seed(ClipSettingsSequenceSeed {
rows: &mut rows,
})?);
}
}
}
Ok(ResolvedEngineSettingsWireV1 {
schema: required(schema, "schema")?,
identity: required(identity, "identity")?,
document_settings: required(document_settings, "document_settings")?,
clips: required(clips, "clips")?,
provenance_rows_overflowed: rows.provenance_overflowed(),
aggregate_rows: rows.local,
})
}
}
deserializer.deserialize_struct(
"ResolvedEngineSettingsV1",
&["schema", "identity", "document_settings", "clips"],
SettingsVisitor {
provenance_limit: self.provenance_limit,
},
)
}
}
impl<'de> Deserialize<'de> for ResolvedEngineSettingsWireV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
ResolvedEngineSettingsWireSeed {
provenance_limit: None,
}
.deserialize(deserializer)
}
}
impl ResolvedEngineSettingsV1 {
fn validate_wire_limits(
wire: &ResolvedEngineSettingsWireV1,
) -> Result<(), EngineContractError> {
validate_schema(
"settings.schema",
&wire.schema,
RESOLVED_ENGINE_SETTINGS_V1_ID,
)?;
for (field, overflowed) in [
(
"settings.document_settings",
wire.document_settings.overflowed,
),
("settings.clips", wire.clips.overflowed),
] {
if overflowed {
return Err(EngineContractError::TooManyRows {
field,
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
}
for clip in &wire.clips.values {
if clip.settings.overflowed {
return Err(EngineContractError::TooManyRows {
field: "settings.clips.settings",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
});
}
}
if wire.aggregate_rows.overflowed() {
return Err(EngineContractError::TooManyAggregateRows {
found: wire.aggregate_rows.found(),
max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
});
}
Ok(())
}
fn from_wire(wire: ResolvedEngineSettingsWireV1) -> Result<Self, EngineContractError> {
Self::validate_wire_limits(&wire)?;
let clips = wire
.clips
.values
.into_iter()
.map(EngineClipSettingsV1::from_wire)
.collect::<Result<Vec<_>, _>>()?;
let settings = Self {
schema: wire.schema,
identity: wire.identity,
document_settings: wire.document_settings.values,
clips,
};
settings.validate_structure(true)?;
Ok(settings)
}
}
pub(crate) enum EngineSettingsLimitedDecodeError {
Contract(EngineContractDecodeError),
ProvenanceRowsOverflow,
}
pub(crate) fn decode_resolved_engine_settings_v1_with_provenance_limit(
raw: &str,
provenance_limit: usize,
) -> Result<ResolvedEngineSettingsV1, EngineSettingsLimitedDecodeError> {
let mut deserializer = serde_json::Deserializer::from_str(raw);
let wire = ResolvedEngineSettingsWireSeed {
provenance_limit: Some(provenance_limit),
}
.deserialize(&mut deserializer)
.map_err(|source| {
EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
})?;
deserializer.end().map_err(|source| {
EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Shape(source))
})?;
ResolvedEngineSettingsV1::validate_wire_limits(&wire).map_err(|source| {
EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
})?;
if wire.provenance_rows_overflowed {
return Err(EngineSettingsLimitedDecodeError::ProvenanceRowsOverflow);
}
ResolvedEngineSettingsV1::from_wire(wire).map_err(|source| {
EngineSettingsLimitedDecodeError::Contract(EngineContractDecodeError::Semantic(source))
})
}
impl<'de> Deserialize<'de> for ResolvedEngineSettingsV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::from_wire(ResolvedEngineSettingsWireV1::deserialize(deserializer)?)
.map_err(D::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum EngineContractError {
#[error("{field} must be {expected:?}, found {found:?}")]
InvalidSchema {
field: &'static str,
expected: &'static str,
found: String,
},
#[error("{field} must not be empty")]
EmptyText {
field: &'static str,
},
#[error("{field} retains {found} UTF-8 bytes, exceeding {max}")]
TextTooLong {
field: &'static str,
found: usize,
max: usize,
},
#[error("{field} contains {found} rows, exceeding {max}")]
TooManyRows {
field: &'static str,
found: usize,
max: usize,
},
#[error("profile/settings retain {found} aggregate rows, exceeding {max}")]
TooManyAggregateRows {
found: usize,
max: usize,
},
#[error("profile/settings retain {found} UTF-8 bytes, exceeding {max}")]
TooMuchAggregateText {
found: usize,
max: usize,
},
#[error("checked arithmetic overflow while accounting {field}")]
ArithmeticOverflow {
field: &'static str,
},
#[error("{field} contains duplicate key {key:?}")]
DuplicateKey {
field: &'static str,
key: String,
},
#[error("{field} is not in canonical order")]
NonCanonicalOrder {
field: &'static str,
},
#[error("profile facts must contain every V1 fact id exactly once")]
InvalidFactInventory,
#[error("profile fact {fact:?} carries an invalid known-value variant")]
InvalidFactValue {
fact: EngineFactIdV1,
},
#[error("profile accepted_inputs must be a nonempty canonical set")]
InvalidAcceptedInputs,
#[error("setting descriptor {setting:?} has inconsistent applicability/default status")]
InvalidDescriptorDefault {
setting: EngineSettingIdV1,
},
#[error("primary source {source_id:?} references absent fact {fact:?}")]
UnknownSourceFact {
source_id: String,
fact: EngineFactIdV1,
},
#[error("primary source {source_id:?} references non-known fact {fact:?}")]
SourceReferencesNonKnownFact {
source_id: String,
fact: EngineFactIdV1,
},
#[error("primary source {source_id:?} references absent setting {setting:?}")]
UnknownSourceSetting {
source_id: String,
setting: EngineSettingIdV1,
},
#[error("known profile fact {fact:?} has no primary-source reference")]
UnreferencedKnownFact {
fact: EngineFactIdV1,
},
#[error("setting descriptor {setting:?} has no primary-source reference")]
UnreferencedSetting {
setting: EngineSettingIdV1,
},
#[error("source-transform path is invalid: {reason}")]
InvalidSourceTransformPath {
reason: &'static str,
},
#[error("{location} contains unknown setting {setting:?}")]
UnknownMaterializedSetting {
location: String,
setting: EngineSettingIdV1,
},
#[error("{location} contains {setting:?} at the wrong scope")]
WrongSettingScope {
location: String,
setting: EngineSettingIdV1,
},
#[error("{location} contains non-applicable setting {setting:?}")]
NonApplicableSetting {
location: String,
setting: EngineSettingIdV1,
},
#[error("{location} contains {setting:?} with a value outside its domain")]
WrongSettingDomain {
location: String,
setting: EngineSettingIdV1,
},
#[error("{location} is missing required setting {setting:?}")]
MissingRequiredSetting {
location: String,
setting: EngineSettingIdV1,
},
#[error("identity does not match canonical {contract}")]
IdentityMismatch {
contract: &'static str,
},
}
fn validate_schema(
field: &'static str,
found: &str,
expected: &'static str,
) -> Result<(), EngineContractError> {
if found == expected {
Ok(())
} else {
Err(EngineContractError::InvalidSchema {
field,
expected,
found: found.to_owned(),
})
}
}
fn validate_required_text(field: &'static str, value: &str) -> Result<(), EngineContractError> {
if value.is_empty() {
return Err(EngineContractError::EmptyText { field });
}
validate_text(field, value)
}
fn validate_text(field: &'static str, value: &str) -> Result<(), EngineContractError> {
if value.len() > ENGINE_CONTRACT_V1_MAX_TEXT_BYTES {
Err(EngineContractError::TextTooLong {
field,
found: value.len(),
max: ENGINE_CONTRACT_V1_MAX_TEXT_BYTES,
})
} else {
Ok(())
}
}
fn validate_collection_len(field: &'static str, found: usize) -> Result<(), EngineContractError> {
if found > ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS {
Err(EngineContractError::TooManyRows {
field,
found,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
})
} else {
Ok(())
}
}
fn validate_unique_order<T>(
field: &'static str,
rows: &[T],
key: impl Fn(&T) -> &str,
require_order: bool,
) -> Result<(), EngineContractError> {
let mut seen = BTreeSet::new();
let mut previous = None;
for row in rows {
let current = key(row);
if !seen.insert(current) {
return Err(EngineContractError::DuplicateKey {
field,
key: current.to_owned(),
});
}
if require_order && previous.is_some_and(|previous| previous >= current) {
return Err(EngineContractError::NonCanonicalOrder { field });
}
previous = Some(current);
}
Ok(())
}
fn validate_fact_value(fact: &EngineProfileFactV1) -> Result<(), EngineContractError> {
let EngineFactStateV1::Known(value) = &fact.state else {
return Ok(());
};
let valid = matches!(
(fact.id, value),
(
EngineFactIdV1::AcceptedInputs,
EngineFactValueV1::AcceptedFormats(_)
) | (
EngineFactIdV1::AnimationAddressability,
EngineFactValueV1::AnimationAddressability(_)
) | (
EngineFactIdV1::TargetCoordinateBasis,
EngineFactValueV1::CoordinateBasis(_)
) | (
EngineFactIdV1::TargetLinearUnit,
EngineFactValueV1::LinearUnit(_)
) | (
EngineFactIdV1::UnitConversionControl | EngineFactIdV1::AxisConversionControl,
EngineFactValueV1::ConversionControl(_)
) | (
EngineFactIdV1::WholeEndFrameRequired,
EngineFactValueV1::Boolean(_)
) | (
EngineFactIdV1::AnimationChannelHandling
| EngineFactIdV1::ExtensionHandling
| EngineFactIdV1::ConstructHandling,
EngineFactValueV1::ImportHandling(_)
) | (
EngineFactIdV1::AnimationTargetAddressability,
EngineFactValueV1::TargetAddressability(_)
) | (
EngineFactIdV1::RootMotionAddressability,
EngineFactValueV1::RootMotionAddressability(_)
)
);
if !valid {
return Err(EngineContractError::InvalidFactValue { fact: fact.id });
}
if let EngineFactValueV1::AcceptedFormats(formats) = value
&& (formats.is_empty()
|| formats.len() > ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
|| !formats
.windows(2)
.all(|pair| source_format_name(pair[0]) < source_format_name(pair[1])))
{
return Err(EngineContractError::InvalidAcceptedInputs);
}
if let EngineFactValueV1::ConversionControl(EngineConversionControlV1::ProfileSetting(
setting,
)) = value
{
let expected = match fact.id {
EngineFactIdV1::UnitConversionControl => EngineSettingIdV1::ConvertUnits,
EngineFactIdV1::AxisConversionControl => EngineSettingIdV1::BakeAxisConversion,
_ => return Err(EngineContractError::InvalidFactValue { fact: fact.id }),
};
if *setting != expected {
return Err(EngineContractError::InvalidFactValue { fact: fact.id });
}
}
Ok(())
}
fn validate_setting_value(value: &EngineSettingValueV1) -> Result<(), EngineContractError> {
let EngineSettingValueV1::SourceTransformPath(path) = value else {
return Ok(());
};
validate_text("source_transform_path", path)?;
let reason = if path.is_empty() {
Some("empty path")
} else if path.starts_with('/') {
Some("absolute path")
} else if path.chars().any(char::is_control) {
Some("control character")
} else if path.split('/').any(str::is_empty) {
Some("empty path segment")
} else if path.split('/').any(|segment| matches!(segment, "." | "..")) {
Some("dot path segment")
} else {
None
};
if let Some(reason) = reason {
Err(EngineContractError::InvalidSourceTransformPath { reason })
} else {
Ok(())
}
}
fn validate_rows_for_scope(
profile: &ResolvedEngineProfileV1,
rows: &[EngineSettingRowV1],
scope: EngineSettingScopeV1,
location: &str,
) -> Result<(), EngineContractError> {
for row in rows {
let Some(descriptor) = profile.setting_descriptor(row.id) else {
return Err(EngineContractError::UnknownMaterializedSetting {
location: location.to_owned(),
setting: row.id,
});
};
if descriptor.scope != scope {
return Err(EngineContractError::WrongSettingScope {
location: location.to_owned(),
setting: row.id,
});
}
if descriptor.applicability != EngineSettingApplicabilityV1::Applicable {
return Err(EngineContractError::NonApplicableSetting {
location: location.to_owned(),
setting: row.id,
});
}
let domain_matches = matches!(
(descriptor.domain, &row.value),
(
EngineSettingDomainV1::Boolean,
EngineSettingValueV1::Boolean(_)
) | (
EngineSettingDomainV1::BakeOrExtract,
EngineSettingValueV1::BakeOrExtract(_)
) | (
EngineSettingDomainV1::SourceTransformPath,
EngineSettingValueV1::SourceTransformPath(_)
)
);
if !domain_matches {
return Err(EngineContractError::WrongSettingDomain {
location: location.to_owned(),
setting: row.id,
});
}
}
for descriptor in &profile.setting_descriptors {
if descriptor.scope == scope
&& descriptor.applicability == EngineSettingApplicabilityV1::Applicable
&& descriptor.default_status == EngineDefaultStatusV1::RequiredWithoutDefault
&& !rows.iter().any(|row| row.id == descriptor.id)
{
return Err(EngineContractError::MissingRequiredSetting {
location: location.to_owned(),
setting: descriptor.id,
});
}
}
Ok(())
}
fn checked_sum(
field: &'static str,
values: impl IntoIterator<Item = usize>,
) -> Result<usize, EngineContractError> {
values.into_iter().try_fold(0_usize, |total, value| {
total
.checked_add(value)
.ok_or(EngineContractError::ArithmeticOverflow { field })
})
}
fn checked_sum_results(
field: &'static str,
initial: impl IntoIterator<Item = usize>,
values: impl IntoIterator<Item = Result<usize, EngineContractError>>,
) -> Result<usize, EngineContractError> {
let initial = checked_sum(field, initial)?;
values.into_iter().try_fold(initial, |total, value| {
total
.checked_add(value?)
.ok_or(EngineContractError::ArithmeticOverflow { field })
})
}
fn encode_profile_key(encoder: &mut CanonicalEncoder, selection: &EngineProfileSelectionV1) {
encoder.field("selection");
encoder.token(&selection.family);
encoder.token(selection.profile_revision.to_string());
encoder.token(&selection.engine_version);
encoder.token(&selection.importer);
}
fn encode_fact_state(encoder: &mut CanonicalEncoder, state: &EngineFactStateV1) {
match state {
EngineFactStateV1::Unknown => encoder.token("unknown"),
EngineFactStateV1::NotApplicable => encoder.token("not_applicable"),
EngineFactStateV1::Known(value) => {
encoder.token("known");
match value {
EngineFactValueV1::AcceptedFormats(formats) => {
encoder.token("accepted_formats");
encoder.count(formats.len());
for format in formats {
encoder.token(source_format_name(*format));
}
}
EngineFactValueV1::AnimationAddressability(value) => {
encoder.token("animation_addressability");
encoder.token(match value {
EngineAnimationAddressabilityV1::GltfAssetLabel => "gltf_asset_label",
});
}
EngineFactValueV1::CoordinateBasis(value) => {
encoder.token("coordinate_basis");
encoder.token(match value.handedness {
EngineHandednessV1::Left => "left",
EngineHandednessV1::Right => "right",
});
encoder.token(match value.up_axis {
EngineUpAxisV1::X => "x",
EngineUpAxisV1::Y => "y",
EngineUpAxisV1::Z => "z",
});
encoder.token(match value.forward_axis {
EngineForwardAxisV1::PositiveX => "+x",
EngineForwardAxisV1::NegativeX => "-x",
EngineForwardAxisV1::PositiveY => "+y",
EngineForwardAxisV1::NegativeY => "-y",
EngineForwardAxisV1::PositiveZ => "+z",
EngineForwardAxisV1::NegativeZ => "-z",
});
}
EngineFactValueV1::LinearUnit(value) => {
encoder.token("linear_unit");
encoder.token(match value {
EngineLinearUnitV1::Metre => "metre",
EngineLinearUnitV1::Centimetre => "centimetre",
});
}
EngineFactValueV1::ConversionControl(value) => {
encoder.token("conversion_control");
match value {
EngineConversionControlV1::ProfileSetting(setting) => {
encoder.token("profile_setting");
encoder.token(setting.as_str());
}
EngineConversionControlV1::ImporterOption => {
encoder.token("importer_option");
}
}
}
EngineFactValueV1::Boolean(value) => {
encoder.token("boolean");
encoder.token(if *value { "true" } else { "false" });
}
EngineFactValueV1::ImportHandling(value) => {
encoder.token("import_handling");
encoder.token(match value {
EngineImportHandlingV1::Preserved => "preserved",
EngineImportHandlingV1::Converted => "converted",
EngineImportHandlingV1::Discarded => "discarded",
EngineImportHandlingV1::Unsupported => "unsupported",
});
}
EngineFactValueV1::TargetAddressability(value) => {
encoder.token("target_addressability");
encoder.token(match value {
EngineTargetAddressabilityV1::NamePathDerivedId => "name_path_derived_id",
});
}
EngineFactValueV1::RootMotionAddressability(value) => {
encoder.token("root_motion_addressability");
encoder.token(match value {
EngineRootMotionAddressabilityV1::ExactSourceTransformPath => {
"exact_source_transform_path"
}
EngineRootMotionAddressabilityV1::HumanoidAvatarBody => {
"humanoid_avatar_body"
}
});
}
}
}
}
}
fn encode_setting_value(encoder: &mut CanonicalEncoder, value: &EngineSettingValueV1) {
match value {
EngineSettingValueV1::Boolean(value) => {
encoder.token("boolean");
encoder.token(if *value { "true" } else { "false" });
}
EngineSettingValueV1::BakeOrExtract(value) => {
encoder.token("bake_or_extract");
encoder.token(match value {
EngineBakeOrExtractV1::Bake => "bake",
EngineBakeOrExtractV1::Extract => "extract",
});
}
EngineSettingValueV1::SourceTransformPath(value) => {
encoder.token("source_transform_path");
encoder.token(value);
}
}
}
const fn source_format_name(format: SourceFormatV1) -> &'static str {
match format {
SourceFormatV1::GltfJson => "gltf_json",
SourceFormatV1::Glb => "glb",
SourceFormatV1::Fbx => "fbx",
}
}
const fn setting_scope_name(scope: EngineSettingScopeV1) -> &'static str {
match scope {
EngineSettingScopeV1::Document => "document",
EngineSettingScopeV1::Clip => "clip",
}
}
const fn setting_domain_name(domain: EngineSettingDomainV1) -> &'static str {
match domain {
EngineSettingDomainV1::Boolean => "boolean",
EngineSettingDomainV1::BakeOrExtract => "bake_or_extract",
EngineSettingDomainV1::SourceTransformPath => "source_transform_path",
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn fact_inventory(accepted: Vec<SourceFormatV1>) -> Vec<EngineProfileFactV1> {
ALL_FACT_IDS
.into_iter()
.map(|id| {
let state = if id == EngineFactIdV1::AcceptedInputs {
EngineFactStateV1::Known(EngineFactValueV1::AcceptedFormats(accepted.clone()))
} else {
EngineFactStateV1::Unknown
};
EngineProfileFactV1::new(id, state)
})
.collect()
}
fn godot_profile() -> ResolvedEngineProfileV1 {
ResolvedEngineProfileV1::new(
EngineProfileSelectionV1::new("godot", 1, "4.7", "resource-importer-scene").unwrap(),
"urn:animsmith:engine-profile:godot:1",
fact_inventory(vec![
SourceFormatV1::GltfJson,
SourceFormatV1::Glb,
SourceFormatV1::Fbx,
]),
vec![],
vec![
EnginePrimarySourceV1::new(
"godot-resource-importer-scene-4.7",
"4.7",
"https://docs.godotengine.org/en/4.7/classes/class_resourceimporterscene.html",
"2026-08-20",
vec![EngineFactIdV1::AcceptedInputs],
vec![],
)
.unwrap(),
],
)
.unwrap()
}
fn settings_profile(family: &str) -> ResolvedEngineProfileV1 {
let mut facts = fact_inventory(vec![SourceFormatV1::Fbx]);
facts
.iter_mut()
.find(|fact| fact.id == EngineFactIdV1::UnitConversionControl)
.unwrap()
.state = EngineFactStateV1::Known(EngineFactValueV1::ConversionControl(
EngineConversionControlV1::ProfileSetting(EngineSettingIdV1::ConvertUnits),
));
facts
.iter_mut()
.find(|fact| fact.id == EngineFactIdV1::AxisConversionControl)
.unwrap()
.state = EngineFactStateV1::Known(EngineFactValueV1::ConversionControl(
EngineConversionControlV1::ProfileSetting(EngineSettingIdV1::BakeAxisConversion),
));
let descriptors = vec![
EngineSettingDescriptorV1::new(
EngineSettingIdV1::ConvertUnits,
EngineSettingScopeV1::Document,
EngineSettingDomainV1::Boolean,
EngineSettingApplicabilityV1::Applicable,
EngineDefaultStatusV1::RequiredWithoutDefault,
),
EngineSettingDescriptorV1::new(
EngineSettingIdV1::BakeAxisConversion,
EngineSettingScopeV1::Document,
EngineSettingDomainV1::Boolean,
EngineSettingApplicabilityV1::Applicable,
EngineDefaultStatusV1::RequiredWithoutDefault,
),
];
let source = EnginePrimarySourceV1::new(
"source",
"1",
"https://example.invalid/source",
"2026-08-20",
vec![
EngineFactIdV1::AcceptedInputs,
EngineFactIdV1::UnitConversionControl,
EngineFactIdV1::AxisConversionControl,
],
vec![
EngineSettingIdV1::ConvertUnits,
EngineSettingIdV1::BakeAxisConversion,
],
)
.unwrap();
ResolvedEngineProfileV1::new(
EngineProfileSelectionV1::new(family, 1, "1", "importer").unwrap(),
format!("urn:animsmith:engine-profile:{family}:1"),
facts,
descriptors,
vec![source],
)
.unwrap()
}
fn document_settings() -> Vec<EngineSettingRowV1> {
vec![
EngineSettingRowV1::new(
EngineSettingIdV1::ConvertUnits,
EngineSettingValueV1::Boolean(true),
),
EngineSettingRowV1::new(
EngineSettingIdV1::BakeAxisConversion,
EngineSettingValueV1::Boolean(false),
),
]
}
#[test]
fn profile_encoder_preserves_464_godot_golden() {
let profile = godot_profile();
assert_eq!(
profile.facts_identity().sha256(),
"e9c8316d1655c487b60dd35bbfc70289952c5fa12f4718f0be09c7e9a00fbe87"
);
assert_eq!(profile.facts_identity().bytes(), 1_166);
let mut encoder = CanonicalEncoder::default();
profile.encode_preimage(&mut encoder);
assert_eq!(encoder.into_bytes().len(), 1_166);
}
#[test]
fn settings_encoder_preserves_464_godot_golden() {
let profile = godot_profile();
let settings = ResolvedEngineSettingsV1::new(&profile, vec![], vec![]).unwrap();
assert_eq!(
settings.settings_identity().sha256(),
"02032c315fa41ad65249efe1b6914456b3b98caf9b5374b168854cd357f85515"
);
assert_eq!(settings.settings_identity().bytes(), 240);
}
#[test]
fn constructors_canonicalize_sets_maps_and_retain_repeated_clips() {
let profile = settings_profile("test");
let first = ResolvedEngineSettingsV1::new(
&profile,
document_settings(),
vec![
EngineClipSettingsV1::new("walk", vec![]).unwrap(),
EngineClipSettingsV1::new("idle", vec![]).unwrap(),
EngineClipSettingsV1::new("walk", vec![]).unwrap(),
],
)
.unwrap();
let mut reversed = document_settings();
reversed.reverse();
let second = ResolvedEngineSettingsV1::new(
&profile,
reversed,
vec![
EngineClipSettingsV1::new("walk", vec![]).unwrap(),
EngineClipSettingsV1::new("walk", vec![]).unwrap(),
EngineClipSettingsV1::new("idle", vec![]).unwrap(),
],
)
.unwrap();
assert_eq!(first, second);
assert_eq!(
first
.clips()
.iter()
.map(EngineClipSettingsV1::clip_name)
.collect::<Vec<_>>(),
vec!["idle", "walk", "walk"]
);
assert!(first.clip_row(1, "walk").is_some());
assert!(first.clip_row(1, "idle").is_none());
let deduplicated = ResolvedEngineSettingsV1::new(
&profile,
document_settings(),
vec![
EngineClipSettingsV1::new("idle", vec![]).unwrap(),
EngineClipSettingsV1::new("walk", vec![]).unwrap(),
],
)
.unwrap();
assert_ne!(first.settings_identity(), deduplicated.settings_identity());
}
#[test]
fn wire_round_trip_is_strict_and_revalidates_identities() {
let profile = godot_profile();
let value = serde_json::to_value(&profile).unwrap();
assert_eq!(
value["schema"],
json!("urn:animsmith:engine-profile-facts:1")
);
assert!(value.get("primary_sources").is_some());
let decoded: ResolvedEngineProfileV1 = serde_json::from_value(value.clone()).unwrap();
assert_eq!(decoded, profile);
let mut unknown = value.clone();
unknown["unexpected"] = json!(true);
assert!(
serde_json::from_value::<ResolvedEngineProfileV1>(unknown)
.unwrap_err()
.to_string()
.contains("unknown field")
);
let mut identity = value.clone();
identity["identity"]["bytes"] = json!(0);
assert!(
serde_json::from_value::<ResolvedEngineProfileV1>(identity)
.unwrap_err()
.to_string()
.contains("identity does not match")
);
let mut reordered = value;
reordered["facts"].as_array_mut().unwrap().swap(0, 1);
assert!(
serde_json::from_value::<ResolvedEngineProfileV1>(reordered)
.unwrap_err()
.to_string()
.contains("canonical order")
);
}
#[test]
fn profile_mutations_return_the_specific_first_contract_error() {
let profile = godot_profile();
let mut changed = profile.clone();
changed.schema = "urn:changed".into();
assert!(matches!(
changed.validate(),
Err(EngineContractError::InvalidSchema {
field: "profile.schema",
..
})
));
let mut changed = profile.clone();
changed.selection.family.push_str("-changed");
assert_eq!(
changed.validate(),
Err(EngineContractError::IdentityMismatch {
contract: ENGINE_PROFILE_FACTS_V1_ID,
})
);
let mut changed = profile.clone();
changed.facts[0].state = EngineFactStateV1::Unknown;
assert_eq!(
changed.validate(),
Err(EngineContractError::InvalidAcceptedInputs)
);
let mut changed = profile;
changed.primary_sources[0].url.push_str("/changed");
assert_eq!(
changed.validate(),
Err(EngineContractError::IdentityMismatch {
contract: ENGINE_PROFILE_FACTS_V1_ID,
})
);
}
#[test]
fn profile_acceptance_mutation_matrix_pins_tuple_facts_descriptors_and_sources() {
let profile = settings_profile("matrix");
let identity_mismatch = Err(EngineContractError::IdentityMismatch {
contract: ENGINE_PROFILE_FACTS_V1_ID,
});
let mut changed = profile.clone();
changed.selection.family.push_str("-changed");
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
changed.selection.profile_revision += 1;
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
changed.selection.engine_version.push_str("-changed");
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
changed.selection.importer.push_str("-changed");
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
changed.fact_bundle_urn.push_str(":changed");
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
changed.facts.pop();
assert_eq!(
changed.validate(),
Err(EngineContractError::InvalidFactInventory)
);
let mut changed = profile.clone();
changed
.facts
.iter_mut()
.find(|fact| fact.id == EngineFactIdV1::AcceptedInputs)
.unwrap()
.state = EngineFactStateV1::Known(EngineFactValueV1::Boolean(true));
assert_eq!(
changed.validate(),
Err(EngineContractError::InvalidFactValue {
fact: EngineFactIdV1::AcceptedInputs,
})
);
let mut changed = profile.clone();
changed.setting_descriptors[1].id = EngineSettingIdV1::BakeAxisConversion;
assert_eq!(
changed.validate(),
Err(EngineContractError::InvalidFactValue {
fact: EngineFactIdV1::UnitConversionControl,
})
);
let mut changed = profile.clone();
changed.setting_descriptors[0].scope = EngineSettingScopeV1::Clip;
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
changed.setting_descriptors[0].domain = EngineSettingDomainV1::BakeOrExtract;
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
let descriptor_id = changed.setting_descriptors[0].id;
changed.setting_descriptors[0].applicability = EngineSettingApplicabilityV1::NotApplicable;
assert_eq!(
changed.validate(),
Err(EngineContractError::InvalidDescriptorDefault {
setting: descriptor_id,
})
);
let mut changed = profile.clone();
let descriptor_id = changed.setting_descriptors[0].id;
changed.setting_descriptors[0].default_status = EngineDefaultStatusV1::NotApplicable;
assert_eq!(
changed.validate(),
Err(EngineContractError::InvalidDescriptorDefault {
setting: descriptor_id,
})
);
let mut changed = profile.clone();
changed.primary_sources[0].id.clear();
assert_eq!(
changed.validate(),
Err(EngineContractError::EmptyText {
field: "primary_sources.id",
})
);
let mut changed = profile.clone();
changed.primary_sources[0].url.push_str("/changed");
assert_eq!(changed.validate(), identity_mismatch);
let mut changed = profile.clone();
changed.primary_sources[0]
.supported_fact_ids
.push(EngineFactIdV1::AnimationAddressability);
changed.primary_sources[0]
.supported_fact_ids
.sort_by_key(|id| id.as_str());
assert_eq!(
changed.validate(),
Err(EngineContractError::SourceReferencesNonKnownFact {
source_id: "source".to_owned(),
fact: EngineFactIdV1::AnimationAddressability,
})
);
let mut changed = profile.clone();
changed.schema = "urn:changed".to_owned();
assert_eq!(
changed.validate(),
Err(EngineContractError::InvalidSchema {
field: "profile.schema",
expected: ENGINE_PROFILE_FACTS_V1_ID,
found: "urn:changed".to_owned(),
})
);
let mut changed = profile;
changed.identity = InputIdentity::from_bytes(b"changed");
assert_eq!(changed.validate(), identity_mismatch);
}
#[test]
fn settings_wire_requires_profile_validation_after_structural_read() {
let profile = settings_profile("wire");
let settings =
ResolvedEngineSettingsV1::new(&profile, document_settings(), vec![]).unwrap();
let mut value = serde_json::to_value(&settings).unwrap();
let decoded: ResolvedEngineSettingsV1 = serde_json::from_value(value.clone()).unwrap();
decoded.validate_against(&profile).unwrap();
value["identity"]["bytes"] = json!(0);
let decoded: ResolvedEngineSettingsV1 = serde_json::from_value(value).unwrap();
assert_eq!(
decoded.validate_against(&profile),
Err(EngineContractError::IdentityMismatch {
contract: RESOLVED_ENGINE_SETTINGS_V1_ID,
})
);
}
#[test]
fn settings_mutations_reject_noncanonical_order_before_identity() {
let profile = settings_profile("order");
let mut settings =
ResolvedEngineSettingsV1::new(&profile, document_settings(), vec![]).unwrap();
settings.document_settings.swap(0, 1);
assert_eq!(
settings.validate_against(&profile),
Err(EngineContractError::NonCanonicalOrder {
field: "settings.document_settings",
})
);
}
#[test]
fn materialized_settings_acceptance_mutation_matrix_pins_id_value_location_and_identity() {
let profile = settings_profile("settings-matrix");
let settings =
ResolvedEngineSettingsV1::new(&profile, document_settings(), vec![]).unwrap();
let mut changed = settings.clone();
changed.document_settings[1].id = EngineSettingIdV1::RootMotionSource;
assert_eq!(
changed.validate_against(&profile),
Err(EngineContractError::UnknownMaterializedSetting {
location: "document".to_owned(),
setting: EngineSettingIdV1::RootMotionSource,
})
);
let mut changed = settings.clone();
changed.document_settings[0].value =
EngineSettingValueV1::BakeOrExtract(EngineBakeOrExtractV1::Bake);
assert_eq!(
changed.validate_against(&profile),
Err(EngineContractError::WrongSettingDomain {
location: "document".to_owned(),
setting: EngineSettingIdV1::BakeAxisConversion,
})
);
let mut changed = settings.clone();
changed.clips.push(
EngineClipSettingsV1::new(
"walk",
vec![EngineSettingRowV1::new(
EngineSettingIdV1::ConvertUnits,
EngineSettingValueV1::Boolean(true),
)],
)
.unwrap(),
);
assert_eq!(
changed.validate_against(&profile),
Err(EngineContractError::WrongSettingScope {
location: "clip[0]".to_owned(),
setting: EngineSettingIdV1::ConvertUnits,
})
);
let mut changed = settings.clone();
changed.document_settings.swap(0, 1);
assert_eq!(
changed.validate_against(&profile),
Err(EngineContractError::NonCanonicalOrder {
field: "settings.document_settings",
})
);
let mut changed = settings.clone();
changed.schema = "urn:changed".to_owned();
assert_eq!(
changed.validate_against(&profile),
Err(EngineContractError::InvalidSchema {
field: "settings.schema",
expected: RESOLVED_ENGINE_SETTINGS_V1_ID,
found: "urn:changed".to_owned(),
})
);
let mut changed = settings;
changed.identity = InputIdentity::from_bytes(b"changed");
assert_eq!(
changed.validate_against(&profile),
Err(EngineContractError::IdentityMismatch {
contract: RESOLVED_ENGINE_SETTINGS_V1_ID,
})
);
}
#[test]
fn clip_collection_bound_accepts_exact_n_and_rejects_n_plus_one() {
let profile = godot_profile();
let clips = (0..ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
.map(|_| EngineClipSettingsV1::new("same", vec![]).unwrap())
.collect();
let exact = ResolvedEngineSettingsV1::new(&profile, vec![], clips).unwrap();
assert_eq!(exact.clips().len(), ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS);
let clips = (0..=ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS)
.map(|_| EngineClipSettingsV1::new("same", vec![]).unwrap())
.collect();
assert_eq!(
ResolvedEngineSettingsV1::new(&profile, vec![], clips),
Err(EngineContractError::TooManyRows {
field: "settings.clips",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
})
);
}
#[test]
fn text_bound_accepts_exact_n_and_rejects_n_plus_one() {
let exact = "a".repeat(ENGINE_CONTRACT_V1_MAX_TEXT_BYTES);
EngineClipSettingsV1::new(
"clip",
vec![EngineSettingRowV1::new(
EngineSettingIdV1::RootMotionSource,
EngineSettingValueV1::SourceTransformPath(exact),
)],
)
.unwrap();
let oversized = "a".repeat(ENGINE_CONTRACT_V1_MAX_TEXT_BYTES + 1);
assert_eq!(
EngineClipSettingsV1::new(
"clip",
vec![EngineSettingRowV1::new(
EngineSettingIdV1::RootMotionSource,
EngineSettingValueV1::SourceTransformPath(oversized),
)],
),
Err(EngineContractError::TextTooLong {
field: "source_transform_path",
found: ENGINE_CONTRACT_V1_MAX_TEXT_BYTES + 1,
max: ENGINE_CONTRACT_V1_MAX_TEXT_BYTES,
})
);
}
#[test]
fn materialized_setting_mutations_name_the_violated_contract() {
let profile = settings_profile("mutations");
assert_eq!(
ResolvedEngineSettingsV1::new(
&profile,
vec![EngineSettingRowV1::new(
EngineSettingIdV1::ConvertUnits,
EngineSettingValueV1::Boolean(true),
)],
vec![],
),
Err(EngineContractError::MissingRequiredSetting {
location: "document".into(),
setting: EngineSettingIdV1::BakeAxisConversion,
})
);
assert_eq!(
ResolvedEngineSettingsV1::new(
&profile,
vec![
EngineSettingRowV1::new(
EngineSettingIdV1::ConvertUnits,
EngineSettingValueV1::BakeOrExtract(EngineBakeOrExtractV1::Bake),
),
EngineSettingRowV1::new(
EngineSettingIdV1::BakeAxisConversion,
EngineSettingValueV1::Boolean(true),
),
],
vec![],
),
Err(EngineContractError::WrongSettingDomain {
location: "document".into(),
setting: EngineSettingIdV1::ConvertUnits,
})
);
}
#[test]
fn source_format_and_input_identity_deserialization_are_closed() {
assert_eq!(
serde_json::from_str::<SourceFormatV1>("\"glb\"").unwrap(),
SourceFormatV1::Glb
);
assert!(serde_json::from_str::<SourceFormatV1>("\"obj\"").is_err());
let identity = InputIdentity::from_bytes(b"identity");
let wire = serde_json::to_string(&identity).unwrap();
assert_eq!(
serde_json::from_str::<InputIdentity>(&wire).unwrap(),
identity
);
let mut upper = serde_json::to_value(&identity).unwrap();
upper["sha256"] = json!("A".repeat(64));
assert!(serde_json::from_value::<InputIdentity>(upper).is_err());
}
#[test]
fn canonical_encoder_uses_length_prefixed_tokens() {
let mut encoder = CanonicalEncoder::new("domain");
encoder.field("field");
encoder.count(12);
encode_input_identity(&mut encoder, &InputIdentity::from_bytes(b"x"));
let bytes = encoder.into_bytes();
assert_eq!(&bytes[..8], &6_u64.to_be_bytes());
assert_eq!(&bytes[8..14], b"domain");
}
fn assert_profile_limit(value: &serde_json::Value, expected: EngineContractError) {
match decode_resolved_engine_profile_v1(&serde_json::to_string(value).unwrap()) {
Err(EngineContractDecodeError::Semantic(error)) => assert_eq!(error, expected),
other => panic!("expected typed profile limit, got {other:?}"),
}
}
fn assert_settings_limit(value: &serde_json::Value, expected: EngineContractError) {
match decode_resolved_engine_settings_v1_with_provenance_limit(
&serde_json::to_string(value).unwrap(),
ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
) {
Err(EngineSettingsLimitedDecodeError::Contract(
EngineContractDecodeError::Semantic(error),
)) => assert_eq!(error, expected),
_ => panic!("expected typed settings limit"),
}
}
#[test]
fn profile_sequences_reject_n_plus_one_before_decoding_null_sentinels() {
let profile = settings_profile("stream-profile");
let base = serde_json::to_value(&profile).unwrap();
for (field, element) in [
("facts", base["facts"][0].clone()),
(
"setting_descriptors",
base["setting_descriptors"][0].clone(),
),
("primary_sources", base["primary_sources"][0].clone()),
] {
let mut rows = vec![element; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
rows.push(serde_json::Value::Null);
let mut over = base.clone();
over[field] = rows.into();
assert_profile_limit(
&over,
EngineContractError::TooManyRows {
field: match field {
"facts" => "profile.facts",
"setting_descriptors" => "profile.setting_descriptors",
"primary_sources" => "profile.primary_sources",
_ => unreachable!(),
},
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
},
);
}
let mut accepted = vec![serde_json::json!("glb"); ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
accepted.push(serde_json::Value::Null);
let mut over = base.clone();
over["facts"][0]["state"]["known"]["accepted_formats"] = accepted.into();
assert_profile_limit(&over, EngineContractError::InvalidAcceptedInputs);
for (field, value) in [
("supported_fact_ids", serde_json::json!("accepted_inputs")),
("supported_setting_ids", serde_json::json!("convert_units")),
] {
let mut rows = vec![value; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
rows.push(serde_json::Value::Null);
let mut over = base.clone();
over["primary_sources"][0][field] = rows.into();
assert_profile_limit(
&over,
EngineContractError::TooManyRows {
field: if field == "supported_fact_ids" {
"primary_sources.supported_fact_ids"
} else {
"primary_sources.supported_setting_ids"
},
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
},
);
}
}
#[test]
fn settings_sequences_reject_n_plus_one_before_decoding_null_sentinels() {
let profile = godot_profile();
let settings = ResolvedEngineSettingsV1::new(&profile, vec![], vec![]).unwrap();
let base = serde_json::to_value(settings).unwrap();
let row = serde_json::json!({"id": "convert_units", "value": {"boolean": true}});
let clip = serde_json::json!({"clip_name": "clip", "settings": []});
for (field, element, error_field) in [
(
"document_settings",
row.clone(),
"settings.document_settings",
),
("clips", clip.clone(), "settings.clips"),
] {
let mut rows = vec![element; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
rows.push(serde_json::Value::Null);
let mut over = base.clone();
over[field] = rows.into();
assert_settings_limit(
&over,
EngineContractError::TooManyRows {
field: error_field,
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
},
);
}
let mut rows = vec![row; ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
rows.push(serde_json::Value::Null);
let mut over = base;
over["clips"] = serde_json::json!([{"clip_name": "clip", "settings": rows}]);
assert_settings_limit(
&over,
EngineContractError::TooManyRows {
field: "settings.clips.settings",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
},
);
}
#[test]
fn nested_profile_and_settings_aggregate_budgets_stop_at_global_n_plus_one() {
let profile = settings_profile("aggregate-profile");
let mut profile_wire = serde_json::to_value(profile).unwrap();
profile_wire["facts"] = serde_json::json!([]);
profile_wire["setting_descriptors"] = serde_json::json!([]);
let source_template = serde_json::json!({
"id": "source",
"target_version": "1",
"url": "https://example.invalid",
"verified_on": "2026-08-20",
"supported_fact_ids": vec![
"accepted_inputs";
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
],
"supported_setting_ids": vec![
"convert_units";
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
]
});
let mut sources = vec![source_template.clone(); 7];
let mut last = source_template;
last["supported_setting_ids"] = serde_json::json!(vec!["convert_units"; 4_088]);
last["supported_setting_ids"]
.as_array_mut()
.unwrap()
.push(serde_json::Value::Null);
sources.push(last);
profile_wire["primary_sources"] = sources.into();
assert_profile_limit(
&profile_wire,
EngineContractError::TooManyAggregateRows {
found: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
},
);
let mut locally_oversized =
vec![serde_json::json!("convert_units"); ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS];
locally_oversized.push(serde_json::Value::Null);
profile_wire["primary_sources"][7]["supported_setting_ids"] = locally_oversized.into();
assert_profile_limit(
&profile_wire,
EngineContractError::TooManyRows {
field: "primary_sources.supported_setting_ids",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
},
);
let profile = godot_profile();
let settings = ResolvedEngineSettingsV1::new(&profile, vec![], vec![]).unwrap();
let mut settings_wire = serde_json::to_value(settings).unwrap();
let setting = serde_json::json!({"id": "convert_units", "value": {"boolean": true}});
let full_clip = serde_json::json!({
"clip_name": "clip",
"settings": vec![setting.clone(); 4_095]
});
let mut clips = vec![full_clip; 15];
let mut last = serde_json::json!({
"clip_name": "clip",
"settings": vec![setting; 4_095]
});
last["settings"]
.as_array_mut()
.unwrap()
.push(serde_json::Value::Null);
clips.push(last);
settings_wire["clips"] = clips.into();
assert_settings_limit(
&settings_wire,
EngineContractError::TooManyAggregateRows {
found: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS,
},
);
let mut locally_oversized = vec![
serde_json::json!({"id": "convert_units", "value": {"boolean": true}});
ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS
];
locally_oversized.push(serde_json::Value::Null);
settings_wire["clips"][15]["settings"] = locally_oversized.into();
assert_settings_limit(
&settings_wire,
EngineContractError::TooManyRows {
field: "settings.clips.settings",
found: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS + 1,
max: ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
},
);
}
}