Skip to main content

fmp_rust_sdk/
access.rs

1use serde::Serialize;
2
3use crate::{ApiErrorKind, Client, Error, Plan, Result};
4
5/// Observed access to one endpoint and parameter combination. An empty response
6/// still counts as allowed, and says nothing about other symbols or date ranges.
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub enum AccessOutcome {
9    Allowed,
10    /// FMP explicitly reported a subscription restriction. HTTP 403 alone is
11    /// insufficient evidence; other forbidden responses remain errors.
12    Restricted {
13        status: u16,
14        body: String,
15    },
16}
17
18/// Subscription configuration alongside one server-observed capability.
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct AccessReport {
21    /// The account owner's declaration; not an automatically detected plan.
22    pub configured_plan: Option<Plan>,
23    pub endpoint: String,
24    pub outcome: AccessOutcome,
25}
26
27impl Client {
28    /// Verifies whether the key can call an endpoint with these parameters.
29    /// This costs a real API request. Invalid keys, rate limits, outages, and
30    /// unclassified forbidden responses propagate as errors; they never become
31    /// evidence of a lower plan. FMP does not expose a documented billing-plan
32    /// lookup, so the report keeps the configured plan separate from the result.
33    /// Successful bodies are not checked against a dataset schema; use a typed
34    /// endpoint method or `get` when the data itself must also be validated.
35    pub async fn probe_access<Q>(&self, path: &str, params: &Q) -> Result<AccessReport>
36    where
37        Q: Serialize + ?Sized,
38    {
39        let outcome = match self.get_bytes(path, params).await {
40            Ok(_) => AccessOutcome::Allowed,
41            Err(Error::Api {
42                kind: ApiErrorKind::PlanRestricted,
43                status,
44                body,
45                ..
46            }) => AccessOutcome::Restricted { status, body },
47            Err(error) => return Err(error),
48        };
49        Ok(AccessReport {
50            configured_plan: self.subscription().plan(),
51            endpoint: path.to_owned(),
52            outcome,
53        })
54    }
55}