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