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}