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}