1use crate::canonical::{CanonicalError, CanonicalSerializer};
2use crate::{
3 Completeness, CompletenessProof, GraphRevision, NotModified, PlanError, PlanErrorCode,
4 RefactorOperation, RefactorPlan, RefactorPlanLimits, StatusCode, UncertainReference,
5 WarningCode,
6};
7use blazingly_json::Value;
8use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
9use sha2::{Digest, Sha256};
10use std::{fmt, io, io::Write as _, str::FromStr};
11
12pub const FINGERPRINT_ALGORITHM: &str = "weavatrix.refactor-plan.jcs-sha256.v1";
14
15const DOMAIN_SEPARATOR: &[u8] = b"weavatrix.refactor-plan.jcs-sha256.v1\0";
16
17#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub struct PlanFingerprint([u8; 32]);
20
21impl PlanFingerprint {
22 #[must_use]
23 pub const fn as_bytes(&self) -> &[u8; 32] {
24 &self.0
25 }
26
27 #[must_use]
28 pub fn to_hex(self) -> String {
29 let mut output = String::with_capacity(64);
30 for byte in self.0 {
31 use fmt::Write as _;
32 write!(&mut output, "{byte:02x}").expect("writing to String cannot fail");
33 }
34 output
35 }
36}
37
38impl fmt::Display for PlanFingerprint {
39 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40 formatter.write_str(&self.to_hex())
41 }
42}
43
44#[derive(Clone, Debug, Eq, PartialEq)]
46pub struct FingerprintParseError;
47
48impl fmt::Display for FingerprintParseError {
49 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50 formatter.write_str("fingerprint must be 64 lowercase hexadecimal characters")
51 }
52}
53
54impl std::error::Error for FingerprintParseError {}
55
56impl FromStr for PlanFingerprint {
57 type Err = FingerprintParseError;
58
59 fn from_str(value: &str) -> Result<Self, Self::Err> {
60 if value.len() != 64
61 || !value
62 .bytes()
63 .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
64 {
65 return Err(FingerprintParseError);
66 }
67 let mut bytes = [0_u8; 32];
68 for (index, output) in bytes.iter_mut().enumerate() {
69 let start = index * 2;
70 *output = u8::from_str_radix(&value[start..start + 2], 16)
71 .map_err(|_| FingerprintParseError)?;
72 }
73 Ok(Self(bytes))
74 }
75}
76
77impl Serialize for PlanFingerprint {
78 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
79 where
80 S: Serializer,
81 {
82 serializer.serialize_str(&self.to_hex())
83 }
84}
85
86impl<'de> Deserialize<'de> for PlanFingerprint {
87 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 struct FingerprintVisitor;
92
93 impl Visitor<'_> for FingerprintVisitor {
94 type Value = PlanFingerprint;
95
96 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter.write_str("a 64-character lowercase SHA-256 fingerprint")
98 }
99
100 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
101 where
102 E: serde::de::Error,
103 {
104 value.parse().map_err(E::custom)
105 }
106 }
107
108 deserializer.deserialize_str(FingerprintVisitor)
109 }
110}
111
112pub fn canonical_plan_bytes(plan: &RefactorPlan) -> Result<Vec<u8>, PlanError> {
114 canonical_plan_bytes_with_limits(plan, RefactorPlanLimits::default())
115}
116
117pub fn canonical_plan_bytes_with_limits(
119 plan: &RefactorPlan,
120 limits: RefactorPlanLimits,
121) -> Result<Vec<u8>, PlanError> {
122 crate::validation::validate_for_fingerprint(plan, limits)?;
123 let mut bytes = Vec::with_capacity(256);
124 write_canonical_top_level(&mut bytes, plan)?;
125 Ok(bytes)
126}
127
128pub fn fingerprint_plan(plan: &RefactorPlan) -> Result<PlanFingerprint, PlanError> {
130 fingerprint_plan_with_limits(plan, RefactorPlanLimits::default())
131}
132
133pub fn fingerprint_plan_with_limits(
135 plan: &RefactorPlan,
136 limits: RefactorPlanLimits,
137) -> Result<PlanFingerprint, PlanError> {
138 crate::validation::validate_for_fingerprint(plan, limits)?;
139 fingerprint_validated(plan)
140}
141
142pub(crate) fn fingerprint_validated(plan: &RefactorPlan) -> Result<PlanFingerprint, PlanError> {
143 let mut digest = Sha256::new();
144 digest.update(DOMAIN_SEPARATOR);
145 let mut writer = io::BufWriter::with_capacity(8 * 1024, DigestWriter(&mut digest));
146 write_canonical_top_level(&mut writer, plan)?;
147 writer.flush().map_err(|error| io_error(&error))?;
148 drop(writer);
149 Ok(PlanFingerprint(digest.finalize().into()))
150}
151
152fn write_canonical_top_level<W: io::Write>(
153 writer: W,
154 plan: &RefactorPlan,
155) -> Result<(), PlanError> {
156 let mut entries = top_level_entries(plan);
157 entries
158 .sort_unstable_by(|left, right| crate::canonical::compare_utf16_order(left.key, right.key));
159 write_sorted_entries(writer, &entries).map_err(|error| canonical_error(&error))
160}
161
162fn write_sorted_entries<W: io::Write>(
163 writer: W,
164 entries: &[Entry<'_>],
165) -> Result<(), CanonicalError> {
166 let mut serializer = CanonicalSerializer::new(writer);
167 serializer.write_punctuation(b"{")?;
168 for (index, entry) in entries.iter().enumerate() {
169 if index != 0 {
170 serializer.write_punctuation(b",")?;
171 }
172 serializer.write_string(entry.key)?;
173 serializer.write_punctuation(b":")?;
174 serializer.write_value(&entry.value)?;
175 }
176 serializer.write_punctuation(b"}")
177}
178
179fn top_level_entries(plan: &RefactorPlan) -> Vec<Entry<'_>> {
180 let mut entries = vec![
181 Entry::new("schemaVersion", TopValue::String(&plan.schema_version)),
182 Entry::new("operation", TopValue::String(&plan.operation)),
183 Entry::new("operations", TopValue::Operations(&plan.operations)),
184 ];
185 if let Some(value) = &plan.completeness {
186 entries.push(Entry::new("completeness", TopValue::Completeness(value)));
187 }
188 push_evidence_entries(&mut entries, plan);
189 for (key, value) in &plan.evidence.extensions {
190 entries.push(Entry::new(key, TopValue::Extension(value)));
191 }
192 entries
193}
194
195fn push_evidence_entries<'a>(entries: &mut Vec<Entry<'a>>, plan: &'a RefactorPlan) {
196 let evidence = &plan.evidence;
197 if !evidence.graph_revision.is_missing() {
198 entries.push(Entry::new(
199 "graphRevision",
200 TopValue::GraphRevision(&evidence.graph_revision),
201 ));
202 }
203 if let Some(value) = &evidence.completeness_proof {
204 entries.push(Entry::new("completenessProof", TopValue::Proof(value)));
205 }
206 if let Some(value) = &evidence.uncertain_references {
207 entries.push(Entry::new(
208 "uncertainReferences",
209 TopValue::Uncertain(value),
210 ));
211 }
212 if let Some(value) = &evidence.not_modified {
213 entries.push(Entry::new("notModified", TopValue::Omitted(value)));
214 }
215 if let Some(value) = &evidence.warnings {
216 entries.push(Entry::new("warnings", TopValue::Warnings(value)));
217 }
218 if let Some(value) = &evidence.follow_up {
219 entries.push(Entry::new("followUp", TopValue::String(value)));
220 }
221 if let Some(value) = &evidence.syntax_check {
222 entries.push(Entry::new("syntaxCheck", TopValue::Status(value)));
223 }
224}
225
226struct Entry<'a> {
227 key: &'a str,
228 value: TopValue<'a>,
229}
230
231impl<'a> Entry<'a> {
232 const fn new(key: &'a str, value: TopValue<'a>) -> Self {
233 Self { key, value }
234 }
235}
236
237enum TopValue<'a> {
238 String(&'a str),
239 Operations(&'a [RefactorOperation]),
240 Completeness(&'a Completeness),
241 GraphRevision(&'a GraphRevision),
242 Proof(&'a CompletenessProof),
243 Uncertain(&'a [UncertainReference]),
244 Omitted(&'a [NotModified]),
245 Warnings(&'a [WarningCode]),
246 Status(&'a StatusCode),
247 Extension(&'a Value),
248}
249
250impl Serialize for TopValue<'_> {
251 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
252 where
253 S: Serializer,
254 {
255 match self {
256 Self::String(value) => value.serialize(serializer),
257 Self::Operations(value) => value.serialize(serializer),
258 Self::Completeness(value) => value.serialize(serializer),
259 Self::GraphRevision(value) => value.serialize(serializer),
260 Self::Proof(value) => value.serialize(serializer),
261 Self::Uncertain(value) => value.serialize(serializer),
262 Self::Omitted(value) => value.serialize(serializer),
263 Self::Warnings(value) => value.serialize(serializer),
264 Self::Status(value) => value.serialize(serializer),
265 Self::Extension(value) => value.serialize(serializer),
266 }
267 }
268}
269
270struct DigestWriter<'a>(&'a mut Sha256);
271
272impl io::Write for DigestWriter<'_> {
273 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
274 self.0.update(buffer);
275 Ok(buffer.len())
276 }
277
278 fn flush(&mut self) -> io::Result<()> {
279 Ok(())
280 }
281}
282
283fn canonical_error(error: &CanonicalError) -> PlanError {
284 PlanError::new(
285 PlanErrorCode::JsonEncoding,
286 format!("could not encode canonical plan JSON: {error}"),
287 )
288}
289
290fn io_error(error: &io::Error) -> PlanError {
291 PlanError::new(
292 PlanErrorCode::JsonEncoding,
293 format!("could not write canonical plan JSON: {error}"),
294 )
295}