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")]
38pub 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}