architect_api/auth/
grants.rs

1use crate::symbology::MarketdataVenue;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5pub static ALL: Grants = Grants::all();
6pub static NONE: Grants = Grants::none();
7
8#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
9pub struct Grants {
10    /// If true, auth is scoped to the specific claims rather
11    /// than granting broad access permissions;
12    #[serde(default, skip_serializing_if = "Option::is_none")]
13    pub scoped: Option<bool>,
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub marketdata: Option<BoolOrList<MarketdataVenue>>,
16}
17
18impl Grants {
19    pub const fn all() -> Self {
20        Self { scoped: Some(false), marketdata: Some(BoolOrList::Bool(true)) }
21    }
22
23    pub const fn none() -> Self {
24        Self { scoped: Some(true), marketdata: None }
25    }
26
27    pub fn is_subset_of(&self, other: &Grants) -> bool {
28        if other.scoped.is_none() || other.scoped.is_some_and(|o| o == false) {
29            // trivially true
30            return true;
31        }
32        // INVARIANT: other.scoped == Some(true)
33        if self.scoped.is_none() || self.scoped.is_some_and(|s| s == false) {
34            // trivially false
35            return false;
36        }
37        // INVARIANT: self.scoped == Some(true)
38        match (&self.marketdata, &other.marketdata) {
39            (None, _) => {}
40            (Some(BoolOrList::Bool(false)), _) => {}
41            (Some(BoolOrList::List(_)), Some(BoolOrList::Bool(true))) => {}
42            (Some(BoolOrList::List(vs)), Some(BoolOrList::List(vs2))) => {
43                for v in vs {
44                    if !vs2.contains(v) {
45                        return false;
46                    }
47                }
48            }
49            (Some(BoolOrList::Bool(true)), Some(BoolOrList::Bool(true))) => {}
50            _ => {
51                return false;
52            }
53        }
54        true
55    }
56
57    pub fn is_scoped(&self) -> bool {
58        self.scoped.unwrap_or(false)
59    }
60
61    pub fn allowed_to_trade(&self) -> bool {
62        if self.is_scoped() {
63            false
64        } else {
65            true
66        }
67    }
68
69    pub fn allowed_to_marketdata(&self, venue: &MarketdataVenue) -> bool {
70        if self.is_scoped() {
71            if let Some(marketdata) = &self.marketdata {
72                marketdata.includes(venue)
73            } else {
74                false
75            }
76        } else {
77            true
78        }
79    }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
83#[serde(untagged)]
84pub enum BoolOrList<T> {
85    Bool(bool),
86    List(Vec<T>),
87}
88
89impl<T> BoolOrList<T> {
90    pub fn is_all(&self) -> bool {
91        match self {
92            Self::Bool(b) => *b,
93            Self::List(_) => false,
94        }
95    }
96
97    pub fn is_none(&self) -> bool {
98        match self {
99            Self::Bool(b) => !*b,
100            Self::List(xs) => xs.is_empty(),
101        }
102    }
103}
104
105impl<T> BoolOrList<T>
106where
107    T: PartialEq,
108{
109    pub fn includes(&self, item: &T) -> bool {
110        match self {
111            Self::Bool(b) => *b,
112            Self::List(list) => list.contains(item),
113        }
114    }
115}