Skip to content

Commit

Permalink
feat: Mandatory dimensions feature (#173)
Browse files Browse the repository at this point in the history
Co-authored-by: Ankit Mahato <[email protected]>
  • Loading branch information
mahatoankitkumar and Ankit Mahato committed Aug 12, 2024
1 parent 46b7342 commit 8d95a30
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 67 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ ENABLE_TENANT_AND_SCOPE=true
TENANTS=dev,test
TENANT_MIDDLEWARE_EXCLUSION_LIST="/health,/assets/favicon.ico,/pkg/frontend.js,/pkg,/pkg/frontend_bg.wasm,/pkg/tailwind.css,/pkg/style.css,/assets,/admin,/"
SERVICE_PREFIX=""
SERVICE_NAME="CAC"
SERVICE_NAME="CAC"
MANDATORY_DIMENSIONS=""
# MANDATORY_DIMENSIONS="dev:clientId,fare;test:fare"
10 changes: 9 additions & 1 deletion crates/context_aware_config/src/api/config/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
helpers::generate_cac,
};
use actix_http::header::HeaderValue;
use actix_web::web::Data;
use actix_web::{get, put, web, HttpRequest, HttpResponse, HttpResponseBuilder, Scope};
use cac_client::{eval_cac, eval_cac_with_reasoning, MergeStrategy};
use chrono::{DateTime, NaiveDateTime, TimeZone, Timelike, Utc};
Expand All @@ -22,6 +23,7 @@ use diesel::{
ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl,
};
use serde_json::{json, Map, Value};
use service_utils::service::types::{AppState, Tenant};
use superposition_macros::{bad_argument, db_error, unexpected_error};
use superposition_types::{result as superposition, Cac, Condition, Overrides, User};

