use std::cmp::Ordering;
use crate::errors::FlagrantError;
use flagrant_types::{Environment, Feature, FeatureValue, Variant};
use hugsqlx::{params, HugSqlx};
use serde_valid::Validate;
use sqlx::{sqlite::SqliteRow, Acquire, Pool, Row, Sqlite, SqliteConnection};
use super::variant;
#[derive(HugSqlx)]
#[queries = "resources/db/queries/features.sql"]
struct Features {}
pub async fn create(
pool: &Pool<Sqlite>,
environment: &Environment,
name: String,
value: Option<FeatureValue>,
is_enabled: bool,
) -> anyhow::Result<Feature> {
let mut tx = pool.begin().await?;
let mut feature = Features::create_feature(
&mut *tx,
params![
environment.project_id,
name,
is_enabled,
value
.as_ref()
.map(|FeatureValue(_, value_type)| value_type.clone())
.unwrap_or_default()
],
|row| row_to_feature(row, environment),
)
.await
.map_err(|e| FlagrantError::QueryFailed("Could not create a feature", e))?;
if let Some(FeatureValue(value, _)) = value {
set_default_value(&mut tx, environment, &mut feature, value).await?;
}
feature.validate()?;
tx.commit().await?;
Ok(feature)
}
pub async fn set_default_value(
conn: &mut SqliteConnection,
environment: &Environment,
feature: &mut Feature,
value: String,
) -> anyhow::Result<()> {
let variant = variant::upsert_default(conn, environment, feature, value).await?;
feature.variants.insert(0, variant);
Ok(())
}
pub async fn fetch(
pool: &Pool<Sqlite>,
environment: &Environment,
feature_id: u16,
) -> anyhow::Result<Feature> {
let feature = Features::fetch_feature(pool, params![feature_id], |row| {
row_to_feature(row, environment)
})
.await
.map_err(|e| FlagrantError::QueryFailed("Could not fetch a feature", e))?;
let variants = variant::list(pool, environment, &feature)
.await
.unwrap_or_default();
Ok(feature.with_variants(variants))
}
pub async fn fetch_by_name(
pool: &Pool<Sqlite>,
environment: &Environment,
name: String,
) -> anyhow::Result<Feature> {
let feature =
Features::fetch_feature_by_name(pool, params![environment.project_id, name], |row| {
row_to_feature(row, environment)
})
.await
.map_err(|e| FlagrantError::QueryFailed("Could not fetch a feature", e))?;
let variants = variant::list(pool, environment, &feature)
.await
.unwrap_or_default();
Ok(feature.with_variants(variants))
}
pub async fn fetch_by_prefix(
pool: &Pool<Sqlite>,
environment: &Environment,
prefix: String,
) -> anyhow::Result<Vec<Feature>> {
let features = Features::fetch_features_by_pattern(
pool,
params![environment.project_id, environment.id, format!("{prefix}%")],
|row| row_to_feature(row, environment),
)
.await
.map_err(|e| FlagrantError::QueryFailed("Could not fetch a feature", e))?;
Ok(features)
}
pub async fn list(pool: &Pool<Sqlite>, environment: &Environment) -> anyhow::Result<Vec<Feature>> {
Ok(Features::fetch_features_for_environment(
pool,
params![environment.project_id, environment.id],
|row| row_to_feature(row, environment),
)
.await
.map_err(|e| FlagrantError::QueryFailed("Could not fetch list of features", e))?)
}
pub async fn update(
pool: &Pool<Sqlite>,
environment: &Environment,
feature: &Feature,
new_name: String,
new_value: Option<FeatureValue>,
is_enabled: bool,
) -> anyhow::Result<()> {
let mut tx = pool.begin().await?;
let new_value_type = new_value
.as_ref()
.map(|FeatureValue(_, t)| t)
.unwrap_or_else(|| &feature.value_type);
Features::update_feature(
&mut *tx,
params![feature.id, new_name, new_value_type, is_enabled],
)
.await
.map_err(|e| FlagrantError::QueryFailed("Could not update a feature", e))?;
if let Some(FeatureValue(value, _)) = new_value {
variant::upsert_default(&mut tx, environment, feature, value)
.await
.map_err(|e| match e.downcast::<sqlx::Error>() {
Ok(db_err) => FlagrantError::QueryFailed("Could not update a feature", db_err),
Err(e) => FlagrantError::UnexpectedFailure("Error while updating a feature", e),
})?;
}
tx.commit().await?;
Ok(())
}
pub async fn bump_up_accumulators(
conn: &mut SqliteConnection,
environment: &Environment,
feature: &Feature,
by_value: i16,
) -> anyhow::Result<()> {
Features::update_feature_variants_accumulators(
conn,
params![environment.id, feature.id, by_value],
)
.await
.map_err(|e| FlagrantError::QueryFailed("Could not bump up variants accumulators", e))?;
Ok(())
}
pub async fn delete(
pool: &Pool<Sqlite>,
environment: &Environment,
feature: &Feature,
) -> anyhow::Result<()> {
let mut tx = pool.begin().await?;
let conn = tx.acquire().await?;
let mut vars = variant::list(pool, environment, feature).await?;
vars.sort_by(|a, _| match a.is_control() {
true => Ordering::Greater,
false => Ordering::Less,
});
for var in vars {
variant::delete(conn, environment, &var).await?;
}
Features::delete_variants_for_feature(&mut *tx, params![feature.id]).await?;
Features::delete_feature(&mut *tx, params![feature.id]).await?;
tx.commit().await?;
Ok(())
}
pub(crate) fn row_to_feature(row: SqliteRow, environment: &Environment) -> Feature {
let mut variants = Vec::with_capacity(1);
if let Ok(Some(variant_id)) = row.try_get("variant_id") {
if let Ok(Some(variant_value)) = row.try_get("value") {
variants.push(Variant::build_default(
environment,
variant_id,
variant_value,
))
}
}
Feature {
id: row.get("feature_id"),
project_id: row.get("project_id"),
is_enabled: row.get("is_enabled"),
name: row.get("name"),
value_type: row.get("value_type"),
variants,
}
}