Skip to main content

animsmith_engine/
error.rs

1use crate::{
2    ProfileSelection, SettingDomain, SettingId, SettingScope, SettingValue, SettingValueKind,
3};
4use animsmith_core::SourceFormatV1;
5use animsmith_core::engine_contract::EngineContractError;
6use std::fmt;
7
8/// Location of one settings declaration.
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
10pub enum SettingLocation {
11    /// Document-wide engine settings.
12    Document,
13    /// Settings declared under one clip selector.
14    ClipSelector(String),
15}
16
17impl fmt::Display for SettingLocation {
18    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
19        match self {
20            Self::Document => formatter.write_str("document settings"),
21            Self::ClipSelector(selector) => write!(formatter, "clip selector {selector:?}"),
22        }
23    }
24}
25
26/// Why a closed setting value failed its declared domain.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum InvalidSettingReason {
29    /// The value variant does not match the descriptor domain.
30    WrongDomain {
31        /// Required descriptor domain.
32        expected: SettingDomain,
33        /// Supplied closed value kind.
34        found: SettingValueKind,
35    },
36    /// The transform path is empty.
37    EmptyPath,
38    /// The transform path exceeds the V1 UTF-8 byte limit.
39    PathTooLong {
40        /// Actual UTF-8 byte count.
41        bytes: usize,
42        /// Maximum UTF-8 byte count.
43        limit: usize,
44    },
45    /// The transform path begins with `/` and is not relative.
46    AbsolutePath,
47    /// The transform path contains an empty segment.
48    EmptyPathSegment,
49    /// The transform path contains `.` or `..`.
50    DotPathSegment,
51    /// The transform path contains an ASCII control character.
52    ControlCharacter,
53}
54
55/// Typed failure from static or input-dependent engine resolution.
56#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
57#[non_exhaustive]
58pub enum ResolutionError {
59    /// Settings were declared without selecting an engine profile.
60    #[error("engine settings were declared without an engine profile selection")]
61    SettingsWithoutSelection,
62    /// No exact registry tuple matches the selection.
63    #[error("unknown engine profile selection {0:?}")]
64    UnknownProfile(ProfileSelection),
65    /// A setting key is not in the selected profile's descriptor inventory.
66    #[error("unknown engine setting {key:?} in {location}")]
67    UnknownSetting {
68        /// Supplied setting key.
69        key: String,
70        /// Declaration location.
71        location: SettingLocation,
72    },
73    /// A known setting was declared in the wrong scope.
74    #[error(
75        "engine setting {setting:?} has {expected:?} scope but was declared in {found:?} scope"
76    )]
77    WrongScope {
78        /// Stable setting id.
79        setting: SettingId,
80        /// Descriptor scope.
81        expected: SettingScope,
82        /// Supplied scope.
83        found: SettingScope,
84        /// Declaration location.
85        location: SettingLocation,
86    },
87    /// A known descriptor genuinely does not apply to the selected profile.
88    #[error("engine setting {setting:?} is not applicable in {location}")]
89    NotApplicable {
90        /// Stable setting id.
91        setting: SettingId,
92        /// Declaration location.
93        location: SettingLocation,
94    },
95    /// A setting value is outside its closed descriptor domain.
96    #[error("invalid value for engine setting {setting:?} in {location}: {reason:?}")]
97    InvalidSettingValue {
98        /// Stable setting id.
99        setting: SettingId,
100        /// Declaration location.
101        location: SettingLocation,
102        /// Exact domain/path failure.
103        reason: InvalidSettingReason,
104    },
105    /// One required-without-default setting was not materialized.
106    #[error("missing required engine setting {setting:?} in {location}")]
107    MissingRequiredSetting {
108        /// Stable setting id.
109        setting: SettingId,
110        /// Document or exact real clip requiring the setting.
111        location: SettingLocation,
112    },
113    /// The authoritative input container is outside the selected V1 boundary.
114    #[error("input format {format:?} is not accepted by engine profile {selection:?}")]
115    UnacceptedInputFormat {
116        /// Selected exact profile tuple.
117        selection: ProfileSelection,
118        /// Authoritative loader-owned input format.
119        format: SourceFormatV1,
120    },
121    /// Fully materialized settings exceeded or contradicted the bounded V1
122    /// engine contract.
123    #[error("resolved engine settings are outside the V1 contract: {0}")]
124    ResolvedSettingsContract(#[from] EngineContractError),
125}
126
127/// Typed failure while binding an engine-owned check to immutable evidence.
128#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
129#[non_exhaustive]
130pub enum PredictionRuleError {
131    /// The supplied provenance was not projected from the supplied same-load
132    /// source facts and dependency closure.
133    #[error("prediction provenance does not describe the supplied same-load source")]
134    SourceProvenanceMismatch,
135    /// An exact built-in profile tuple carried facts other than the immutable
136    /// registry record for that revision.
137    #[error("prediction provenance does not match the frozen engine profile facts")]
138    FrozenProfileMismatch,
139}
140
141/// Author-owned invariant failure in the built-in V1 registry.
142#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
143#[non_exhaustive]
144pub enum RegistryValidationError {
145    /// V1 did not contain exactly five singleton profiles.
146    #[error("V1 registry contains {found} profiles rather than five")]
147    ProfileCount {
148        /// Actual profile count.
149        found: usize,
150    },
151    /// Two records use the same exact selection.
152    #[error("duplicate profile selection {selection:?}")]
153    DuplicateSelection {
154        /// Repeated selection.
155        selection: ProfileSelection,
156    },
157    /// Two records use the same revisioned fact-bundle URN.
158    #[error("duplicate fact-bundle URN {urn}")]
159    DuplicateFactBundleUrn {
160        /// Repeated URN.
161        urn: &'static str,
162    },
163    /// The record does not enumerate every stable V1 fact id exactly once.
164    #[error("invalid fact inventory for {selection:?}")]
165    InvalidFactInventory {
166        /// Affected selection.
167        selection: ProfileSelection,
168    },
169    /// The accepted-input fact is not a nonempty canonical known-format set.
170    #[error("invalid accepted-input fact for {selection:?}")]
171    InvalidAcceptedInputFact {
172        /// Affected selection.
173        selection: ProfileSelection,
174    },
175    /// A setting descriptor id is repeated.
176    #[error("duplicate setting descriptor for {selection:?}")]
177    DuplicateSettingDescriptor {
178        /// Affected selection.
179        selection: ProfileSelection,
180    },
181    /// Applicability and default status contradict each other.
182    #[error("invalid default status for {setting:?} in {selection:?}")]
183    InvalidDescriptorDefault {
184        /// Affected selection.
185        selection: ProfileSelection,
186        /// Affected setting.
187        setting: SettingId,
188    },
189    /// A primary-source id is repeated within one profile.
190    #[error("duplicate source id {source_id} in {selection:?}")]
191    DuplicateSourceId {
192        /// Affected selection.
193        selection: ProfileSelection,
194        /// Repeated source id.
195        source_id: &'static str,
196    },
197    /// A source references a fact outside the record vocabulary.
198    #[error("source {source_id} references unknown fact {fact:?} in {selection:?}")]
199    UnknownSourceFact {
200        /// Affected selection.
201        selection: ProfileSelection,
202        /// Source id.
203        source_id: &'static str,
204        /// Unknown fact id.
205        fact: crate::FactId,
206    },
207    /// A source claims support for a fact whose record state is not known.
208    #[error("source {source_id} references non-known fact {fact:?} in {selection:?}")]
209    SourceReferencesNonKnownFact {
210        /// Affected selection.
211        selection: ProfileSelection,
212        /// Source id.
213        source_id: &'static str,
214        /// Fact without a known value.
215        fact: crate::FactId,
216    },
217    /// A source references a setting absent from the record.
218    #[error("source {source_id} references unknown setting {setting:?} in {selection:?}")]
219    UnknownSourceSetting {
220        /// Affected selection.
221        selection: ProfileSelection,
222        /// Source id.
223        source_id: &'static str,
224        /// Missing setting id.
225        setting: SettingId,
226    },
227    /// A known fact has no supporting primary-source reference.
228    #[error("known fact {fact:?} has no source reference in {selection:?}")]
229    UnreferencedKnownFact {
230        /// Affected selection.
231        selection: ProfileSelection,
232        /// Unsupported known fact.
233        fact: crate::FactId,
234    },
235    /// A descriptor has no supporting primary-source reference.
236    #[error("setting descriptor {setting:?} has no source reference in {selection:?}")]
237    UnreferencedSetting {
238        /// Affected selection.
239        selection: ProfileSelection,
240        /// Unsupported descriptor.
241        setting: SettingId,
242    },
243    /// The stored record identity differs from a fresh canonical encoding.
244    #[error("facts identity mismatch for {selection:?}")]
245    FactsIdentityMismatch {
246        /// Affected selection.
247        selection: ProfileSelection,
248    },
249}
250
251impl SettingValue {
252    pub(crate) const fn kind(&self) -> SettingValueKind {
253        match self {
254            SettingValue::Boolean(_) => SettingValueKind::Boolean,
255            SettingValue::BakeOrExtract(_) => SettingValueKind::BakeOrExtract,
256            SettingValue::SourceTransformPath(_) => SettingValueKind::SourceTransformPath,
257        }
258    }
259}