Skip to content

Commit

Permalink
feat: allow ClientFeatures to apply deltas to itself (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswk authored Jan 7, 2025
1 parent b7526d0 commit 15a7eca
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 25 deletions.
94 changes: 94 additions & 0 deletions examples/delta_base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"revisionId": 1,
"updated": [
{
"name": "test-flag",
"type": "release",
"enabled": false,
"project": "default",
"stale": false,
"strategies": [
{
"name": "flexibleRollout",
"constraints": [],
"parameters": {
"groupId": "test-flag",
"rollout": "100",
"stickiness": "default"
},
"variants": []
}
],
"variants": [],
"description": null,
"impressionData": false
},
{
"name": "segment-flag",
"type": "release",
"enabled": true,
"project": "default",
"stale": false,
"strategies": [
{
"name": "flexibleRollout",
"constraints": [],
"parameters": {
"groupId": "test-flag",
"rollout": "100",
"stickiness": "default"
},
"variants": [],
"segments": [
1
]
}
],
"variants": [],
"description": null,
"impressionData": false
},
{
"name": "removed-flag",
"type": "release",
"enabled": true,
"project": "default",
"stale": false,
"strategies": [
{
"name": "flexibleRollout",
"constraints": [],
"parameters": {
"groupId": "test-flag",
"rollout": "100",
"stickiness": "default"
},
"variants": []
}
],
"variants": [],
"description": null,
"impressionData": false
}
],
"removed": [],
"segments": [
{
"id": 1,
"name": "VIPUsers",
"constraints": [
{
"values": [
"1",
"2",
"3"
],
"inverted": false,
"operator": "IN",
"contextName": "userId",
"caseInsensitive": false
}
]
}
]
}
73 changes: 73 additions & 0 deletions examples/delta_patch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"revisionId": 2,
"updated": [
{
"name": "test-flag",
"type": "release",
"enabled": true,
"project": "default",
"stale": false,
"strategies": [
{
"name": "flexibleRollout",
"constraints": [],
"parameters": {
"groupId": "test-flag",
"rollout": "100",
"stickiness": "default"
},
"variants": []
}
],
"variants": [],
"description": null,
"impressionData": false
},
{
"name": "segment-flag",
"type": "release",
"enabled": true,
"project": "default",
"stale": false,
"strategies": [
{
"name": "flexibleRollout",
"constraints": [],
"parameters": {
"groupId": "test-flag",
"rollout": "100",
"stickiness": "default"
},
"variants": [],
"segments": [
1
]
}
],
"variants": [],
"description": null,
"impressionData": false
}
],
"removed": ["removed-flag"],
"segments": [
{
"id": 1,
"name": "VIPUsers",
"constraints": [
{
"values": [
"1",
"2",
"3",
"4"
],
"inverted": false,
"operator": "IN",
"contextName": "userId",
"caseInsensitive": false
}
]
}
]
}
70 changes: 69 additions & 1 deletion src/client_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,12 +492,42 @@ pub struct ClientFeaturesDelta {
pub segments: Option<Vec<Segment>>,
}

impl ClientFeatures {
/// Modifies the current ClientFeatures instance by applying the delta.
pub fn modify_in_place(&mut self, delta: &ClientFeaturesDelta) {
let mut features = self.features.clone();
features.retain(|f| !delta.removed.contains(&f.name));
self.features = features.merge(delta.updated.clone());
self.features.sort();
self.segments = delta.segments.clone();
}

/// Returns a new ClientFeatures instance with the delta applied.
pub fn modify_and_copy(&self, delta: &ClientFeaturesDelta) -> ClientFeatures {
let mut features = self.features.clone();
features.retain(|f| !delta.removed.contains(&f.name));
let mut updated_features = features.merge(delta.updated.clone());
updated_features.sort();
let segments = delta.segments.clone();
ClientFeatures {
version: self.version,
features: updated_features,
segments,
query: self.query.clone(),
meta: self.meta.clone(),
}
}
}

#[cfg(test)]
mod tests {
use serde_qs::Config;
use std::{fs::File, io::BufReader, path::PathBuf};

use crate::{client_features::ClientFeature, Merge, Upsert};
use crate::{
client_features::{ClientFeature, ClientFeaturesDelta},
Merge, Upsert,
};

use super::{ClientFeatures, Constraint, Operator, Segment, Strategy};
use crate::client_features::Context;
Expand Down Expand Up @@ -680,6 +710,44 @@ mod tests {
assert!(prop_map.contains_key("email"));
}

#[test_case("./examples/delta_base.json".into(), "./examples/delta_patch.json".into(); "Base and delta")]
pub fn can_take_delta_updates(base: PathBuf, delta: PathBuf) {
let base_delta: ClientFeaturesDelta =
serde_json::from_reader(read_file(base).unwrap()).unwrap();
let mut features = ClientFeatures {
version: 2,
features: vec![],
segments: None,
query: None,
meta: None,
};
features.modify_in_place(&base_delta);
assert_eq!(features.features.len(), 3);
let delta: ClientFeaturesDelta =
serde_json::from_reader(read_file(delta).unwrap()).unwrap();
features.modify_in_place(&delta);
assert_eq!(features.features.len(), 2);
}

#[test_case("./examples/delta_base.json".into(), "./examples/delta_patch.json".into(); "Base and delta")]
pub fn can_apply_delta_updates(base: PathBuf, delta: PathBuf) {
let base_delta: ClientFeaturesDelta =
serde_json::from_reader(read_file(base).unwrap()).unwrap();
let features = ClientFeatures {
version: 2,
features: vec![],
segments: None,
query: None,
meta: None,
};
let changed = features.modify_and_copy(&base_delta);
assert_eq!(changed.features.len(), 3);
let delta: ClientFeaturesDelta =
serde_json::from_reader(read_file(delta).unwrap()).unwrap();
let second_change = changed.modify_and_copy(&delta);
assert_eq!(second_change.features.len(), 2);
}

#[test]
pub fn when_strategy_variants_is_none_default_to_empty_vec() {
let client_features = ClientFeatures {
Expand Down
28 changes: 4 additions & 24 deletions src/client_metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ pub struct ConnectVia {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Builder)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
#[derive(Default)]
pub struct ClientApplication {
pub app_name: String,
pub connect_via: Option<Vec<ConnectVia>>,
Expand All @@ -146,6 +147,7 @@ pub struct ClientApplication {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Builder)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
#[derive(Default)]
pub struct MetricsMetadata {
pub sdk_version: Option<String>,
pub yggdrasil_version: Option<String>,
Expand Down Expand Up @@ -249,31 +251,9 @@ mod tests {

use super::*;

impl Default for ClientApplication {
fn default() -> Self {
Self {
app_name: Default::default(),
connect_via: Default::default(),
environment: Default::default(),
instance_id: Default::default(),
interval: Default::default(),
started: Default::default(),
strategies: Default::default(),
metadata: Default::default(),
}
}
}


impl Default for MetricsMetadata {
fn default() -> Self {
Self {
sdk_version: Default::default(),
yggdrasil_version: Default::default(),
platform_name: Default::default(),
platform_version: Default::default(),
}
}
}


#[test]
pub fn can_increment_counts() {
Expand Down

0 comments on commit 15a7eca

Please sign in to comment.