flagrant_types/
lib.rs

1use serde::{Deserialize, Serialize};
2use serde_valid::Validate;
3use std::fmt;
4
5extern crate regex;
6
7#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
8pub struct Project {
9    #[sqlx(rename = "project_id")]
10    pub id: u16,
11    pub name: String,
12}
13
14#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
15pub struct Environment {
16    #[sqlx(rename = "environment_id")]
17    pub id: u16,
18    pub project_id: u16,
19    pub name: String,
20    pub description: Option<String>,
21}
22
23#[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Validate)]
24pub struct Feature {
25    #[sqlx(rename = "feature_id")]
26    pub id: u16,
27    pub project_id: u16,
28    #[validate(pattern = r"^[A-Za-z][A-Za-z0-9_]+$")]
29    #[validate(max_length = 255)]
30    pub name: String,
31    pub variants: Vec<Variant>,
32    pub value_type: FeatureValueType,
33    pub is_enabled: bool,
34}
35
36#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, sqlx::Type)]
37#[sqlx(type_name = "value_type", rename_all = "lowercase")]
38// #[serde(rename_all = "lowercase")]
39pub enum FeatureValueType {
40    #[default]
41    Text,
42    Json,
43    Toml,
44}
45
46#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
47pub struct FeatureValue(pub String, pub FeatureValueType);
48
49#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, sqlx::FromRow)]
50pub struct Variant {
51    #[sqlx(rename = "variant_id")]
52    pub id: u16,
53    pub value: String,
54    pub weight: i16,
55    pub accumulator: i16,
56    pub environment_id: Option<u16>,
57}
58
59#[derive(Debug, Serialize, Deserialize)]
60pub struct EnvRequestPayload {
61    pub name: String,
62    pub description: Option<String>,
63}
64
65#[derive(Debug, Serialize, Deserialize)]
66pub struct FeatureRequestPayload {
67    pub name: String,
68    pub value: Option<FeatureValue>,
69    pub description: Option<String>,
70    pub is_enabled: bool,
71}
72
73#[derive(Debug, Serialize, Deserialize)]
74pub struct VariantRequestPayload {
75    pub value: String,
76    pub weight: i16,
77}
78
79impl From<Feature> for FeatureRequestPayload {
80    fn from(feature: Feature) -> Self {
81        FeatureRequestPayload {
82            name: feature.name,
83            value: feature
84                .variants
85                .first()
86                .map(|v| FeatureValue(v.value.clone(), feature.value_type)),
87            description: None,
88            is_enabled: feature.is_enabled,
89        }
90    }
91}
92
93impl Feature {
94    pub fn get_default_variant(&self) -> Option<&Variant> {
95        self.variants.iter().find(|v| v.is_control())
96    }
97    pub fn get_default_value(&self) -> Option<FeatureValue> {
98        if let Some(variant) = self.get_default_variant() {
99            return Some(FeatureValue(variant.value.clone(), self.value_type.clone()));
100        }
101        None
102    }
103    pub fn with_variants(mut self, variants: Vec<Variant>) -> Self {
104        self.variants = variants;
105        self
106    }
107}
108
109impl Variant {
110    pub fn build(id: u16, value: String, weight: i16) -> Variant {
111        Variant {
112            id,
113            value,
114            weight,
115            accumulator: 100,
116            environment_id: None,
117        }
118    }
119    pub fn build_default(environment: &Environment, id: u16, value: String) -> Variant {
120        Variant {
121            id,
122            value,
123            weight: 100,
124            accumulator: 100,
125            environment_id: Some(environment.id),
126        }
127    }
128    pub fn is_control(&self) -> bool {
129        self.environment_id.is_some()
130    }
131}
132
133impl fmt::Display for FeatureValueType {
134    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135        write!(f, "{:?}", self)
136    }
137}
138
139impl From<&str> for FeatureValueType {
140    fn from(value: &str) -> Self {
141        match value {
142            "json" => Self::Json,
143            "toml" => Self::Toml,
144            _ => Self::default(),
145        }
146    }
147}
148
149pub trait Tabular {
150    fn tabular_print(&self);
151}
152
153impl Tabular for Feature {
154    fn tabular_print(&self) {
155        let toggle = if self.is_enabled { "▣" } else { "▢" };
156        let value = self
157            .get_default_variant()
158            .map(|v| v.value.as_str())
159            .unwrap_or_else(|| "");
160
161        println!(
162            "│ {:<8}: {}\n│ {:<8}: {}\n│ {:<8}: {toggle} {}\n│ {:<8}: {}\n│ {:<8}: {}",
163            "ID",
164            self.id,
165            "NAME",
166            self.name,
167            "ENABLED",
168            self.is_enabled,
169            "TYPE",
170            self.value_type,
171            "VALUE",
172            value
173        )
174    }
175}
176
177impl Tabular for Variant {
178    fn tabular_print(&self) {
179        println!(
180            "│ {:<8}: {}\n│ {:<8}: {}\n│ {:<8}: {}",
181            "ID", self.id, "WEIGHT", self.weight, "VALUE", self.value
182        )
183    }
184}