client_side_validation/api.rs
1// Client-side-validation foundation libraries.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 LNP/BP Laboratories,
10// Institute for Distributed and Cognitive Systems
11// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not
15// use this file except in compliance with the License. You may obtain a copy of
16// the License at
17//
18// http://www.apache.org/licenses/LICENSE-2.0
19//
20// Unless required by applicable law or agreed to in writing, software
21// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
22// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
23// License for the specific language governing permissions and limitations under
24// the License.
25
26use std::fmt::{self, Debug, Display, Formatter};
27use std::hash::Hash;
28use std::ops::AddAssign;
29
30/// Result of client-side validation operation
31#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
32#[repr(u8)]
33pub enum Validity {
34 /// The data are valid both in terms of the internal consistency and in
35 /// terms of commitments in the single-use-seals medium
36 Valid = 1,
37
38 /// The data are internally consistent and valid, but there were (multiple)
39 /// issues with resolving single-use-seals reported by the provided seal
40 /// resolver. These issues are not failed single-use-seals, but rather
41 /// errors accessing single-use-seals commitment medium about its status
42 /// (networking issues, transaction present in mempool and
43 /// not yet mined etc).
44 SealIssues = 0xFF,
45
46 /// The data are internally inconsistent/invalid
47 Invalid = 0,
48}
49
50impl Display for Validity {
51 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
52 match self {
53 Validity::Valid => f.write_str("valid"),
54 Validity::SealIssues => f.write_str("unresolved seal issues"),
55 Validity::Invalid => f.write_str("invalid"),
56 }
57 }
58}
59
60#[cfg(not(feature = "serde"))]
61/// Marker trait for all types of validation log entries (failures, trust
62/// issues, warnings, info messages) contained within a [`ValidationReport`]
63/// produced during client-side-validation.
64pub trait ValidationLog: Clone + Eq + Hash + Debug + Display {}
65
66#[cfg(feature = "serde")]
67/// Marker trait for all types of validation log entries (failures, trust
68/// issues, warnings, info messages) contained within a [`ValidationReport`]
69/// produced during client-side-validation.
70pub trait ValidationLog:
71 Clone + Eq + Hash + Debug + Display + serde::Serialize + for<'de> serde::Deserialize<'de>
72{
73}
74
75/// Trait for concrete implementations of seal resolution issues reported by
76/// [`SealResolver`]s during client-side-validation process
77pub trait SealIssue: ValidationLog + std::error::Error {
78 /// Type defining single-use-seals used by the client-side-validated data
79 /// and the seal resolver
80 type Seal;
81
82 /// Method returning single-use-seal specific to the reported issue
83 fn seal(&self) -> &Self::Seal;
84}
85
86/// Validation failures marker trait indicating that the data had not passed
87/// client-side-validation and must not be accepted by the client. This does not
88/// cover issues related to single-use-seal status, which are covered by
89/// [`SealIssue`] type
90pub trait ValidationFailure: ValidationLog + std::error::Error {}
91
92/// Trait combining different forms of client-side-validation reporting as into
93/// a single type pack
94pub trait ValidationReport {
95 /// Reports on seal resolution issues, for instance produced by failing
96 /// accessing the single-use-seals or its medium or inability to
97 /// determine whether a given seal was closed.
98 type SealIssue: SealIssue;
99
100 /// Internal client-side-validated data inconsistency/invalidity codes and
101 /// reports
102 type Failure: ValidationFailure;
103
104 #[cfg(not(feature = "serde"))]
105 /// Issues which does not render client-side-validated data invalid, but
106 /// which should be reported to the user anyway
107 type Warning: Clone + Eq + Hash + Debug + Display;
108
109 #[cfg(feature = "serde")]
110 /// Issues which does not render client-side-validated data invalid, but
111 /// which should be reported to the user anyway
112 type Warning: Clone
113 + Eq
114 + Hash
115 + Debug
116 + Display
117 + serde::Serialize
118 + for<'de> serde::Deserialize<'de>;
119
120 #[cfg(not(feature = "serde"))]
121 /// Information reports about client-side-validation, which do not affect
122 /// data safety or validity and may not be presented to the user
123 type Info: Clone + Eq + Hash + Debug + Display;
124
125 #[cfg(feature = "serde")]
126 /// Information reports about client-side-validation, which do not affect
127 /// data safety or validity and may not be presented to the user
128 type Info: Clone
129 + Eq
130 + Hash
131 + Debug
132 + Display
133 + serde::Serialize
134 + for<'de> serde::Deserialize<'de>;
135}
136
137/// Client-side-validation status containing all reports from the validation
138/// process
139#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
140#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
141pub struct Status<R>
142where R: ValidationReport
143{
144 /// List of seal resolver reported issues (see [`SealIssue`] trait for
145 /// details).
146 pub seal_issues: Vec<R::SealIssue>,
147
148 /// Failures generated during client-side-validation.
149 ///
150 /// When the failure happens, the process of client-side-validation is not
151 /// stopped and proceeds to the rest of data items, such that there might
152 /// be a multiple validation failures stored in this array.
153 ///
154 /// Does not include issues from single-use-seal resolution, which are
155 /// stored in [`Status::seal_issues`] and must be handled separately.
156 pub failures: Vec<R::Failure>,
157
158 /// Warnings generated during client-side-validation.
159 ///
160 /// Warnings are issues which does not render client-side-validated data
161 /// invalid, but which should be reported to the user anyway
162 ///
163 /// See also [`ValidationReport::Warning`].
164 pub warnings: Vec<R::Warning>,
165
166 /// Information reports about client-side-validation, which do not affect
167 /// data safety or validity and may not be presented to the user
168 ///
169 /// See also [`ValidationReport::Info`].
170 pub info: Vec<R::Info>,
171}
172
173impl<R> AddAssign for Status<R>
174where R: ValidationReport
175{
176 fn add_assign(&mut self, rhs: Self) {
177 self.seal_issues.extend(rhs.seal_issues);
178 self.failures.extend(rhs.failures);
179 self.warnings.extend(rhs.warnings);
180 self.info.extend(rhs.info);
181 }
182}
183
184impl<R> FromIterator<R::Failure> for Status<R>
185where R: ValidationReport
186{
187 fn from_iter<T: IntoIterator<Item = R::Failure>>(iter: T) -> Self {
188 Status {
189 seal_issues: vec![],
190 failures: iter.into_iter().collect(),
191 warnings: vec![],
192 info: vec![],
193 }
194 }
195}
196
197impl<R> Status<R>
198where R: ValidationReport
199{
200 /// Constructs empty status report
201 pub fn new() -> Self {
202 Status {
203 seal_issues: vec![],
204 failures: vec![],
205 warnings: vec![],
206 info: vec![],
207 }
208 }
209
210 /// Constructs status report from a single failure with the rest of log
211 /// lists set to the empty state.
212 pub fn from_failure(failure: R::Failure) -> Self {
213 Status {
214 seal_issues: vec![],
215 failures: vec![failure],
216 warnings: vec![],
217 info: vec![],
218 }
219 }
220
221 /// Adds a single [`SealIssue`] to the validation report logs
222 pub fn add_seal_issue(&mut self, seal_issue: R::SealIssue) -> &Self {
223 self.seal_issues.push(seal_issue);
224 self
225 }
226
227 /// Adds a single [`ValidationFailure`] to the validation report logs
228 pub fn add_failure(&mut self, failure: R::Failure) -> &Self {
229 self.failures.push(failure);
230 self
231 }
232
233 /// Adds a single warning entry to the validation report logs. See
234 /// [`ValidationReport::Warning`] for more details about warnings.
235 pub fn add_warning(&mut self, warning: R::Warning) -> &Self {
236 self.warnings.push(warning);
237 self
238 }
239
240 /// Adds a single information record to the validation report logs. See
241 /// [`ValidationReport::Info`] for more details about information log
242 /// entries.
243 pub fn add_info(&mut self, info: R::Info) -> &Self {
244 self.info.push(info);
245 self
246 }
247
248 /// Returns validity of the client-side data deduced from the current status
249 /// containing all reported issues.
250 ///
251 /// Client-side data are valid ([`Validity::Valid`] status) only and only if
252 /// the status report contains no validation failures and seal resolution
253 /// issues.
254 ///
255 /// See also [`Validity`] for the details of possible validation statuses.
256 pub fn validity(&self) -> Validity {
257 if !self.failures.is_empty() {
258 Validity::Invalid
259 } else if !self.seal_issues.is_empty() {
260 Validity::SealIssues
261 } else {
262 Validity::Valid
263 }
264 }
265}
266
267/// This simple trait MUST be used by all top-level data structures implementing
268/// client-side validation paradigm. The core concept of this paradigm is that a
269/// client must have a complete and uniform set of data, which can be
270/// represented or accessed through a single structure; and MUST be able to
271/// deterministically validate this set giving an external validation function,
272/// that is able to provide validator with
273pub trait ClientSideValidate<'client_data>: ClientData<'client_data>
274where Self::ValidationItem: 'client_data
275{
276 /// Data type for data sub-entries contained withing the current
277 /// client-side-validated data item.
278 ///
279 /// If the client-side-validated data contain different types of internal
280 /// entries, this may be a special enum type with a per-data-type variant.
281 ///
282 /// If the data do not contain internal data, set this type to `()`.
283 type ValidationItem: ClientData<'client_data, ValidationReport = Self::ValidationReport>;
284
285 /// Iterator over the list of specific validation items.
286 ///
287 /// If the client-side-validated data contain different types of internal
288 /// entries, this may be a special enum type with a per-data-type variant.
289 type ValidationIter: Iterator<Item = &'client_data Self::ValidationItem>;
290
291 /// The mein method performing client-side-validation for the whole block of
292 /// client-side-validated data.
293 ///
294 /// The default implementation of the trait iterates over
295 /// client-side-validated data hierarchy using iterator returned by
296 /// [`ClientSideValidate::validation_iter`] and for each of the items
297 /// - validates internal data consistency with
298 /// [`ClientData::validate_internal_consistency`] method,
299 /// - validates single-use-seal for the item using the provided `resolver`
300 /// object, adding reported issues to the [`Status`] log returned by the
301 /// function.
302 ///
303 /// The function should not fail on any validation failures and run the
304 /// whole validation process up to the end, accumulating all failures and
305 /// reported issues withing [`Status`] object, returned by the function at
306 /// the end.
307 fn client_side_validate<Resolver>(
308 &'client_data self,
309 resolver: &'client_data mut Resolver,
310 ) -> Status<Self::ValidationReport>
311 where
312 Resolver: SealResolver<
313 <<<Self as ClientData<'client_data>>::ValidationReport as ValidationReport>::SealIssue as SealIssue>::Seal,
314 Error = <<Self as ClientData<'client_data>>::ValidationReport as ValidationReport>::SealIssue,
315 >,
316 {
317 let mut status = Status::new();
318
319 status += self.validate_internal_consistency();
320 for item in self.validation_iter() {
321 for seal in item.single_use_seals() {
322 let _ = resolver
323 .resolve_trust(seal)
324 .map_err(|issue| status.add_seal_issue(issue));
325 }
326 status += item.validate_internal_consistency();
327 }
328
329 status
330 }
331
332 /// Returns iterator over hierarchy of individual data items inside
333 /// client-side-validation data.
334 fn validation_iter(&'client_data self) -> Self::ValidationIter;
335}
336
337/// Marker trait for client-side-validation data at any level of data hierarchy.
338pub trait ClientData<'client_data>
339where Self: 'client_data
340{
341 /// Data type that stores validation report configuration for the validation
342 /// [`Status`] object.
343 ///
344 /// This type defines also a type of single-use-seals via
345 /// [`ValidationReport::SealIssue`]`<Seal>`.
346 type ValidationReport: ValidationReport;
347
348 /// Iterator over single-use-seals belonging to a specific validation item.
349 type SealIterator: Iterator<Item = &'client_data <<Self::ValidationReport as ValidationReport>::SealIssue as SealIssue>::Seal>;
350
351 /// Method returning iterator over single-use-seal references corresponding
352 /// to the current piece of client-side-validated data.
353 fn single_use_seals(&'client_data self) -> Self::SealIterator;
354
355 /// Validates internal consistency of the current client-side-validated data
356 /// item. Must not validate any single-use-seals or commitments against
357 /// external commitment mediums.
358 ///
359 /// The method should not iterate over internal data items and go deeper
360 /// inside the data hierarchy and must validate only data related to the
361 /// single current item. The iteration is performed at higher levels,
362 /// normally as a part of [`ClientSideValidate::client_side_validate`]
363 /// method logic.
364 fn validate_internal_consistency(&'client_data self) -> Status<Self::ValidationReport>;
365}
366
367/// Seal resolver validates seal to have `closed` status, or reports
368/// [`SealResolver::Error`] otherwise, if the seal does not have a determined
369/// status or there was a error accessing seal commitment medium. The reported
370/// error does not necessary implies that the seal is not closed and the final
371/// decision about seal status must be solved at upper protocol levels or by a
372/// informed user action.
373///
374/// Seal resolution MUST always produce a singular success type (defined by
375/// `()`) or fail with a well-defined type of [`SealResolver::Error`].
376///
377/// Seal resolver may have an internal state (represented by `self` reference)
378/// and it does not require to produce a deterministic result for the same
379/// given data piece and context: the seal resolver may depend on previous
380/// operation history and depend on type and other external parameters.
381pub trait SealResolver<Seal> {
382 /// Error type returned by [`SealResolver::resolve_trust`], which should
383 /// cover both errors in accessing single-use-seal medium (like network
384 /// connectivity) or evidences of the facts the seal was not (yet) closed.
385 type Error: SealIssue<Seal = Seal>;
386
387 /// Resolves trust to the provided single-use-seal.
388 ///
389 /// The method mutates resolver such that it can be able to store cached
390 /// data from a single-use-seal medium.
391 ///
392 /// Method must fail on both errors in accessing single-use-seal medium
393 /// (like network connectivity) or if the seal is not (yet) closed.
394 fn resolve_trust(&mut self, seal: &Seal) -> Result<(), Self::Error>;
395}