Expand Down Expand Up @@ -363,6 +365,8 @@ fn construct_new_payload(
async fn reduce_config_key(
user: User,
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
state: &Data<AppState>,
tenant: &Tenant,
mut og_contexts: Vec<Context>,
mut og_overrides: HashMap<String, Overrides>,
check_key: &str,
Expand Down Expand Up @@ -450,7 +454,7 @@ async fn reduce_config_key(
if is_approve {
let _ = delete_context_api(cid.clone(), user.clone(), conn);
if let Ok(put_req) = construct_new_payload(request_payload) {
let _ = put(put_req, conn, false, &user);
let _ = put(put_req, conn, false, &user, state, tenant);
}
}

Expand Down Expand Up @@ -493,6 +497,8 @@ async fn reduce_config(
req: HttpRequest,
user: User,
db_conn: DbConnection,
state: Data<AppState>,
tenant: Tenant,
) -> superposition::Result<HttpResponse> {
let DbConnection(mut conn) = db_conn;
let is_approve = req
Expand All @@ -511,6 +517,8 @@ async fn reduce_config(
config = reduce_config_key(
user.clone(),
&mut conn,
&state,
&tenant,
contexts.clone(),
overrides.clone(),
key.as_str(),
Expand Down
122 changes: 81 additions & 41 deletions crates/context_aware_config/src/api/context/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
},
};
use actix_web::web::Data;
use service_utils::service::types::{AppHeader, AppState, CustomHeaders};
use service_utils::service::types::{AppHeader, AppState, CustomHeaders, Tenant};

use actix_web::{
delete, get, post, put,
Expand All @@ -45,7 +45,8 @@ use std::collections::HashMap;
use superposition_types::{SuperpositionUser, User};

use super::helpers::{
validate_condition_with_functions, validate_override_with_functions,
validate_condition_with_functions, validate_condition_with_mandatory_dimensions,
validate_override_with_functions,
};

use superposition_macros::{
Expand Down Expand Up @@ -192,11 +193,18 @@ fn create_ctx_from_put_req(
req: Json<PutReq>,
conn: &mut DBConnection,
user: &User,
state: &Data<AppState>,
tenant: &Tenant,
) -> superposition::Result<Context> {
let ctx_condition = req.context.to_owned().into_inner();
let condition_val = json!(ctx_condition);
let r_override = req.r#override.clone().into_inner();
let ctx_override = json!(r_override.to_owned());
validate_condition_with_mandatory_dimensions(
&ctx_condition,
&state.mandatory_dimensions,
tenant,
)?;
validate_override_with_default_configs(conn, &r_override)?;
validate_condition_with_functions(conn, &ctx_condition)?;
validate_override_with_functions(conn, &r_override)?;
Expand Down Expand Up @@ -299,9 +307,11 @@ pub fn put(
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
already_under_txn: bool,
user: &User,
state: &Data<AppState>,
tenant: &Tenant,
) -> superposition::Result<PutResp> {
use contexts::dsl::contexts;
let new_ctx = create_ctx_from_put_req(req, conn, user)?;
let new_ctx = create_ctx_from_put_req(req, conn, user, &state, tenant)?;

if already_under_txn {
diesel::sql_query("SAVEPOINT put_ctx_savepoint").execute(conn)?;
Expand Down Expand Up @@ -330,15 +340,15 @@ async fn put_handler(
req: Json<PutReq>,
mut db_conn: DbConnection,
user: User,
tenant: Tenant,
) -> superposition::Result<HttpResponse> {
let tags = parse_config_tags(custom_headers.config_tags)?;
db_conn.transaction::<_, superposition::AppError, _>(|transaction_conn| {
let put_response = put(req, transaction_conn, true, &user).map_err(
|err: superposition::AppError| {
let put_response = put(req, transaction_conn, true, &user, &state, &tenant)
.map_err(|err: superposition::AppError| {
log::info!("context put failed with error: {:?}", err);
err
},
)?;
})?;
let version_id = add_config_version(&state, tags, transaction_conn)?;
let mut http_resp = HttpResponse::Ok();

Expand All @@ -355,9 +365,11 @@ fn override_helper(
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
already_under_txn: bool,
user: &User,
state: &Data<AppState>,
tenant: Tenant,
) -> superposition::Result<PutResp> {
use contexts::dsl::contexts;
let new_ctx = create_ctx_from_put_req(req, conn, user)?;
let new_ctx = create_ctx_from_put_req(req, conn, user, state, &tenant)?;
if already_under_txn {
diesel::sql_query("SAVEPOINT insert_ctx_savepoint").execute(conn)?;
}
Expand Down Expand Up @@ -385,15 +397,17 @@ async fn update_override_handler(
req: Json<PutReq>,
mut db_conn: DbConnection,
user: User,
tenant: Tenant,
) -> superposition::Result<HttpResponse> {
let tags = parse_config_tags(custom_headers.config_tags)?;
db_conn.transaction::<_, superposition::AppError, _>(|transaction_conn| {
let override_resp = override_helper(req, transaction_conn, true, &user).map_err(
|err: superposition::AppError| {
log::info!("context put failed with error: {:?}", err);
err
},
)?;
let override_resp =
override_helper(req, transaction_conn, true, &user, &state, tenant).map_err(
|err: superposition::AppError| {
log::info!("context put failed with error: {:?}", err);
err
},
)?;
let version_id = add_config_version(&state, tags, transaction_conn)?;
let mut http_resp = HttpResponse::Ok();

Expand All @@ -411,17 +425,25 @@ fn r#move(
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
already_under_txn: bool,
user: &User,
state: &Data<AppState>,
tenant: &Tenant,
) -> superposition::Result<PutResp> {
use contexts::dsl;
let req = req.into_inner();
let ctx_condition = Value::Object(req.context.into_inner().into());
let new_ctx_id = hash(&ctx_condition);
let ctx_condition = req.context.to_owned().into_inner();
let ctx_condition_value = Value::Object(ctx_condition.into());
let new_ctx_id = hash(&ctx_condition_value);
let dimension_schema_map = get_all_dimension_schema_map(conn)?;
let priority = validate_dimensions_and_calculate_priority(
"context",
&ctx_condition,
&ctx_condition_value,
&dimension_schema_map,
)?;
validate_condition_with_mandatory_dimensions(
&req.context.into_inner(),
&state.mandatory_dimensions,
tenant,
)?;

if priority == 0 {
return Err(bad_argument!("no dimension found in context"));
Expand All @@ -435,7 +457,7 @@ fn r#move(
.filter(dsl::id.eq(&old_ctx_id))
.set((
dsl::id.eq(&new_ctx_id),
dsl::value.eq(&ctx_condition),
dsl::value.eq(&ctx_condition_value),
dsl::priority.eq(priority),
dsl::last_modified_at.eq(Utc::now().naive_utc()),
dsl::last_modified_by.eq(user.get_email()),
Expand All @@ -444,7 +466,7 @@ fn r#move(

let contruct_new_ctx_with_old_overrides = |ctx: Context| Context {
id: new_ctx_id,
value: ctx_condition,
value: ctx_condition_value,
priority,
created_at: Utc::now(),
created_by: user.get_email(),
Expand Down Expand Up @@ -497,14 +519,23 @@ async fn move_handler(
req: Json<MoveReq>,
mut db_conn: DbConnection,
user: User,
tenant: Tenant,
) -> superposition::Result<HttpResponse> {
let tags = parse_config_tags(custom_headers.config_tags)?;
db_conn.transaction::<_, superposition::AppError, _>(|transaction_conn| {
let move_reponse = r#move(path.into_inner(), req, transaction_conn, true, &user)
.map_err(|err| {
log::info!("move api failed with error: {:?}", err);
err
})?;
let move_reponse = r#move(
path.into_inner(),
req,
transaction_conn,
true,
&user,
&state,
&tenant,
)
.map_err(|err| {
log::info!("move api failed with error: {:?}", err);
err
})?;
let version_id = add_config_version(&state, tags, transaction_conn)?;
let mut http_resp = HttpResponse::Ok();

Expand Down Expand Up @@ -638,6 +669,7 @@ async fn bulk_operations(
reqs: Json<Vec<ContextAction>>,
db_conn: DbConnection,
user: User,
tenant: Tenant,
) -> superposition::Result<HttpResponse> {
use contexts::dsl::contexts;
let DbConnection(mut conn) = db_conn;
Expand All @@ -648,14 +680,18 @@ async fn bulk_operations(
for action in reqs.into_inner().into_iter() {
match action {
ContextAction::Put(put_req) => {
let put_resp = put(Json(put_req), transaction_conn, true, &user)
.map_err(|err| {
log::error!(
"Failed at insert into contexts due to {:?}",
err
);
err
})?;
let put_resp = put(
Json(put_req),
transaction_conn,
true,
&user,
&state,
&tenant,
)
.map_err(|err| {
log::error!("Failed at insert into contexts due to {:?}", err);
err
})?;
response.push(ContextBulkResponse::Put(put_resp));
}
ContextAction::Delete(ctx_id) => {
Expand Down Expand Up @@ -683,15 +719,19 @@ async fn bulk_operations(
};
}
ContextAction::Move((old_ctx_id, move_req)) => {
let move_context_resp =
r#move(old_ctx_id, Json(move_req), transaction_conn, true, &user)
.map_err(|err| {
log::error!(
"Failed at moving context reponse due to {:?}",
err
);
err
})?;
let move_context_resp = r#move(
old_ctx_id,
Json(move_req),
transaction_conn,
true,
&user,
&state,
&tenant,
)
.map_err(|err| {
log::error!("Failed at moving context reponse due to {:?}", err);
err
})?;
response.push(ContextBulkResponse::Move(move_context_resp));
}
}
Expand Down
22 changes: 22 additions & 0 deletions crates/context_aware_config/src/api/context/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
extern crate base64;
use base64::prelude::*;
use service_utils::helpers::extract_dimensions;
use service_utils::service::types::Tenant;
use std::str;
use superposition_macros::{unexpected_error, validation_error};
use superposition_types::{result as superposition, Condition};
Expand All @@ -22,6 +23,27 @@ use serde_json::{Map, Value};
use std::collections::HashMap;
type DBConnection = PooledConnection<ConnectionManager<PgConnection>>;

pub fn validate_condition_with_mandatory_dimensions(
context: &Condition,
mandatory_dimensions: &Map<String, Value>,
tenant: &Tenant,
) -> superposition::Result<()> {
let context_map = extract_dimensions(context)?;
let dimensions_list: Vec<String> = context_map.keys().cloned().collect();
let mandatory_dimensions =
Tenant::get_mandatory_dimensions(&tenant, mandatory_dimensions);
let all_mandatory_present = mandatory_dimensions
.iter()
.all(|dimension| dimensions_list.contains(dimension));
if !all_mandatory_present {
return Err(validation_error!(
"The context should contain all the mandatory dimensions : {:?}.",
mandatory_dimensions,
));
}
Ok(())
}

pub fn validate_condition_with_functions(
conn: &mut DBConnection,
context: &Condition,
Expand Down
Loading

0 comments on commit 8d95a30

Please sign in to comment.