Skip to main content

dash_sdk/mock/
sdk.rs

1//! Mocking mechanisms for Dash Platform SDK.
2//!
3//! See [MockDashPlatformSdk] for more details.
4use super::MockResponse;
5use crate::{
6    platform::{
7        types::{evonode::EvoNode, identity::IdentityRequest},
8        Fetch, FetchMany, Query,
9    },
10    sync::block_on,
11    Error, Sdk,
12};
13use arc_swap::ArcSwapOption;
14use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
15use dapi_grpc::{
16    mock::Mockable,
17    platform::v0::{self as proto},
18};
19use dash_context_provider::{ContextProvider, ContextProviderError};
20use dpp::dashcore::Network;
21use dpp::version::PlatformVersion;
22use drive_proof_verifier::FromProof;
23use rs_dapi_client::mock::MockError;
24use rs_dapi_client::{
25    mock::{Key, MockDapiClient},
26    transport::TransportRequest,
27    DapiClient, DumpData, ExecutionResponse,
28};
29use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
30use tokio::sync::{Mutex, OwnedMutexGuard};
31
32/// Mechanisms to mock Dash Platform SDK.
33///
34/// This object is returned by [Sdk::mock()](crate::Sdk::mock()) and is used to define mock expectations.
35///
36/// Use [MockDashPlatformSdk::expect_fetch_many()] to define expectations for [FetchMany] requests
37/// and [MockDashPlatformSdk::expect_fetch()] for [Fetch] requests.
38///
39/// ## Panics
40///
41/// Can panic on errors.
42#[derive(Debug)]
43pub struct MockDashPlatformSdk {
44    from_proof_expectations: BTreeMap<Key, Vec<u8>>,
45    dapi: Arc<Mutex<MockDapiClient>>,
46    sdk: ArcSwapOption<Sdk>,
47}
48
49impl MockDashPlatformSdk {
50    /// Returns true when requests should use proofs.
51    ///
52    /// ## Panics
53    ///
54    /// Panics when sdk is not set during initialization.
55    pub fn prove(&self) -> bool {
56        if let Some(sdk) = self.sdk.load().as_ref() {
57            sdk.prove()
58        } else {
59            panic!("sdk must be set when creating mock ")
60        }
61    }
62
63    /// Create new mock SDK.
64    ///
65    /// ## Note
66    ///
67    /// You have to call [MockDashPlatformSdk::set_sdk()] to set sdk, otherwise Mock SDK will panic.
68    pub(crate) fn new(dapi: Arc<Mutex<MockDapiClient>>) -> Self {
69        Self {
70            from_proof_expectations: Default::default(),
71            dapi,
72            sdk: ArcSwapOption::new(None),
73        }
74    }
75
76    pub(crate) fn set_sdk(&mut self, sdk: Sdk) {
77        self.sdk.store(Some(Arc::new(sdk)));
78    }
79
80    /// Returns the current `PlatformVersion` from the outer [`Sdk`]'s
81    /// auto-detect-aware atomic. Both request-encode (`sdk.query_settings()`)
82    /// and proof-decode (`parse_proof_with_metadata`) read through this
83    /// single source, so a mock ratchet from response metadata is visible
84    /// to both paths.
85    ///
86    /// ## Panics
87    ///
88    /// Panics when sdk is not set during initialization.
89    pub(crate) fn version<'v>(&self) -> &'v PlatformVersion {
90        if let Some(sdk) = self.sdk.load().as_ref() {
91            sdk.version()
92        } else {
93            panic!("sdk must be set when creating mock ")
94        }
95    }
96
97    /// Load all expectations from files in a directory asynchronously.
98    ///
99    /// See [MockDashPlatformSdk::load_expectations_sync()] for more details.
100    #[deprecated(since = "1.4.0", note = "use load_expectations_sync")]
101    pub async fn load_expectations<P: AsRef<std::path::Path> + Send + 'static>(
102        &mut self,
103        dir: P,
104    ) -> Result<&mut Self, Error> {
105        self.load_expectations_sync(dir)
106    }
107
108    /// Load all expectations from files in a directory.
109    ///
110    ///
111    /// By default, mock expectations are loaded when Sdk is built with [SdkBuilder::build()](crate::SdkBuilder::build()).
112    /// This function can be used to load expectations after the Sdk is created, or use alternative location.
113    /// Expectation files must be prefixed with [DapiClient::DUMP_FILE_PREFIX] and
114    /// have `.json` extension.
115    pub fn load_expectations_sync<P: AsRef<std::path::Path>>(
116        &mut self,
117        dir: P,
118    ) -> Result<&mut Self, Error> {
119        let prefix = DapiClient::DUMP_FILE_PREFIX;
120
121        let entries = dir.as_ref().read_dir().map_err(|e| {
122            Error::Config(format!(
123                "cannot load mock expectations from {}: {}",
124                dir.as_ref().display(),
125                e
126            ))
127        })?;
128
129        let files: Vec<PathBuf> = entries
130            .into_iter()
131            .filter_map(|x| x.ok())
132            .filter(|f| {
133                f.file_type().is_ok_and(|t| t.is_file())
134                    && f.file_name().to_string_lossy().starts_with(prefix)
135                    && f.file_name().to_string_lossy().ends_with(".json")
136            })
137            .map(|f| f.path())
138            .collect();
139
140        let mut dapi = block_on(self.dapi.clone().lock_owned())?;
141
142        for filename in &files {
143            let basename = filename.file_name().unwrap().to_str().unwrap();
144            let request_type = basename.split('_').nth(1).unwrap_or_default();
145
146            match request_type {
147                "GetDocumentsRequest" => {
148                    load_expectation::<proto::GetDocumentsRequest>(&mut dapi, filename)?
149                }
150                "GetEpochsInfoRequest" => {
151                    load_expectation::<proto::GetEpochsInfoRequest>(&mut dapi, filename)?
152                }
153                "GetDataContractRequest" => {
154                    load_expectation::<proto::GetDataContractRequest>(&mut dapi, filename)?
155                }
156                "GetDataContractsRequest" => {
157                    load_expectation::<proto::GetDataContractsRequest>(&mut dapi, filename)?
158                }
159                "GetDataContractsByRangeRequest" => {
160                    load_expectation::<proto::GetDataContractsByRangeRequest>(&mut dapi, filename)?
161                }
162                "GetDataContractsLatestVersionsRequest" => load_expectation::<
163                    proto::GetDataContractsLatestVersionsRequest,
164                >(&mut dapi, filename)?,
165                "GetDataContractHistoryRequest" => {
166                    load_expectation::<proto::GetDataContractHistoryRequest>(&mut dapi, filename)?
167                }
168                "GetContractGroupInfoRequest" => {
169                    load_expectation::<proto::GetContractGroupInfoRequest>(&mut dapi, filename)?
170                }
171                "GetContractGroupMembersRequest" => {
172                    load_expectation::<proto::GetContractGroupMembersRequest>(&mut dapi, filename)?
173                }
174                "GetContractGroupsForContractRequest" => load_expectation::<
175                    proto::GetContractGroupsForContractRequest,
176                >(&mut dapi, filename)?,
177                "GetContractModerationStatusRequest" => load_expectation::<
178                    proto::GetContractModerationStatusRequest,
179                >(&mut dapi, filename)?,
180                "GetContractModerationEntriesRequest" => load_expectation::<
181                    proto::GetContractModerationEntriesRequest,
182                >(&mut dapi, filename)?,
183                "GetContractDocumentRemovalsRequest" => load_expectation::<
184                    proto::GetContractDocumentRemovalsRequest,
185                >(&mut dapi, filename)?,
186                "GetContractTeamActionsRequest" => {
187                    load_expectation::<proto::GetContractTeamActionsRequest>(&mut dapi, filename)?
188                }
189                "GetContractTeamActionSignersRequest" => load_expectation::<
190                    proto::GetContractTeamActionSignersRequest,
191                >(&mut dapi, filename)?,
192                "GetContractModerationActionCountsRequest" => {
193                    load_expectation::<proto::GetContractModerationActionCountsRequest>(
194                        &mut dapi, filename,
195                    )?
196                }
197                "GetContractFeePotsRequest" => {
198                    load_expectation::<proto::GetContractFeePotsRequest>(&mut dapi, filename)?
199                }
200                "GetDocumentHistoryRequest" => {
201                    load_expectation::<proto::GetDocumentHistoryRequest>(&mut dapi, filename)?
202                }
203                "IdentityRequest" => load_expectation::<IdentityRequest>(&mut dapi, filename)?,
204                "GetIdentityRequest" => {
205                    load_expectation::<proto::GetIdentityRequest>(&mut dapi, filename)?
206                }
207
208                "GetIdentityBalanceRequest" => {
209                    load_expectation::<proto::GetIdentityBalanceRequest>(&mut dapi, filename)?
210                }
211                "GetIdentityContractNonceRequest" => {
212                    load_expectation::<proto::GetIdentityContractNonceRequest>(&mut dapi, filename)?
213                }
214                "GetIdentityBalanceAndRevisionRequest" => load_expectation::<
215                    proto::GetIdentityBalanceAndRevisionRequest,
216                >(&mut dapi, filename)?,
217                "GetAddressInfoRequest" => {
218                    load_expectation::<proto::GetAddressInfoRequest>(&mut dapi, filename)?
219                }
220                "GetAddressesInfosRequest" => {
221                    load_expectation::<proto::GetAddressesInfosRequest>(&mut dapi, filename)?
222                }
223                "GetIdentityKeysRequest" => {
224                    load_expectation::<proto::GetIdentityKeysRequest>(&mut dapi, filename)?
225                }
226                "GetProtocolVersionUpgradeStateRequest" => load_expectation::<
227                    proto::GetProtocolVersionUpgradeStateRequest,
228                >(&mut dapi, filename)?,
229                "GetProtocolVersionUpgradeVoteStatusRequest" => {
230                    load_expectation::<proto::GetProtocolVersionUpgradeVoteStatusRequest>(
231                        &mut dapi, filename,
232                    )?
233                }
234                "GetContestedResourcesRequest" => {
235                    load_expectation::<proto::GetContestedResourcesRequest>(&mut dapi, filename)?
236                }
237                "GetContestedResourceVoteStateRequest" => load_expectation::<
238                    proto::GetContestedResourceVoteStateRequest,
239                >(&mut dapi, filename)?,
240                "GetContestedResourceVotersForIdentityRequest" => {
241                    load_expectation::<proto::GetContestedResourceVotersForIdentityRequest>(
242                        &mut dapi, filename,
243                    )?
244                }
245                "GetContestedResourceIdentityVotesRequest" => {
246                    load_expectation::<proto::GetContestedResourceIdentityVotesRequest>(
247                        &mut dapi, filename,
248                    )?
249                }
250                "GetVotePollsByEndDateRequest" => {
251                    load_expectation::<proto::GetVotePollsByEndDateRequest>(&mut dapi, filename)?
252                }
253                "GetPrefundedSpecializedBalanceRequest" => load_expectation::<
254                    proto::GetPrefundedSpecializedBalanceRequest,
255                >(&mut dapi, filename)?,
256                "GetPathElementsRequest" => {
257                    load_expectation::<proto::GetPathElementsRequest>(&mut dapi, filename)?
258                }
259                "GetTotalCreditsInPlatformRequest" => load_expectation::<
260                    proto::GetTotalCreditsInPlatformRequest,
261                >(&mut dapi, filename)?,
262                "GetIdentityKeysRemainingBudgetsRequest" => load_expectation::<
263                    proto::GetIdentityKeysRemainingBudgetsRequest,
264                >(&mut dapi, filename)?,
265                "GetIdentityTokenBalancesRequest" => {
266                    load_expectation::<proto::GetIdentityTokenBalancesRequest>(&mut dapi, filename)?
267                }
268                "GetIdentitiesTokenBalancesRequest" => load_expectation::<
269                    proto::GetIdentitiesTokenBalancesRequest,
270                >(&mut dapi, filename)?,
271                "GetIdentityTokenInfosRequest" => {
272                    load_expectation::<proto::GetIdentityTokenInfosRequest>(&mut dapi, filename)?
273                }
274                "GetIdentitiesTokenInfosRequest" => {
275                    load_expectation::<proto::GetIdentitiesTokenInfosRequest>(&mut dapi, filename)?
276                }
277                "GetTokenStatusesRequest" => {
278                    load_expectation::<proto::GetTokenStatusesRequest>(&mut dapi, filename)?
279                }
280                "GetTokenTotalSupplyRequest" => {
281                    load_expectation::<proto::GetTokenTotalSupplyRequest>(&mut dapi, filename)?
282                }
283                "GetGroupInfoRequest" => {
284                    load_expectation::<proto::GetGroupInfoRequest>(&mut dapi, filename)?
285                }
286                "GetGroupInfosRequest" => {
287                    load_expectation::<proto::GetGroupInfosRequest>(&mut dapi, filename)?
288                }
289                "GetGroupActionsRequest" => {
290                    load_expectation::<proto::GetGroupActionsRequest>(&mut dapi, filename)?
291                }
292                "GetGroupActionSignersRequest" => {
293                    load_expectation::<proto::GetGroupActionSignersRequest>(&mut dapi, filename)?
294                }
295                "EvoNode" => load_expectation::<EvoNode>(&mut dapi, filename)?,
296                "GetTokenDirectPurchasePricesRequest" => load_expectation::<
297                    proto::GetTokenDirectPurchasePricesRequest,
298                >(&mut dapi, filename)?,
299                "GetTokenPerpetualDistributionLastClaimRequest" => {
300                    load_expectation::<proto::GetTokenPerpetualDistributionLastClaimRequest>(
301                        &mut dapi, filename,
302                    )?
303                }
304                "GetTokenPreProgrammedDistributionsRequest" => {
305                    load_expectation::<proto::GetTokenPreProgrammedDistributionsRequest>(
306                        &mut dapi, filename,
307                    )?
308                }
309                "GetAddressesTrunkStateRequest" => {
310                    load_expectation::<proto::GetAddressesTrunkStateRequest>(&mut dapi, filename)?
311                }
312                _ => {
313                    return Err(Error::Config(format!(
314                        "unknown request type {} in {}, missing match arm in load_expectations?",
315                        request_type,
316                        filename.display()
317                    )))
318                }
319            };
320        }
321
322        Ok(self)
323    }
324
325    /// Expect a [Fetch] request and return provided object.
326    ///
327    /// This method is used to define mock expectations for [Fetch] requests.
328    ///
329    /// ## Generic Parameters
330    ///
331    /// - `O`: Type of the object that will be returned in response to the query. Must implement [Fetch] and [MockResponse].
332    /// - `Q`: Type of the query that will be sent to Platform. Must implement [Query].
333    ///
334    /// ## Arguments
335    ///
336    /// - `query`: Query that will be sent to Platform.
337    /// - `object`: Object that will be returned in response to `query`, or None if the object is expected to not exist.
338    ///
339    /// ## Returns
340    ///
341    /// * Reference to self on success, to allow chaining
342    /// * Error when expectation cannot be set or is already defined for this request
343    ///
344    /// ## Panics
345    ///
346    /// Can panic on errors.
347    ///
348    /// ## Example
349    ///
350    /// ```no_run
351    /// # let r = tokio::runtime::Runtime::new().unwrap();
352    /// #
353    /// # r.block_on(async {
354    ///     use dash_sdk::{Sdk, platform::{Identity, Fetch, dpp::identity::accessors::IdentityGettersV0}};
355    ///
356    ///     let mut api = Sdk::new_mock();
357    ///     // Define expected response
358    ///     let expected: Identity = Identity::random_identity(1, None, api.version())
359    ///         .expect("create expected identity");
360    ///     // Define query that will be sent
361    ///     let query = expected.id();
362    ///     // Expect that in response to `query`, `expected` will be returned
363    ///     api.mock().expect_fetch(query, Some(expected.clone())).await.unwrap();
364    ///
365    ///     // Fetch the identity
366    ///     let retrieved = dpp::prelude::Identity::fetch(&api, query)
367    ///         .await
368    ///         .unwrap()
369    ///         .expect("object should exist");
370    ///
371    ///     // Check that the identity is the same as expected
372    ///     assert_eq!(retrieved, expected);
373    /// # });
374    /// ```
375    pub async fn expect_fetch<O: Fetch + MockResponse, Q: Query<<O as Fetch>::Query>>(
376        &mut self,
377        query: Q,
378        object: Option<O>,
379    ) -> Result<&mut Self, Error>
380    where
381        <<O as Fetch>::Request as TransportRequest>::Response: Default,
382    {
383        let (rich, wire) =
384            self.encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query);
385        self.expect(&rich, wire, object).await?;
386
387        Ok(self)
388    }
389
390    /// Remove previously defined expectation for a [Fetch] request.
391    ///
392    /// Returns `true` if any expectation was removed.
393    pub async fn remove_fetch_expectation<O, Q>(&mut self, query: Q) -> bool
394    where
395        O: Fetch,
396        Q: Query<<O as Fetch>::Query>,
397    {
398        let (rich, wire) =
399            self.encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query);
400        self.remove(&rich, wire).await
401    }
402
403    /// Expect a [FetchMany] request and return provided object.
404    ///
405    /// This method is used to define mock expectations for [FetchMany] requests.
406    ///
407    /// ## Generic Parameters
408    ///
409    /// - `O`: Type of the object that will be returned in response to the query.
410    ///   Must implement [FetchMany].
411    /// - `Q`: Type of the query that will be sent to Platform. Must implement [Query].
412    /// - `R`: Collection type for the results. Must implement [MockResponse].
413    ///
414    /// ## Arguments
415    ///
416    /// - `query`: Query that will be sent to Platform.
417    /// - `objects`: Collection of objects that will be returned in response to `query`, or None if no objects are expected.
418    ///
419    /// ## Returns
420    ///
421    /// * Reference to self on success, to allow chaining
422    /// * Error when expectation cannot be set or is already defined for this request
423    ///
424    /// ## Panics
425    ///
426    /// Can panic on errors.
427    ///
428    /// ## Example
429    ///
430    /// Usage example is similar to
431    /// [MockDashPlatformSdk::expect_fetch()], but the expected
432    /// object must be a vector of objects.
433    pub async fn expect_fetch_many<
434        K: Ord,
435        O: FetchMany<K, R>,
436        Q: Query<<O as FetchMany<K, R>>::Query>,
437        R,
438    >(
439        &mut self,
440        query: Q,
441        objects: Option<R>,
442    ) -> Result<&mut Self, Error>
443    where
444        R: FromIterator<(K, Option<O>)>
445            + MockResponse
446            + FromProof<
447                <O as FetchMany<K, R>>::Query,
448                Request = <O as FetchMany<K, R>>::Query,
449                Response = <<O as FetchMany<K, R>>::Request as TransportRequest>::Response,
450            > + Sync
451            + Send
452            + Default,
453        <<O as FetchMany<K, R>>::Request as TransportRequest>::Response: Default,
454    {
455        let (rich, wire) = self
456            .encode_rich_to_wire::<Q, <O as FetchMany<K, R>>::Query, <O as FetchMany<K, R>>::Request>(
457                query,
458            );
459        self.expect(&rich, wire, objects).await?;
460
461        Ok(self)
462    }
463
464    /// Encode a user-facing `query` first into its rich form (`R`) and
465    /// then into its wire form (`W`), both against the SDK's current
466    /// `QuerySettings`. Returns `(rich, wire)` for use as proof-mock /
467    /// DAPI-mock expectation keys.
468    ///
469    /// ## Panics
470    ///
471    /// INTENTIONAL(SEC-001): test-harness fail-fast — encoder errors
472    /// for V1-only `DocumentQuery` features against a V0
473    /// `PlatformVersion` crash the test setup loudly rather than
474    /// silently propagate. Panics also if `set_sdk` was not called.
475    fn encode_rich_to_wire<Q, R, W>(&self, query: Q) -> (R, W)
476    where
477        Q: Query<R>,
478        R: Query<W> + Mockable,
479        W: TransportRequest,
480    {
481        let sdk_guard = self.sdk.load();
482        let sdk = sdk_guard
483            .as_ref()
484            .expect("sdk must be set when creating mock");
485        let settings = sdk.query_settings();
486        let rich: R = query.query(&settings).expect("query must be correct");
487        let wire: W = rich.query(&settings).expect("wire encoding must succeed");
488        (rich, wire)
489    }
490
491    /// Save expectations for a request.
492    ///
493    /// `rich_request` is the user-facing query (what [`FromProof`] binds to) and seeds
494    /// the proof-mock cache key. `wire_request` is the proto that flows over the wire
495    /// and seeds the DAPI executor mock. For non-versioned operations both arguments
496    /// are the same value; for documents the rich form is [`DocumentQuery`] and the
497    /// wire is [`GetDocumentsRequest`].
498    async fn expect<R: Mockable + std::fmt::Debug, W: TransportRequest, O: MockResponse>(
499        &mut self,
500        rich_request: &R,
501        wire_request: W,
502        returned_object: Option<O>,
503    ) -> Result<(), Error>
504    where
505        W::Response: Default,
506    {
507        let key = Key::new(rich_request);
508
509        if self.from_proof_expectations.contains_key(&key) {
510            return Err(MockError::MockExpectationConflict(format!(
511                "proof expectation key {} already defined for {} request: {:?}",
512                key,
513                std::any::type_name::<R>(),
514                rich_request
515            ))
516            .into());
517        }
518
519        self.from_proof_expectations
520            .insert(key, returned_object.mock_serialize(self));
521
522        let mut dapi_guard = self.dapi.lock().await;
523        dapi_guard.expect(
524            &wire_request,
525            &Ok(ExecutionResponse {
526                inner: Default::default(),
527                retries: 0,
528                address: "http://127.0.0.1".parse().expect("failed to parse address"),
529            }),
530        )?;
531
532        Ok(())
533    }
534
535    /// Remove expectations for a request.
536    async fn remove<R: Mockable, W: TransportRequest>(
537        &mut self,
538        rich_request: &R,
539        wire_request: W,
540    ) -> bool {
541        let key = Key::new(rich_request);
542        let removed_from_proof = self.from_proof_expectations.remove(&key).is_some();
543
544        let mut dapi_guard = self.dapi.lock().await;
545        let removed_from_dapi = dapi_guard.remove(&wire_request);
546
547        removed_from_proof || removed_from_dapi
548    }
549
550    /// Wrapper around [FromProof] that uses mock expectations, falling back to [FromProof] if no expectation is found.
551    pub(crate) fn parse_proof_with_metadata<I, O: FromProof<I>>(
552        &self,
553        request: O::Request,
554        response: O::Response,
555    ) -> Result<(Option<O>, ResponseMetadata, Proof), drive_proof_verifier::Error>
556    where
557        O::Request: Mockable,
558        Option<O>: MockResponse,
559        // O: FromProof<<O as FromProof<I>>::Request>,
560    {
561        let key = Key::new(&request);
562
563        let data = match self.from_proof_expectations.get(&key) {
564            // Report the latest protocol version so the proof path's ratchet
565            // (`maybe_update_protocol_version`) fires as it would against a real
566            // network; `default()` reports 0, which the ratchet ignores.
567            Some(d) => (
568                Option::<O>::mock_deserialize(self, d),
569                ResponseMetadata {
570                    protocol_version: dpp::version::LATEST_VERSION,
571                    ..Default::default()
572                },
573                Proof::default(),
574            ),
575            None => {
576                let version = self.version();
577                let provider = self.context_provider()
578                    .ok_or(ContextProviderError::InvalidQuorum(
579                        "expectation not found and quorum info provider not initialized with sdk.mock().quorum_info_dir()".to_string()
580                    ))?;
581                O::maybe_from_proof_with_metadata(
582                    request,
583                    response,
584                    Network::Regtest,
585                    version,
586                    &provider,
587                )?
588            }
589        };
590
591        Ok(data)
592    }
593    /// Return context provider implementation defined for upstream Sdk object.
594    fn context_provider(&self) -> Option<impl ContextProvider> {
595        if let Some(sdk) = self.sdk.load_full() {
596            sdk.clone().context_provider()
597        } else {
598            None
599        }
600    }
601}
602
603/// Load expectation from file and save it to `dapi_guard` mock Dapi client.
604///
605/// This function is used to load expectations from files in a directory.
606/// It is implemented without reference to the `MockDashPlatformSdk` object
607/// to make it easier to use in async context.
608fn load_expectation<T: TransportRequest>(
609    dapi_guard: &mut OwnedMutexGuard<MockDapiClient>,
610    path: &PathBuf,
611) -> Result<(), Error> {
612    let data = DumpData::<T>::load(path)
613        .map_err(|e| {
614            Error::Config(format!(
615                "cannot load mock expectations from {}: {}",
616                path.display(),
617                e
618            ))
619        })?
620        .deserialize();
621    dapi_guard.expect(&data.0, &data.1)?;
622    Ok(())
623}