From a9a45b2141bc461d0a7321f96ec0a7dcca20c028 Mon Sep 17 00:00:00 2001 From: Aleksandr Petrosyan Date: Wed, 20 Apr 2022 09:29:06 +0000 Subject: [PATCH] [schema] #2108: Add pagination (#2107) Signed-off-by: Aleksandr Petrosyan --- cli/src/torii/routing.rs | 24 ++++++++++++++----- cli/src/torii/tests.rs | 7 ++++-- client/src/client.rs | 6 +++-- config/derive/src/lib.rs | 2 +- config/src/lib.rs | 8 +++---- config/src/runtime_upgrades.rs | 3 ++- core/src/block.rs | 14 +++++------ core/src/smartcontracts/wasm.rs | 22 ++++++++--------- core/src/sumeragi/message.rs | 4 ++-- core/src/sumeragi/network_topology.rs | 12 +++++----- core/src/triggers.rs | 4 ++-- crypto/src/merkle.rs | 18 +++++++------- crypto/src/signature.rs | 8 +++---- data_model/src/lib.rs | 17 ++++++++----- data_model/src/pagination.rs | 7 +++++- data_model/src/query.rs | 20 +++++++++++++--- docs/source/references/api_spec.md | 2 +- schema/bin/src/lib.rs | 2 +- schema/derive/src/lib.rs | 4 ++-- .../parity_scale_decoder/src/generate_map.rs | 3 +++ version/src/lib.rs | 2 +- 21 files changed, 117 insertions(+), 72 deletions(-) diff --git a/cli/src/torii/routing.rs b/cli/src/torii/routing.rs index d72a55eeaf7..cef25eae20e 100644 --- a/cli/src/torii/routing.rs +++ b/cli/src/torii/routing.rs @@ -2,6 +2,8 @@ //! Iroha you should add it here by creating a `handle_*` function, //! and add it to impl Torii. This module also defines the `VerifiedQueryRequest`, //! which is the only kind of query that is permitted to execute. +use std::num::TryFromIntError; + use eyre::WrapErr; use iroha_actor::Addr; use iroha_config::{Configurable, GetConfiguration, PostConfiguration}; @@ -95,7 +97,7 @@ pub(crate) async fn handle_instructions( #[allow(clippy::map_err_ignore)] let push_result = queue.push(transaction).map_err(|(_, err)| err); if let Err(ref error) = push_result { - iroha_logger::warn!(%error, "Failed to push to queue") + iroha_logger::warn!(%error, "Failed to push into queue") } push_result .map_err(Box::new) @@ -109,17 +111,27 @@ pub(crate) async fn handle_queries( query_validator: Arc>, pagination: Pagination, request: VerifiedQueryRequest, -) -> Result, warp::http::Response> { +) -> Result, warp::http::Response> { let valid_request = request .validate(&wsv, &query_validator) .map_err(into_reply)?; - let result = valid_request.execute(&wsv).map_err(into_reply)?; - let result = QueryResult(if let Value::Vec(value) = result { + let original_result = valid_request.execute(&wsv).map_err(into_reply)?; + let total: u64 = original_result + .len() + .try_into() + .map_err(|e: TryFromIntError| QueryError::Conversion(e.to_string())) + .map_err(into_reply)?; + let result = QueryResult(if let Value::Vec(value) = original_result { Value::Vec(value.into_iter().paginate(pagination).collect()) } else { - result + original_result }); - Ok(Scale(result.into())) + let paginated_result = PaginatedQueryResult { + result, + pagination, + total, + }; + Ok(Scale(paginated_result.into())) } #[allow(clippy::needless_pass_by_value)] // Required for `map_err`. diff --git a/cli/src/torii/tests.rs b/cli/src/torii/tests.rs index 74b186fbb58..47d68706208 100644 --- a/cli/src/torii/tests.rs +++ b/cli/src/torii/tests.rs @@ -105,8 +105,11 @@ async fn torii_pagination() { ) .map(|result| { let Scale(query_result) = result.unwrap(); - if let VersionedQueryResult::V1(QueryResult(Value::Vec(domain))) = query_result { - domain + let VersionedPaginatedQueryResult::V1(PaginatedQueryResult { result, .. }) = + query_result; + + if let QueryResult(Value::Vec(domains)) = result { + domains } else { unreachable!() } diff --git a/client/src/client.rs b/client/src/client.rs index ed29cd1c0b8..61b40c59eb7 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -343,8 +343,10 @@ impl Client { std::str::from_utf8(response.body()).unwrap_or(""), )); } - let result = VersionedQueryResult::decode_versioned(response.body())?; - let VersionedQueryResult::V1(QueryResult(result)) = result; + let result = VersionedPaginatedQueryResult::decode_versioned(response.body())?; + + let VersionedPaginatedQueryResult::V1(PaginatedQueryResult { result, .. }) = result; + let QueryResult(result) = result; R::Output::try_from(result) .map_err(Into::into) .wrap_err("Unexpected type") diff --git a/config/derive/src/lib.rs b/config/derive/src/lib.rs index 0c1a9dd7b10..a0aa16feae1 100644 --- a/config/derive/src/lib.rs +++ b/config/derive/src/lib.rs @@ -22,7 +22,7 @@ mod attrs { pub const INNER: &str = "inner"; } -fn get_type_argument<'a, 'b>(s: &'a str, ty: &'b Type) -> Option<&'b GenericArgument> { +fn get_type_argument<'sl, 'tl>(s: &'sl str, ty: &'tl Type) -> Option<&'tl GenericArgument> { let path = if let Type::Path(r#type) = ty { r#type } else { diff --git a/config/src/lib.rs b/config/src/lib.rs index 1ce41d754e3..008bb6bc555 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -172,9 +172,9 @@ pub trait Configurable: Serialize + DeserializeOwned { /// Gets inner field of arbitrary inner depth and returns as json-value /// # Errors /// Fails if field was unknown - fn get_recursive<'a, T>(&self, inner_field: T) -> Result + fn get_recursive<'tl, T>(&self, inner_field: T) -> Result where - T: AsRef<[&'a str]> + Send + 'a; + T: AsRef<[&'tl str]> + Send + 'tl; /// Fails if fails to deserialize from environment /// # Errors @@ -184,8 +184,8 @@ pub trait Configurable: Serialize + DeserializeOwned { /// Gets docs of inner field of arbitrary depth /// # Errors /// Fails if field was unknown - fn get_doc_recursive<'a>( - field: impl AsRef<[&'a str]>, + fn get_doc_recursive<'tl>( + field: impl AsRef<[&'tl str]>, ) -> Result, Self::Error>; /// Gets docs of field diff --git a/config/src/runtime_upgrades.rs b/config/src/runtime_upgrades.rs index 08b662bc4ad..f7ff951fcff 100644 --- a/config/src/runtime_upgrades.rs +++ b/config/src/runtime_upgrades.rs @@ -30,7 +30,8 @@ pub enum ReloadError { Other, } -type PoisonErr<'a, T> = PoisonError + Send + Sync)>>>>; +type PoisonErr<'grd, T> = + PoisonError + Send + Sync)>>>>; impl From> for ReloadError { fn from(_: PoisonErr<'_, T>) -> Self { diff --git a/core/src/block.rs b/core/src/block.rs index c22fa25f1b6..bb4ef2eb4e7 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -96,14 +96,14 @@ impl Chain { } /// Chain iterator -pub struct ChainIterator<'a> { - chain: &'a Chain, +pub struct ChainIterator<'itm> { + chain: &'itm Chain, pos_front: u64, pos_back: u64, } -impl<'a> ChainIterator<'a> { - fn new(chain: &'a Chain) -> Self { +impl<'itm> ChainIterator<'itm> { + fn new(chain: &'itm Chain) -> Self { ChainIterator { chain, pos_front: 1, @@ -116,8 +116,8 @@ impl<'a> ChainIterator<'a> { } } -impl<'a> Iterator for ChainIterator<'a> { - type Item = MapRef<'a, u64, VersionedCommittedBlock>; +impl<'itm> Iterator for ChainIterator<'itm> { + type Item = MapRef<'itm, u64, VersionedCommittedBlock>; fn next(&mut self) -> Option { if !self.is_exhausted() { let val = self.chain.blocks.get(&self.pos_front); @@ -150,7 +150,7 @@ impl<'a> Iterator for ChainIterator<'a> { } } -impl<'a> DoubleEndedIterator for ChainIterator<'a> { +impl<'itm> DoubleEndedIterator for ChainIterator<'itm> { fn next_back(&mut self) -> Option { if !self.is_exhausted() { let val = self.chain.blocks.get(&self.pos_back); diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index 533227d6156..125d768c154 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -68,7 +68,7 @@ impl From for Error { } } -struct Validator<'a, W: WorldTrait> { +struct Validator<'wrld, W: WorldTrait> { /// Number of instructions in the smartcontract instruction_count: u64, /// Max allowed number of instructions in the smartcontract @@ -78,7 +78,7 @@ struct Validator<'a, W: WorldTrait> { /// If this particular query is allowed is_query_allowed: Arc>, /// Current [`WorldStateview`] - wsv: &'a WorldStateView, + wsv: &'wrld WorldStateView, } impl Validator<'_, W> { @@ -125,16 +125,16 @@ impl Validator<'_, W> { } } -struct State<'a, W: WorldTrait> { +struct State<'wrld, W: WorldTrait> { account_id: AccountId, /// Ensures smartcontract adheres to limits - validator: Option>, + validator: Option>, store_limits: StoreLimits, - wsv: &'a WorldStateView, + wsv: &'wrld WorldStateView, } -impl<'a, W: WorldTrait> State<'a, W> { - fn new(wsv: &'a WorldStateView, account_id: AccountId, config: Configuration) -> Self { +impl<'wrld, W: WorldTrait> State<'wrld, W> { + fn new(wsv: &'wrld WorldStateView, account_id: AccountId, config: Configuration) -> Self { Self { wsv, account_id, @@ -171,13 +171,13 @@ impl<'a, W: WorldTrait> State<'a, W> { } /// `WebAssembly` virtual machine -pub struct Runtime<'a, W: WorldTrait> { +pub struct Runtime<'wrld, W: WorldTrait> { engine: Engine, - linker: Linker>, + linker: Linker>, config: Configuration, } -impl<'a, W: WorldTrait> Runtime<'a, W> { +impl<'wrld, W: WorldTrait> Runtime<'wrld, W> { /// `Runtime` constructor with default configuration. /// /// # Errors @@ -344,7 +344,7 @@ impl<'a, W: WorldTrait> Runtime<'a, W> { Ok(()) } - fn create_linker(engine: &Engine) -> Result>, Error> { + fn create_linker(engine: &Engine) -> Result>, Error> { let mut linker = Linker::new(engine); linker diff --git a/core/src/sumeragi/message.rs b/core/src/sumeragi/message.rs index b41983bfe58..61455e8fd03 100644 --- a/core/src/sumeragi/message.rs +++ b/core/src/sumeragi/message.rs @@ -145,9 +145,9 @@ impl VersionedMessage { /// Send this message over the network to multiple `peers`. /// # Errors /// Fails if network sending fails - pub async fn send_to_multiple<'a, I>(self, broker: &Broker, peers: I) + pub async fn send_to_multiple<'itm, I>(self, broker: &Broker, peers: I) where - I: IntoIterator + Send, + I: IntoIterator + Send, { let futures = peers .into_iter() diff --git a/core/src/sumeragi/network_topology.rs b/core/src/sumeragi/network_topology.rs index 3b3da51ffae..0f8e36c5fd6 100644 --- a/core/src/sumeragi/network_topology.rs +++ b/core/src/sumeragi/network_topology.rs @@ -366,10 +366,10 @@ impl Topology { } /// Returns signatures of the peers with the specified `roles` from all `signatures`. - pub fn filter_signatures_by_roles<'a>( - &'a self, - roles: &'a [Role], - signatures: impl IntoIterator> + 'a, + pub fn filter_signatures_by_roles<'slf>( + &'slf self, + roles: &'slf [Role], + signatures: impl IntoIterator> + 'slf, ) -> Vec> { let roles: HashSet = roles.iter().copied().collect(); let public_keys: HashSet<_> = roles @@ -484,8 +484,8 @@ mod tests { fn correct_number_of_peers_genesis() { let peers = topology_test_peers(); // set_a.len() = 2, is wrong as it is not possible to get integer f in: 2f + 1 = 2 - let set_a: HashSet<_> = topology_test_peers().iter().cloned().take(3).collect(); - let set_b: HashSet<_> = topology_test_peers().iter().cloned().skip(3).collect(); + let set_a: HashSet<_> = topology_test_peers().iter().take(3).cloned().collect(); + let set_b: HashSet<_> = topology_test_peers().iter().skip(3).cloned().collect(); let _network_topology = GenesisBuilder::new() .with_leader(peers.iter().next().unwrap().clone()) .with_set_a(set_a) diff --git a/core/src/triggers.rs b/core/src/triggers.rs index 0f4bd3db443..fb217baf21f 100644 --- a/core/src/triggers.rs +++ b/core/src/triggers.rs @@ -105,9 +105,9 @@ impl TriggerSet { /// /// Users should not try to modify [`TriggerSet`] before dropping actions, /// returned by the current function - pub fn find_matching<'e, E>(&self, events: E) -> Vec + pub fn find_matching<'evnt, E>(&self, events: E) -> Vec where - E: IntoIterator, + E: IntoIterator, { let mut result = Vec::new(); diff --git a/crypto/src/merkle.rs b/crypto/src/merkle.rs index 41bd03e1a5f..c490131ea80 100644 --- a/crypto/src/merkle.rs +++ b/crypto/src/merkle.rs @@ -61,8 +61,8 @@ pub enum Node { #[derive(Debug)] /// BFS iterator over the Merkle tree -pub struct BreadthFirstIter<'a, T> { - queue: Vec<&'a Node>, +pub struct BreadthFirstIter<'itm, T> { + queue: Vec<&'itm Node>, } #[cfg(feature = "std")] @@ -196,8 +196,8 @@ impl Node { } } -impl<'a, T> BreadthFirstIter<'a, T> { - fn new(root_node: &'a Node) -> Self { +impl<'itm, T> BreadthFirstIter<'itm, T> { + fn new(root_node: &'itm Node) -> Self { BreadthFirstIter { queue: vec![root_node], } @@ -208,8 +208,8 @@ impl<'a, T> BreadthFirstIter<'a, T> { /// `'a` lifetime specified for `Node`. Because `Node` is recursive data structure with self /// composition in case of `Node::Subtree` we use `Box` to know size of each `Node` object in /// memory. -impl<'a, T> Iterator for BreadthFirstIter<'a, T> { - type Item = &'a Node; +impl<'itm, T> Iterator for BreadthFirstIter<'itm, T> { + type Item = &'itm Node; fn next(&mut self) -> Option { match &self.queue.pop() { @@ -225,9 +225,9 @@ impl<'a, T> Iterator for BreadthFirstIter<'a, T> { } } -impl<'a, T> IntoIterator for &'a MerkleTree { - type Item = &'a Node; - type IntoIter = BreadthFirstIter<'a, T>; +impl<'itm, T> IntoIterator for &'itm MerkleTree { + type Item = &'itm Node; + type IntoIter = BreadthFirstIter<'itm, T>; fn into_iter(self) -> Self::IntoIter { BreadthFirstIter::new(&self.root_node) diff --git a/crypto/src/signature.rs b/crypto/src/signature.rs index a1908203ff1..011622f32b1 100644 --- a/crypto/src/signature.rs +++ b/crypto/src/signature.rs @@ -348,9 +348,9 @@ impl IntoIterator for SignaturesOf { } } -impl<'a, T> IntoIterator for &'a SignaturesOf { - type Item = &'a SignatureOf; - type IntoIter = btree_map::Values<'a, PublicKey, SignatureOf>; +impl<'itm, T> IntoIterator for &'itm SignaturesOf { + type Item = &'itm SignatureOf; + type IntoIter = btree_map::Values<'itm, PublicKey, SignatureOf>; fn into_iter(self) -> Self::IntoIter { self.signatures.values() } @@ -406,7 +406,7 @@ impl SignaturesOf { /// # Warning: /// /// This method uses [`core::mem::transmute`] internally - #[allow(unsafe_code)] + #[allow(unsafe_code, clippy::transmute_undefined_repr)] pub fn transmute(self) -> SignaturesOf { // SAFETY: Safe because we are transmuting to a pointer of // type `` which is related to type ``. diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 0587de2bfdd..4ce5430657b 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -403,11 +403,11 @@ pub enum Value { LimitedMetadata(metadata::Metadata), /// `Id` of `Asset`, `Account`, etc. Id(IdBox), - /// `Identifiable` as `Asset`, `Account` etc. + /// `impl Identifiable` as in `Asset`, `Account` etc. Identifiable(IdentifiableBox), /// [`PublicKey`]. PublicKey(PublicKey), - /// Iroha `Parameter` variant. + /// Iroha [`Parameter`] variant. Parameter(Parameter), /// Signature check condition. SignatureCheckCondition(SignatureCheckCondition), @@ -718,10 +718,15 @@ pub mod prelude { #[cfg(feature = "roles")] pub use super::role::prelude::*; pub use super::{ - account::prelude::*, asset::prelude::*, domain::prelude::*, fixed::prelude::*, - pagination::prelude::*, peer::prelude::*, trigger::prelude::*, uri, EnumTryAsError, IdBox, - Identifiable, IdentifiableBox, Name, Parameter, RegistrableBox, TryAsMut, TryAsRef, - ValidationError, Value, + account::prelude::*, + asset::prelude::*, + domain::prelude::*, + fixed::prelude::*, + pagination::{prelude::*, Pagination}, + peer::prelude::*, + trigger::prelude::*, + uri, EnumTryAsError, IdBox, Identifiable, IdentifiableBox, Name, Parameter, RegistrableBox, + TryAsMut, TryAsRef, ValidationError, Value, }; pub use crate::{ events::prelude::*, expression::prelude::*, isi::prelude::*, metadata::prelude::*, diff --git a/data_model/src/pagination.rs b/data_model/src/pagination.rs index 9132d9b9580..c68d7ebd513 100644 --- a/data_model/src/pagination.rs +++ b/data_model/src/pagination.rs @@ -4,6 +4,7 @@ #[cfg(not(feature = "std"))] use alloc::{ collections::btree_map, + format, string::{String, ToString as _}, vec, vec::Vec, @@ -12,6 +13,8 @@ use core::fmt; #[cfg(feature = "std")] use std::collections::btree_map; +use iroha_schema::IntoSchema; +use iroha_version::{Decode, Encode}; use serde::{Deserialize, Serialize}; #[cfg(feature = "warp")] use warp::{ @@ -70,7 +73,9 @@ impl Iterator for Paginated { } /// Structure for pagination requests -#[derive(Clone, Eq, PartialEq, Debug, Default, Copy, Deserialize, Serialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize, Decode, Encode, IntoSchema, +)] pub struct Pagination { /// start of indexing pub start: Option, diff --git a/data_model/src/query.rs b/data_model/src/query.rs index 1e9ab9830f9..137b4fa4dc3 100644 --- a/data_model/src/query.rs +++ b/data_model/src/query.rs @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "roles")] use self::role::*; use self::{account::*, asset::*, domain::*, peer::*, permissions::*, transaction::*}; -use crate::{account::Account, Identifiable, Value}; +use crate::{account::Account, pagination::Pagination, Identifiable, Value}; /// Sized container for all possible Queries. #[allow(clippy::enum_variant_names)] @@ -148,6 +148,20 @@ declare_versioned_with_scale!(VersionedQueryResult 1..2, Debug, Clone, iroha_mac #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct QueryResult(pub Value); +declare_versioned_with_scale!(VersionedPaginatedQueryResult 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); + +/// Paginated Query Result +#[version_with_scale(n = 1, versioned = "VersionedPaginatedQueryResult")] +#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] +pub struct PaginatedQueryResult { + /// The result of the query execution. + pub result: QueryResult, + /// pagination + pub pagination: Pagination, + /// Total query amount (if applicable) else 0. + pub total: u64, +} + #[cfg(all(feature = "std", feature = "warp"))] impl QueryRequest { /// Constructs a new request with the `query`. @@ -1132,8 +1146,8 @@ pub mod prelude { pub use super::role::prelude::*; pub use super::{ account::prelude::*, asset::prelude::*, domain::prelude::*, peer::prelude::*, - permissions::prelude::*, transaction::*, Query, QueryBox, QueryResult, SignedQueryRequest, - VersionedQueryResult, + permissions::prelude::*, transaction::*, PaginatedQueryResult, Query, QueryBox, + QueryResult, SignedQueryRequest, VersionedPaginatedQueryResult, VersionedQueryResult, }; #[cfg(feature = "warp")] pub use super::{QueryRequest, VersionedSignedQueryRequest}; diff --git a/docs/source/references/api_spec.md b/docs/source/references/api_spec.md index 867bf6f8fea..7f2100454f4 100644 --- a/docs/source/references/api_spec.md +++ b/docs/source/references/api_spec.md @@ -46,7 +46,7 @@ | Evaluate err. | 400 | `QueryError::Evaluate(String)` | | Find err. | 404 | `QueryError::Find(Box)` | | Conversion err. | 400 | `QueryError::Conversion(String)` | -| Success | 200 | `VersionedQueryResult` | +| Success | 200 | `VersionedPaginatedQueryResult` | #### Asset Not Found 404 Whether each prerequisite object was found and `FindError`: diff --git a/schema/bin/src/lib.rs b/schema/bin/src/lib.rs index 7bd6a0e9d12..9d7d5e4810d 100644 --- a/schema/bin/src/lib.rs +++ b/schema/bin/src/lib.rs @@ -32,7 +32,7 @@ pub fn build_schemas() -> MetaMap { VersionedBlockSubscriberMessage, VersionedEventPublisherMessage, VersionedEventSubscriberMessage, - VersionedQueryResult, + VersionedPaginatedQueryResult, VersionedSignedQueryRequest, VersionedTransaction, QueryError, diff --git a/schema/derive/src/lib.rs b/schema/derive/src/lib.rs index a5c04de7682..c380a073941 100644 --- a/schema/derive/src/lib.rs +++ b/schema/derive/src/lib.rs @@ -299,10 +299,10 @@ fn variant_index(v: &Variant, i: usize) -> TokenStream2 { } /// Finds specific attribute with codec ident satisfying predicate -fn find_meta_item<'a, F, R, I, M>(mut itr: I, mut pred: F) -> Option +fn find_meta_item<'attr, F, R, I, M>(mut itr: I, mut pred: F) -> Option where F: FnMut(M) -> Option + Clone, - I: Iterator, + I: Iterator, M: Parse, { itr.find_map(|attr| { diff --git a/tools/parity_scale_decoder/src/generate_map.rs b/tools/parity_scale_decoder/src/generate_map.rs index 48113424759..ca7257dc092 100644 --- a/tools/parity_scale_decoder/src/generate_map.rs +++ b/tools/parity_scale_decoder/src/generate_map.rs @@ -204,6 +204,8 @@ pub fn generate_map() -> DumpDecodedMap { Or, Pair, Parameter, + Pagination, + PaginatedQueryResult, Peer, PeerEvent, PeerEventFilter, @@ -284,6 +286,7 @@ pub fn generate_map() -> DumpDecodedMap { VersionedSignedQueryRequest, VersionedTransaction, VersionedValidTransaction, + VersionedPaginatedQueryResult, WasmExecutionFail, Where, [u8; 32], diff --git a/version/src/lib.rs b/version/src/lib.rs index d03895eef0f..576c9bcf3c4 100644 --- a/version/src/lib.rs +++ b/version/src/lib.rs @@ -217,7 +217,7 @@ pub mod json { use super::{error::Result, Version}; /// [`Serialize`] versioned analog, specifically for JSON. - pub trait DeserializeVersioned<'a>: Deserialize<'a> + Version { + pub trait DeserializeVersioned<'de>: Deserialize<'de> + Version { /// Use this function for versioned objects instead of [`serde_json::from_str`]. /// /// # Errors