Skip to main content

rustev_contract/
plan_diff.rs

1//! Plan comparison wire document (spec 034, 3.12). Computing the diff is
2//! core's job; this module defines only its bounded, canonical shape.
3use crate::{
4    ids::{DefinitionId, PlanId},
5    plan::{ParameterSource, ProfileChain, Variant},
6    schema,
7};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11pub const MAX_CHANGED_LEAVES: usize = 64;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
14#[serde(deny_unknown_fields)]
15pub struct PlanDiff {
16    pub schema: String,
17    pub left: PlanSide,
18    pub right: PlanSide,
19    pub identical: bool,
20    pub parameters: Vec<ParameterChange>,
21    pub inputs: ItemChanges<String>,
22    pub steps: ItemChanges<String>,
23    pub rules: ItemChanges<String>,
24    pub adjustments: ItemChanges<String>,
25    pub handlers: ItemChanges<HandlerKey>,
26    pub order: Difference<Vec<String>>,
27    pub rule_order: Difference<Vec<String>>,
28    pub bindings: Vec<BindingChange>,
29    pub execution: Vec<FieldChange>,
30    pub limits: Vec<FieldChange>,
31    pub notices: NoticeChanges,
32    pub compiler: Difference<String>,
33    pub registry: Difference<String>,
34}
35// RECORD_V1's size/depth/count bounds, with binary64 forbidden by 3.12.1.
36crate::document::document!(
37    PlanDiff,
38    schema::PLAN_DIFF,
39    crate::bounded::ParseLimits {
40        allow_fractional_numbers: false,
41        ..crate::limits::RECORD_V1
42    }
43);
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
45#[serde(deny_unknown_fields)]
46pub struct PlanSide {
47    pub plan_id: PlanId,
48    pub plan_schema: String,
49    pub definition_id: DefinitionId,
50    pub template_id: TemplateIdentity,
51    pub variant: Variant,
52    pub profile: ProfileChain,
53}
54/// Template identity or the `none` sentinel (spec 034, 3.12.2).
55/// The identity parser accepts only `sha256:` followed by 64 lowercase hex digits.
56/// The TypeScript annotation preserves the disjoint prefix when schema patterns
57/// would otherwise become an unconstrained `string` in generated declarations.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
59#[serde(untagged)]
60#[schemars(extend("tsType" = "\"none\" | `sha256:${string}`"))]
61pub enum TemplateIdentity {
62    None(NoneIdentity),
63    Id(DefinitionId),
64}
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
66#[serde(rename_all = "snake_case")]
67pub enum NoneIdentity {
68    None,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
72#[serde(deny_unknown_fields)]
73pub struct ParameterChange {
74    pub name: String,
75    pub left: ParameterSide,
76    pub right: ParameterSide,
77}
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
79#[serde(untagged)]
80pub enum ParameterSide {
81    Absent(Absent),
82    Present(PresentParameter),
83}
84
85// Spec 034, 3.12.2: close the object itself and preserve its inline schema.
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
87#[serde(deny_unknown_fields)]
88#[schemars(inline)]
89pub struct PresentParameter {
90    pub value: Value,
91    pub source: ParameterSource,
92}
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
94#[serde(rename_all = "snake_case")]
95pub enum Absent {
96    Absent,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
100#[serde(deny_unknown_fields)]
101pub struct ItemChanges<K> {
102    pub added: Vec<K>,
103    pub removed: Vec<K>,
104    pub changed: Vec<ChangedItem<K>>,
105}
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
107#[serde(deny_unknown_fields)]
108pub struct ChangedItem<K> {
109    pub key: K,
110    pub leaves: Vec<String>,
111    pub truncated: bool,
112}
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
114#[serde(deny_unknown_fields)]
115pub struct HandlerKey {
116    pub target: String,
117    pub position: usize,
118}
119
120/// The literal `equal`, or the two differing values (spec 034, 3.12.2).
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
122#[serde(untagged)]
123pub enum Difference<T> {
124    Equal(Equal),
125    Changed(ChangedValues<T>),
126}
127
128// Spec 034, 3.12.2: close the object itself and preserve its inline schema.
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
130#[serde(deny_unknown_fields)]
131#[schemars(inline)]
132pub struct ChangedValues<T> {
133    pub left: T,
134    pub right: T,
135}
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
137#[serde(rename_all = "snake_case")]
138pub enum Equal {
139    Equal,
140}
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
142#[serde(deny_unknown_fields)]
143pub struct BindingChange {
144    pub step: String,
145    pub fields: Vec<FieldChange>,
146}
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
148#[serde(deny_unknown_fields)]
149pub struct FieldChange {
150    pub field: String,
151    pub left: Value,
152    pub right: Value,
153}
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
155#[serde(deny_unknown_fields)]
156pub struct NoticeChanges {
157    pub added: Vec<crate::judgment::Notice>,
158    pub removed: Vec<crate::judgment::Notice>,
159}