superposition_types/
lib.rs

1#![deny(unused_crate_dependencies)]
2
3uniffi::setup_scaffolding!();
4
5#[cfg(feature = "api")]
6pub mod api;
7mod config;
8mod contextual;
9pub mod custom_query;
10pub mod database;
11pub mod logic;
12mod overridden;
13#[cfg(feature = "result")]
14pub mod result;
15
16#[cfg(feature = "server")]
17use std::future::{ready, Ready};
18use std::{collections::HashMap, fmt::Display};
19
20#[cfg(feature = "server")]
21use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
22use derive_more::{Deref, DerefMut};
23#[cfg(feature = "diesel_derives")]
24use diesel::{
25    deserialize::FromSqlRow,
26    expression::AsExpression,
27    r2d2::{ConnectionManager, PooledConnection},
28    sql_types::Json,
29    PgConnection,
30};
31#[cfg(feature = "diesel_derives")]
32use diesel_derive_enum as _;
33use regex::Regex;
34use serde::{Deserialize, Serialize};
35use serde_json::{Map, Value};
36#[cfg(feature = "diesel_derives")]
37use superposition_derives::{JsonFromSql, JsonToSql};
38
39pub use config::{
40    Condition, Config, Context, DimensionInfo, OverrideWithKeys, Overrides,
41};
42pub use contextual::Contextual;
43pub use logic::{apply, partial_apply};
44pub use overridden::Overridden;
45
46pub trait IsEmpty {
47    fn is_empty(&self) -> bool;
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct User {
52    pub email: String,
53    pub username: String,
54}
55
56impl User {
57    pub fn get_email(&self) -> String {
58        self.email.clone()
59    }
60
61    pub fn get_username(&self) -> String {
62        self.username.clone()
63    }
64}
65
66impl Default for User {
67    fn default() -> Self {
68        Self {
69            email: "user@superposition.io".into(),
70            username: "superposition".into(),
71        }
72    }
73}
74
75#[cfg(feature = "server")]
76impl FromRequest for User {
77    type Error = actix_web::error::Error;
78    type Future = Ready<Result<Self, Self::Error>>;
79
80    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
81        if let Some(user) = req.extensions().get::<Self>() {
82            ready(Ok(user.to_owned()))
83        } else {
84            log::error!("No user was found while validating token");
85            ready(Err(actix_web::error::ErrorUnauthorized(
86                serde_json::json!({"message":"invalid token provided"}),
87            )))
88        }
89    }
90}
91
92#[derive(Clone, Debug, PartialEq, Copy, Serialize)]
93pub struct Cac<T>(T);
94impl<T> Cac<T> {
95    pub fn into_inner(self) -> T {
96        self.0
97    }
98}
99
100#[derive(Clone, Debug, PartialEq, Copy, Serialize)]
101pub struct Exp<T>(T);
102impl<T> Exp<T> {
103    pub fn into_inner(self) -> T {
104        self.0
105    }
106}
107
108const ALPHANUMERIC_WITH_DOT: &str =
109    "^[a-zA-Z0-9-_]([a-zA-Z0-9-_.]{0,254}[a-zA-Z0-9-_])?$";
110const ALPHANUMERIC_WITH_DOT_WORDS: &str =
111    "It can contain the following characters only [a-zA-Z0-9-_.] \
112                                    and it should not start or end with a '.' character.";
113
114const ALPHANUMERIC_WITHOUT_DOT: &str = "^[a-zA-Z0-9-_]{1,64}$";
115const ALPHANUMERIC_WITHOUT_DOT_WORDS: &str =
116    "It can contain the following characters only [a-zA-Z0-9-_]";
117
118pub enum RegexEnum {
119    DefaultConfigKey,
120    DimensionName,
121    FunctionName,
122    TypeTemplateName,
123}
124
125impl RegexEnum {
126    pub fn match_regex(&self, val: &str) -> Result<(), String> {
127        let regex_str = self.to_string();
128        let regex = Regex::new(regex_str.as_str()).map_err(|err| {
129            log::error!("error while validating with regex : {err}");
130            "Something went wrong".to_string()
131        })?;
132
133        if !regex.is_match(val) {
134            Err(format!(
135                "{val} is invalid, it should obey the regex {regex_str}. \
136                {}",
137                self.get_error_message()
138            ))
139        } else {
140            Ok(())
141        }
142    }
143
144    fn get_error_message(&self) -> String {
145        match self {
146            Self::DefaultConfigKey => ALPHANUMERIC_WITH_DOT_WORDS,
147            Self::DimensionName => ALPHANUMERIC_WITH_DOT_WORDS,
148            Self::FunctionName => ALPHANUMERIC_WITHOUT_DOT_WORDS,
149            Self::TypeTemplateName => ALPHANUMERIC_WITHOUT_DOT_WORDS,
150        }
151        .to_string()
152    }
153}
154
155impl Display for RegexEnum {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        let regex = match self {
158            Self::DefaultConfigKey => ALPHANUMERIC_WITH_DOT,
159            Self::DimensionName => ALPHANUMERIC_WITH_DOT,
160            Self::FunctionName => ALPHANUMERIC_WITHOUT_DOT,
161            Self::TypeTemplateName => ALPHANUMERIC_WITHOUT_DOT,
162        }
163        .to_string();
164        write!(f, "{regex}")
165    }
166}
167
168#[derive(Serialize, Debug, Clone, Deserialize)]
169pub struct PaginatedResponse<T> {
170    pub total_pages: i64,
171    pub total_items: i64,
172    pub data: Vec<T>,
173}
174
175impl<T> Default for PaginatedResponse<T> {
176    fn default() -> Self {
177        Self {
178            total_pages: 0,
179            total_items: 0,
180            data: Vec::new(),
181        }
182    }
183}
184
185impl<T> PaginatedResponse<T> {
186    pub fn all(data: Vec<T>) -> Self {
187        Self {
188            total_pages: 1,
189            total_items: data.len() as i64,
190            data,
191        }
192    }
193}
194
195#[derive(Serialize, Clone, Deserialize)]
196pub struct ListResponse<T> {
197    pub data: Vec<T>,
198}
199
200impl<T> ListResponse<T> {
201    pub fn new(response: Vec<T>) -> Self {
202        Self { data: response }
203    }
204}
205
206#[derive(
207    Debug,
208    Serialize,
209    Deserialize,
210    Clone,
211    PartialEq,
212    PartialOrd,
213    strum_macros::Display,
214    strum_macros::EnumIter,
215    Default,
216)]
217#[serde(rename_all = "lowercase")]
218#[strum(serialize_all = "lowercase")]
219pub enum SortBy {
220    Desc,
221    #[default]
222    Asc,
223}
224
225impl SortBy {
226    pub fn flip(&self) -> Self {
227        match self {
228            Self::Desc => Self::Asc,
229            Self::Asc => Self::Desc,
230        }
231    }
232
233    pub fn label(&self) -> String {
234        match self {
235            Self::Desc => "Descending".to_string(),
236            Self::Asc => "Ascending".to_string(),
237        }
238    }
239}
240
241#[cfg(feature = "diesel_derives")]
242pub type DBConnection = PooledConnection<ConnectionManager<PgConnection>>;
243
244#[derive(Serialize, Deserialize, Clone, Debug, Default, Deref, DerefMut, PartialEq)]
245#[cfg_attr(
246    feature = "diesel_derives",
247    derive(AsExpression, FromSqlRow, JsonFromSql, JsonToSql)
248)]
249#[cfg_attr(feature = "diesel_derives", diesel(sql_type = Json))]
250pub struct ExtendedMap(Map<String, Value>);
251uniffi::custom_type!(ExtendedMap, HashMap<String, String>);
252
253impl TryFrom<HashMap<String, String>> for ExtendedMap {
254    type Error = std::io::Error;
255    fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
256        value
257            .into_iter()
258            .map(|(k, s)| {
259                serde_json::from_str::<Value>(&s)
260                    .map(|v| (k, v))
261                    .map_err(|err| {
262                        std::io::Error::new(std::io::ErrorKind::InvalidData, err)
263                    })
264            })
265            .collect::<Result<Map<String, Value>, Self::Error>>()
266            .map(Self)
267    }
268}
269
270impl From<ExtendedMap> for HashMap<String, String> {
271    fn from(value: ExtendedMap) -> Self {
272        value
273            .iter()
274            .map(|(k, v)| (k.clone(), serde_json::to_string(v).unwrap()))
275            .collect::<Self>()
276    }
277}
278
279impl TryFrom<Value> for ExtendedMap {
280    type Error = String;
281    fn try_from(value: Value) -> Result<Self, Self::Error> {
282        value
283            .as_object()
284            .cloned()
285            .map(Self)
286            .ok_or_else(|| "expected a JSON object".to_string())
287    }
288}
289
290impl From<ExtendedMap> for Value {
291    fn from(value: ExtendedMap) -> Self {
292        Self::Object(value.0)
293    }
294}
295
296impl From<&ExtendedMap> for Value {
297    fn from(value: &ExtendedMap) -> Self {
298        Self::Object(value.0.clone())
299    }
300}
301
302impl From<Map<String, Value>> for ExtendedMap {
303    fn from(value: Map<String, Value>) -> Self {
304        Self(value)
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311    use serde_json::{json, Map, Value};
312
313    #[test]
314    fn ok_test_deserialize_condition() {
315        let db_request_condition_map: Map<String, Value> = Map::from_iter(vec![(
316            "and".to_string(),
317            json!([
318                {
319                "==": [
320                    {
321                        "var": "clientId"
322                    },
323                    "meesho"
324                ]
325                }
326            ]),
327        )]);
328
329        let default_request_condition_map: Map<String, Value> = Map::from_iter(vec![(
330            "and".to_string(),
331            json!([
332                {
333                "==": [
334                    {
335                        "var": "os"
336                    },
337                    "ios"
338                ]
339                }
340            ]),
341        )]);
342
343        let exp_request_condition_map: Map<String, Value> = Map::from_iter(vec![(
344            "and".to_string(),
345            json!([
346                {
347                "==": [
348                    {
349                        "var": "clientId"
350                    },
351                    "meesho"
352                ]
353                }
354            ]),
355        )]);
356
357        let db_condition = serde_json::from_value::<Condition>(Value::Object(
358            db_request_condition_map.clone(),
359        ))
360        .unwrap();
361        let db_expected_condition =
362            Cac::<Condition>::validate_db_data(db_request_condition_map)
363                .map(|a| a.into_inner());
364        assert_eq!(Ok(db_condition), db_expected_condition);
365
366        let default_condition = serde_json::from_str::<Condition>(
367            &json!(default_request_condition_map).to_string(),
368        )
369        .unwrap();
370        let default_expected_condition =
371            Cac::<Condition>::try_from(default_request_condition_map)
372                .map(|a| a.into_inner());
373        assert_eq!(Ok(default_condition), default_expected_condition);
374
375        let exp_condition = serde_json::from_str::<Condition>(
376            &json!(exp_request_condition_map).to_string(),
377        )
378        .unwrap();
379        let exp_expected_condition =
380            Exp::<Condition>::try_from(exp_request_condition_map).map(|a| a.into_inner());
381        assert_eq!(Ok(exp_condition), exp_expected_condition);
382    }
383
384    #[test]
385    fn fail_test_deserialize_condition() {
386        #[cfg(feature = "jsonlogic")]
387        let request_condition_map: Map<String, Value> = Map::from_iter(vec![(
388            "and".to_string(),
389            json!([
390                {
391                ".": [
392                    {
393                        "var": "clientId"
394                    },
395                    "meesho"
396                ]
397                }
398            ]),
399        )]);
400
401        #[cfg(feature = "jsonlogic")]
402        let exp_condition_map: Map<String, Value> = Map::from_iter(vec![(
403            "and".to_string(),
404            json!([
405                {
406                "in": [
407                    "variant-id",
408                    {
409                        "var": "variantIds"
410                    }
411                ]
412                }
413            ]),
414        )]);
415
416        #[cfg(not(feature = "jsonlogic"))]
417        let exp_condition_map: Map<String, Value> =
418            Map::from_iter(vec![("variantIds".to_string(), json!("variant-id"))]);
419
420        #[cfg(feature = "jsonlogic")]
421        let fail_condition = serde_json::from_str::<Cac<Condition>>(
422            &json!(request_condition_map).to_string(),
423        )
424        .map_err(|_| "Invalid operation".to_owned());
425
426        let fail_exp_condition = Exp::<Condition>::try_from(exp_condition_map.clone())
427            .map(|a| a.into_inner())
428            .map_err(|_| "variantIds should not be present".to_owned());
429
430        #[cfg(feature = "jsonlogic")]
431        assert!(json!(fail_condition)
432            .to_string()
433            .contains("Invalid operation"));
434
435        assert!(json!(fail_exp_condition)
436            .to_string()
437            .contains("variantIds should not be present"));
438
439        let db_expected_condition = Exp::<Condition>::validate_db_data(exp_condition_map)
440            .map(|_| true)
441            .map_err(|_| "variantIds should not be present".to_string());
442
443        assert!(json!(db_expected_condition)
444            .to_string()
445            .contains("variantIds should not be present"));
446    }
447
448    #[test]
449    fn test_deserialize_override() {
450        let override_map = Map::from_iter(vec![
451            ("key1".to_string(), json!("val1")),
452            ("key2".to_string(), json!(5)),
453        ]);
454
455        let empty_override_map = Map::new();
456
457        let deserialize_overrides =
458            serde_json::from_value::<Overrides>(Value::Object(override_map.clone()))
459                .unwrap();
460        let db_expected_overrides =
461            Cac::<Overrides>::validate_db_data(override_map.clone())
462                .map(|a| a.into_inner());
463        assert_eq!(Ok(deserialize_overrides.clone()), db_expected_overrides);
464
465        let exp_expected_overrides =
466            Exp::<Overrides>::try_from(override_map.clone()).map(|a| a.into_inner());
467        assert_eq!(Ok(deserialize_overrides.clone()), exp_expected_overrides);
468
469        let default_expected_overrides =
470            Cac::<Overrides>::try_from(override_map.clone()).map(|a| a.into_inner());
471        assert_eq!(Ok(deserialize_overrides), default_expected_overrides);
472
473        let empty_overrides = serde_json::from_str::<Cac<Overrides>>(
474            &json!(empty_override_map.clone()).to_string(),
475        )
476        .map_err(|_| "override should not be empty".to_string());
477
478        assert!(json!(empty_overrides)
479            .to_string()
480            .contains("override should not be empty"));
481    }
482}