Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Commit

Permalink
Merge pull request #26 from flipt-io/add-rollouts
Browse files Browse the repository at this point in the history
feat(rollouts): Add CRUD rollouts and new evaluation operations
  • Loading branch information
yquansah authored Aug 1, 2023
2 parents f25a3bd + 3490d32 commit bf97fd2
Show file tree
Hide file tree
Showing 7 changed files with 456 additions and 8 deletions.
4 changes: 3 additions & 1 deletion src/api/evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub struct BatchEvaluateRequest {
pub request_id: String,
}

#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize)]
#[derive(Debug, Default, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EvaluateRequest {
pub context: HashMap<String, String>,
Expand Down Expand Up @@ -94,4 +94,6 @@ pub enum Reason {
Match,
#[serde(rename = "ERROR_EVALUATION_REASON")]
Error,
#[serde(rename = "DEFAULT_EVALUATION_REASON")]
Default,
}
15 changes: 13 additions & 2 deletions src/api/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub struct FlagCreateRequest {
pub name: String,
pub description: String,
pub enabled: bool,
pub r#type: Option<FlagType>,
}

#[derive(Debug, Serialize, Default)]
Expand Down Expand Up @@ -141,23 +142,33 @@ impl FlagListRequest {
}
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Flag {
pub namespace_key: String,
pub key: String,
pub name: String,
pub description: String,
pub enabled: bool,
pub r#type: Option<FlagType>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub variants: Vec<Variant>,
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FlagList {
pub flags: Vec<Flag>,
pub next_page_token: String,
pub total_count: usize,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FlagType {
#[default]
#[serde(rename = "VARIANT_FLAG_TYPE")]
Variant,
#[serde(rename = "BOOLEAN_FLAG_TYPE")]
Boolean,
}
5 changes: 5 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod distribution;
pub mod evaluation;
pub mod flag;
pub mod namespace;
pub mod rollout;
pub mod rule;
pub mod segment;
pub mod variant;
Expand Down Expand Up @@ -56,6 +57,10 @@ impl ApiClient {
distribution::DistributionClient::new(self)
}

pub fn rollouts(&self) -> rollout::RolloutClient {
rollout::RolloutClient::new(self)
}

pub fn rules(&self) -> rule::RuleClient {
rule::RuleClient::new(self)
}
Expand Down
169 changes: 169 additions & 0 deletions src/api/rollout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use crate::api::{ApiClient, Result, DEFAULT_NAMESPACE};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

pub struct RolloutClient<'client> {
client: &'client ApiClient,
}

impl<'client> RolloutClient<'client> {
pub fn new(client: &'client ApiClient) -> Self {
Self { client }
}

pub async fn get(&self, get: &RolloutGetRequest) -> Result<Rollout> {
let path = format!(
"/api/v1/namespaces/{namespace_key}/flags/{flag_key}/rollouts/{id}",
namespace_key = get
.namespace_key
.as_ref()
.unwrap_or(&DEFAULT_NAMESPACE.to_string()),
flag_key = get.flag_key,
id = get.id
);

self.client.get(&path, None::<&()>).await
}

pub async fn create(&self, create: &RolloutCreateRequest) -> Result<Rollout> {
let path = format!(
"/api/v1/namespaces/{namespace_key}/flags/{flag_key}/rollouts",
namespace_key = create
.namespace_key
.as_ref()
.unwrap_or(&DEFAULT_NAMESPACE.to_string()),
flag_key = create.flag_key
);

self.client.post(&path, Some(create)).await
}

pub async fn delete(&self, delete: &RolloutDeleteRequest) -> Result<Empty> {
let path = format!(
"/api/v1/namespaces/{namespace_key}/flags/{flag_key}/rollouts/{id}",
namespace_key = delete
.namespace_key
.as_ref()
.unwrap_or(&DEFAULT_NAMESPACE.to_string()),
flag_key = delete.flag_key,
id = delete.id
);

self.client.delete(&path, None::<&()>).await
}

pub async fn update(&self, update: &RolloutUpdateRequest) -> Result<Rollout> {
let path = format!(
"/api/v1/namespaces/{namespace_key}/flags/{flag_key}/rollouts/{id}",
namespace_key = update
.namespace_key
.as_ref()
.unwrap_or(&DEFAULT_NAMESPACE.to_string()),
flag_key = update.flag_key,
id = update.id
);

self.client.put(&path, Some(update)).await
}

pub async fn order(&self, order: &RolloutOrderRequest) -> Result<Empty> {
let path = format!(
"/api/v1/namespaces/{namespace_key}/flags/{flag_key}/rollouts/order",
namespace_key = order
.namespace_key
.as_ref()
.unwrap_or(&DEFAULT_NAMESPACE.to_string()),
flag_key = order.flag_key,
);

self.client.put(&path, Some(order)).await
}
}

#[derive(Debug, Clone, Deserialize)]
pub struct Empty {}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RolloutOrderRequest {
#[serde(skip_serializing)]
pub flag_key: String,
#[serde(skip_serializing)]
pub namespace_key: Option<String>,
pub rollout_ids: Vec<String>,
}

#[derive(Debug, Default, Serialize)]
pub struct RolloutGetRequest {
pub id: String,
pub namespace_key: Option<String>,
pub flag_key: String,
}

#[derive(Debug, Default, Serialize)]
pub struct RolloutCreateRequest {
#[serde(skip_serializing)]
pub namespace_key: Option<String>,
#[serde(skip_serializing)]
pub flag_key: String,
pub rank: usize,
pub description: String,
pub threshold: Option<RolloutThreshold>,
pub segment: Option<RolloutSegment>,
}

#[derive(Debug, Default, Serialize)]
pub struct RolloutUpdateRequest {
#[serde(skip_serializing)]
pub id: String,
#[serde(skip_serializing)]
pub namespace_key: Option<String>,
#[serde(skip_serializing)]
pub flag_key: String,
pub rank: u32,
pub description: String,
}

#[derive(Debug, Default, Serialize)]
pub struct RolloutDeleteRequest {
pub namespace_key: Option<String>,
pub flag_key: String,
pub id: String,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum RolloutType {
#[serde(rename = "UNKNOWN_ROLLOUT_TYPE")]
Unknown,
#[serde(rename = "SEGMENT_ROLLOUT_TYPE")]
Segment,
#[serde(rename = "THRESHOLD_ROLLOUT_TYPE")]
Threshold,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Rollout {
pub id: String,
pub rank: u32,
#[serde(rename = "type")]
pub rollout_type: RolloutType,
pub description: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub threshold: Option<RolloutThreshold>,
pub segment: Option<RolloutSegment>,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct RolloutThreshold {
pub percentage: f32,
pub value: bool,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RolloutSegment {
pub segment_key: String,
pub value: bool,
}
125 changes: 125 additions & 0 deletions src/evaluation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use crate::api::{ApiClient, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub struct EvaluationClient<'client> {
client: &'client ApiClient,
}

impl<'client> EvaluationClient<'client> {
pub fn new(client: &'client ApiClient) -> Self {
Self { client }
}

pub async fn boolean(&self, eval: &EvaluateRequest) -> Result<BooleanEvaluation> {
let path = "/evaluate/v1/boolean".to_string();

self.client.post(&path, Some(eval)).await
}

pub async fn variant(&self, eval: &EvaluateRequest) -> Result<VariantEvaluation> {
let path = "/evaluate/v1/variant".to_string();

self.client.post(&path, Some(eval)).await
}

pub async fn batch(&self, batch: &BatchEvaluateRequest) -> Result<BatchEvaluation> {
let path = "/evaluate/v1/batch".to_string();

self.client.post(&path, Some(batch)).await
}
}

#[derive(Debug, Default, Serialize)]
pub struct BatchEvaluateRequest {
pub requests: Vec<EvaluateRequest>,
}

#[derive(Debug, Default, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EvaluateRequest {
pub context: HashMap<String, String>,
pub entity_id: String,
pub namespace_key: String,
pub flag_key: String,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BooleanEvaluation {
pub enabled: bool,
pub reason: Reason,
pub request_id: String,
pub request_duration_millis: f64,
pub timestamp: DateTime<Utc>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VariantEvaluation {
#[serde(rename = "match")]
pub is_match: bool,
pub segment_keys: Vec<String>,
pub reason: Reason,
pub variant_key: String,
pub variant_attachment: String,
pub request_id: String,
pub request_duration_millis: f64,
pub timestamp: DateTime<Utc>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorEvaluation {
pub flag_key: String,
pub namespace_key: String,
pub reason: ErrorEvaluationReason,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchEvaluation {
pub request_id: String,
pub responses: Vec<Response>,
pub request_duration_millis: f64,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Response {
pub r#type: ResponseType,
pub boolean_response: Option<BooleanEvaluation>,
pub variant_response: Option<VariantEvaluation>,
pub error_response: Option<ErrorEvaluation>,
}

#[derive(Debug, Clone, Deserialize)]
pub enum ResponseType {
#[serde(rename = "VARIANT_EVALUATION_RESPONSE_TYPE")]
Variant,
#[serde(rename = "BOOLEAN_EVALUATION_RESPONSE_TYPE")]
Boolean,
#[serde(rename = "ERROR_EVALUATION_RESPONSE_TYPE")]
Error,
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
pub enum ErrorEvaluationReason {
#[serde(rename = "UNKNOWN_ERROR_EVALUATION_REASON")]
Unknown,
#[serde(rename = "NOT_FOUND_ERROR_EVALUATION_REASON")]
NotFound,
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
pub enum Reason {
#[serde(rename = "UNKNOWN_EVALUATION_REASON")]
Unknown,
#[serde(rename = "FLAG_DISABLED_EVALUATION_REASON")]
FlagDisabled,
#[serde(rename = "MATCH_EVALUATION_REASON")]
Match,
#[serde(rename = "DEFAULT_EVALUATION_REASON")]
Default,
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod api;
pub mod auth;
pub mod error;
pub mod evaluation;
pub mod meta;

use anyhow::Result;
Expand Down
Loading

0 comments on commit bf97fd2

Please sign in to comment.