Skip to main content

statsig_rust/
statsig_types_raw.rs

1use std::collections::HashMap;
2
3use serde::Serialize;
4use serde_with::skip_serializing_none;
5
6use crate::evaluation::dynamic_returnable::DynamicReturnable;
7use crate::evaluation::evaluation_details::EvaluationDetails;
8use crate::interned_string::InternedString;
9use crate::specs_response::explicit_params::ExplicitParameters;
10use crate::user::StatsigUserLoggable;
11use crate::{log_e, SecondaryExposure};
12
13const TAG: &str = "SpecTypesRaw";
14
15#[derive(Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct FeatureGateRaw<'a> {
18    pub name: &'a str,
19
20    pub value: bool,
21
22    #[serde(rename = "ruleID")]
23    pub rule_id: SuffixedRuleId<'a>,
24
25    pub id_type: Option<&'a InternedString>,
26
27    pub details: &'a EvaluationDetails,
28}
29
30impl<'a> FeatureGateRaw<'a> {
31    pub fn empty(name: &'a str, details: &'a EvaluationDetails) -> Self {
32        Self {
33            name,
34            value: false,
35            details,
36            rule_id: SuffixedRuleId {
37                rule_id: InternedString::empty_ref(),
38                rule_id_suffix: None,
39            },
40            id_type: None,
41        }
42    }
43
44    pub fn unperformant_to_json_string(&self) -> String {
45        match serde_json::to_string(self) {
46            Ok(s) => s,
47            Err(e) => {
48                log_e!(TAG, "Failed to convert FeatureGateRaw to string: {}", e);
49                format!(r#"{{"name": "{}", "value": false}}"#, self.name)
50            }
51        }
52    }
53}
54
55#[derive(Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct DynamicConfigRaw<'a> {
58    pub name: &'a str,
59
60    pub value: Option<&'a DynamicReturnable>,
61
62    #[serde(rename = "ruleID")]
63    pub rule_id: SuffixedRuleId<'a>,
64
65    pub id_type: Option<&'a InternedString>,
66
67    pub details: &'a EvaluationDetails,
68}
69
70impl<'a> DynamicConfigRaw<'a> {
71    pub fn empty(name: &'a str, details: &'a EvaluationDetails) -> Self {
72        Self {
73            name,
74            value: None,
75            details,
76            rule_id: SuffixedRuleId {
77                rule_id: InternedString::empty_ref(),
78                rule_id_suffix: None,
79            },
80            id_type: None,
81        }
82    }
83
84    pub fn unperformant_to_json_string(&self) -> String {
85        match serde_json::to_string(self) {
86            Ok(s) => s,
87            Err(e) => {
88                log_e!(TAG, "Failed to convert DynamicConfigRaw to string: {}", e);
89                format!(r#"{{"name": "{}", "value": null}}"#, self.name)
90            }
91        }
92    }
93}
94
95#[derive(Serialize)]
96#[serde(rename_all = "camelCase")]
97pub struct ExperimentRaw<'a> {
98    pub name: &'a str,
99
100    pub value: Option<&'a DynamicReturnable>,
101
102    #[serde(rename = "ruleID")]
103    pub rule_id: SuffixedRuleId<'a>,
104
105    pub id_type: Option<&'a InternedString>,
106
107    pub group_name: Option<&'a InternedString>,
108
109    pub is_experiment_active: Option<bool>,
110
111    pub details: &'a EvaluationDetails,
112
113    pub secondary_exposures: Option<&'a Vec<SecondaryExposure>>,
114}
115
116impl<'a> ExperimentRaw<'a> {
117    pub fn empty(name: &'a str, details: &'a EvaluationDetails) -> Self {
118        Self {
119            name,
120            value: None,
121            details,
122            rule_id: SuffixedRuleId {
123                rule_id: InternedString::empty_ref(),
124                rule_id_suffix: None,
125            },
126            id_type: None,
127            group_name: None,
128            is_experiment_active: None,
129            secondary_exposures: None,
130        }
131    }
132
133    pub fn unperformant_to_json_string(&self) -> String {
134        match serde_json::to_string(self) {
135            Ok(s) => s,
136            Err(e) => {
137                log_e!(TAG, "Failed to convert ExperimentRaw to string: {}", e);
138                format!(r#"{{"name": "{}"}}"#, self.name)
139            }
140        }
141    }
142}
143
144fn is_interned_string_none_or_empty(value: &Option<&InternedString>) -> bool {
145    match value {
146        Some(value) => value.is_empty(),
147        None => true,
148    }
149}
150
151#[derive(Serialize)]
152#[serde(rename_all = "camelCase")]
153#[skip_serializing_none]
154pub struct LayerRaw<'a> {
155    pub name: &'a str,
156
157    pub value: Option<&'a DynamicReturnable>,
158
159    #[serde(rename = "ruleID")]
160    pub rule_id: SuffixedRuleId<'a>,
161
162    pub id_type: Option<&'a InternedString>,
163
164    pub group_name: Option<&'a InternedString>,
165
166    pub is_experiment_active: Option<bool>,
167
168    pub details: &'a EvaluationDetails,
169
170    #[serde(skip_serializing_if = "is_interned_string_none_or_empty")]
171    pub allocated_experiment_name: Option<&'a InternedString>,
172
173    pub disable_exposure: bool,
174
175    pub user: StatsigUserLoggable,
176
177    pub secondary_exposures: Option<&'a Vec<SecondaryExposure>>,
178
179    pub undelegated_secondary_exposures: Option<&'a Vec<SecondaryExposure>>,
180
181    pub explicit_parameters: Option<ExplicitParameters>,
182
183    pub parameter_rule_ids: Option<&'a HashMap<InternedString, InternedString>>,
184}
185
186impl<'a> LayerRaw<'a> {
187    pub fn empty(name: &'a str, details: &'a EvaluationDetails) -> Self {
188        Self {
189            name,
190            details,
191            rule_id: SuffixedRuleId {
192                rule_id: InternedString::empty_ref(),
193                rule_id_suffix: None,
194            },
195            id_type: None,
196            group_name: None,
197            is_experiment_active: None,
198            value: None,
199            allocated_experiment_name: None,
200            disable_exposure: false,
201            user: StatsigUserLoggable::null(),
202            secondary_exposures: None,
203            undelegated_secondary_exposures: None,
204            explicit_parameters: None,
205            parameter_rule_ids: None,
206        }
207    }
208
209    pub fn unperformant_to_json_string(&self) -> String {
210        match serde_json::to_string(self) {
211            Ok(s) => s,
212            Err(e) => {
213                log_e!(TAG, "Failed to convert LayerRaw to string: {}", e);
214                format!(r#"{{"name": "{}"}}"#, self.name)
215            }
216        }
217    }
218}
219
220#[derive(serde::Deserialize)]
221#[serde(rename_all = "camelCase")]
222#[cfg(feature = "ffi-support")]
223pub struct PartialLayerRaw {
224    pub name: InternedString,
225
226    #[serde(rename = "ruleID")]
227    pub rule_id: Option<InternedString>,
228
229    pub id_type: Option<InternedString>,
230
231    pub group_name: Option<InternedString>,
232
233    pub details: EvaluationDetails,
234
235    pub allocated_experiment_name: Option<InternedString>,
236    pub disable_exposure: bool,
237    pub user: StatsigUserLoggable,
238    pub secondary_exposures: Option<Vec<SecondaryExposure>>,
239    pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
240    pub explicit_parameters: Option<ExplicitParameters>,
241    pub parameter_rule_ids: Option<HashMap<InternedString, InternedString>>,
242}
243
244pub struct SuffixedRuleId<'a> {
245    pub rule_id: &'a InternedString,
246    pub rule_id_suffix: Option<&'a str>,
247}
248
249impl<'a> std::fmt::Display for SuffixedRuleId<'a> {
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        f.write_str(self.rule_id.as_str())?;
252        if let Some(suffix) = self.rule_id_suffix {
253            f.write_str(":")?;
254            f.write_str(suffix)?;
255        }
256        Ok(())
257    }
258}
259
260impl<'a> Serialize for SuffixedRuleId<'a> {
261    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
262    where
263        S: serde::Serializer,
264    {
265        serializer.collect_str(self)
266    }
267}