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}