diff --git a/Cargo.lock b/Cargo.lock index c5d4e6b37..090235c8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -932,6 +932,7 @@ dependencies = [ "leptos_router", "log", "rand", + "regex", "reqwest", "rs-snowflake", "rusoto_core", @@ -1388,6 +1389,7 @@ name = "experimentation_platform" version = "0.15.1" dependencies = [ "actix", + "actix-http", "actix-web", "anyhow", "chrono", @@ -3458,6 +3460,7 @@ dependencies = [ "jsonschema", "log", "once_cell", + "regex", "reqwest", "rs-snowflake", "rusoto_core", diff --git a/Cargo.toml b/Cargo.toml index 1d56ad76b..724051022 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,4 @@ leptos = { version = "0.5.2" } leptos_meta = { version = "0.5.2" } leptos_router = { version = "0.5.2" } thiserror = { version = "1.0.57" } +regex = { version = "1.9.1" } \ No newline at end of file diff --git a/crates/caclang/Cargo.toml b/crates/caclang/Cargo.toml index 8a5ffb6e3..c6e58fca6 100644 --- a/crates/caclang/Cargo.toml +++ b/crates/caclang/Cargo.toml @@ -17,7 +17,7 @@ path = "src/bin.rs" toml = "0.8.8" clap = { version = "4.3.0", features = ["derive"] } inquire = "0.6.2" -regex = "1.9.1" +regex = { workspace = true } serde = "1.0.163" blake3 = { workspace = true } anyhow = { workspace = true } diff --git a/crates/context_aware_config/Cargo.toml b/crates/context_aware_config/Cargo.toml index 5f4d9dedd..32b9ab8b3 100644 --- a/crates/context_aware_config/Cargo.toml +++ b/crates/context_aware_config/Cargo.toml @@ -60,3 +60,4 @@ leptos_router = { workspace = true } actix-files = { version = "0.6" } anyhow = { workspace = true } superposition_types = { path = "../superposition_types" } +regex = { workspace = true } diff --git a/crates/context_aware_config/migrations/2024-04-22-122806_config_verions/up.sql b/crates/context_aware_config/migrations/2024-04-22-122806_config_verions/up.sql index 287f4b79b..0800b5b82 100644 --- a/crates/context_aware_config/migrations/2024-04-22-122806_config_verions/up.sql +++ b/crates/context_aware_config/migrations/2024-04-22-122806_config_verions/up.sql @@ -8,4 +8,7 @@ CREATE TABLE public.config_versions ( tags varchar(100)[] check (array_position(tags, null) is null), created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL ); + +CREATE INDEX IF NOT EXISTS config_verions_tags_index ON public.config_versions USING gin(tags); +CREATE INDEX IF NOT EXISTS config_versions_id_index ON public.config_versions(id); -- \ No newline at end of file diff --git a/crates/context_aware_config/src/api/config/handlers.rs b/crates/context_aware_config/src/api/config/handlers.rs index 4edec93ec..c19454da5 100644 --- a/crates/context_aware_config/src/api/config/handlers.rs +++ b/crates/context_aware_config/src/api/config/handlers.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::{collections::HashMap, str::FromStr}; use super::helpers::{ - filter_config_by_dimensions, filter_config_by_prefix, filter_context + filter_config_by_dimensions, filter_config_by_prefix, filter_context, }; use super::types::{Config, Context}; use crate::api::context::{ @@ -10,17 +10,11 @@ use crate::api::context::{ }; use crate::api::dimension::get_all_dimension_schema_map; use crate::{ - db::schema::{ - contexts::dsl as ctxt, default_configs::dsl as def_conf, - event_log::dsl as event_log, config_versions::dsl as config_versions - }, - helpers::{json_to_sorted_string, generate_cac}, + db::schema::{config_versions::dsl as config_versions, event_log::dsl as event_log}, + helpers::generate_cac, }; use actix_http::header::{HeaderName, HeaderValue}; -use actix_web::web; -use actix_web::{ - error::ErrorBadRequest, get, put, web::Query, HttpRequest, HttpResponse, Scope, -}; +use actix_web::{get, put, web, web::Query, HttpRequest, HttpResponse, Scope}; use cac_client::{eval_cac, eval_cac_with_reasoning, MergeStrategy}; use chrono::{DateTime, NaiveDateTime, TimeZone, Timelike, Utc}; use diesel::{ @@ -138,23 +132,24 @@ pub fn generate_config_from_version( version: Option, conn: &mut PooledConnection>, ) -> superposition::Result { - let config = match version { - None => config_versions::config_versions + let config = if let Some(val) = version { + config_versions::config_versions .select(config_versions::config) - .order_by(config_versions::created_at.desc()) - .first::(conn) + .filter(config_versions::id.eq(val)) + .get_result::(conn) .map_err(|err| { log::error!("failed to fetch config with error: {}", err); db_error!(err) - }), - Some(val) => config_versions::config_versions + }) + } else { + config_versions::config_versions .select(config_versions::config) - .filter(config_versions::id.eq(val)) - .get_result::(conn) + .order_by(config_versions::created_at.desc()) + .first::(conn) .map_err(|err| { log::error!("failed to fetch config with error: {}", err); db_error!(err) - }), + }) }?; serde_json::from_value::(config).map_err(|err| { @@ -169,17 +164,15 @@ fn generate_subsets(map: &Map) -> Vec> { let all_subsets_keys = generate_subsets_keys(keys); for subset_keys in &all_subsets_keys { - if subset_keys.len() >= 0 { - let mut subset_map = Map::new(); + let mut subset_map = Map::new(); - for key in subset_keys { - if let Some(value) = map.get(key) { - subset_map.insert(key.to_string(), value.clone()); - } + for key in subset_keys { + if let Some(value) = map.get(key) { + subset_map.insert(key.to_string(), value.clone()); } - - subsets.push(subset_map); } + + subsets.push(subset_map); } subsets @@ -406,12 +399,12 @@ async fn reduce_config_key( ) => { if *to_be_deleted { if is_approve { - let _ = delete_context_api(cid.clone(), user.clone(), conn).await; + let _ = delete_context_api(cid.clone(), user.clone(), conn); } og_contexts.retain(|x| x.id != *cid); } else { if is_approve { - let _ = delete_context_api(cid.clone(), user.clone(), conn).await; + let _ = delete_context_api(cid.clone(), user.clone(), conn); let put_req = construct_new_payload(request_payload); let _ = put(put_req, conn, false, &user); } @@ -464,9 +457,9 @@ async fn reduce_config( .unwrap_or(false); let dimensions_schema_map = get_all_dimension_schema_map(&mut conn)?; - let mut config = generate_cac(&mut conn).await?; + let mut config = generate_cac(&mut conn)?; let default_config = (config.default_configs).clone(); - for (key, val) in default_config { + for (key, _val) in default_config { let contexts = config.contexts; let overrides = config.overrides; let default_config = config.default_configs; @@ -482,7 +475,7 @@ async fn reduce_config( ) .await?; if is_approve { - config = generate_cac(&mut conn).await?; + config = generate_cac(&mut conn)?; } } diff --git a/crates/context_aware_config/src/api/context/handlers.rs b/crates/context_aware_config/src/api/context/handlers.rs index 240296eaf..ba5812c31 100644 --- a/crates/context_aware_config/src/api/context/handlers.rs +++ b/crates/context_aware_config/src/api/context/handlers.rs @@ -8,9 +8,8 @@ use crate::helpers::{ use crate::{ api::{ context::types::{ - BulkOperationReq, ContextAction, ContextBulkResponse, DeleteReq, - DimensionCondition, MoveReq, PaginationParams, PriorityRecomputeResponse, - PutReq, PutResp, + ContextAction, ContextBulkResponse, DimensionCondition, MoveReq, + PaginationParams, PriorityRecomputeResponse, PutReq, PutResp, }, dimension::get_all_dimension_schema_map, }, @@ -23,7 +22,7 @@ use crate::{ }, }; use actix_web::web::Data; -use service_utils::service::types::AppState; +use service_utils::service::types::{AppState, CustomHeaders}; use actix_web::{ delete, get, put, @@ -40,7 +39,7 @@ use diesel::{ }; use jsonschema::{Draft, JSONSchema, ValidationError}; use serde_json::{from_value, json, Map, Value}; -use service_utils::helpers::validation_err_to_str; +use service_utils::helpers::{validate_config_tags, validation_err_to_str}; use service_utils::service::types::DbConnection; use service_utils::{db_error, not_found, unexpected_error, validation_error}; use std::collections::HashMap; @@ -309,11 +308,12 @@ pub fn put( #[put("")] async fn put_handler( state: Data, + custom_headers: CustomHeaders, req: Json, mut db_conn: DbConnection, user: User, ) -> superposition::Result> { - let tags = req.version_tags.to_owned(); + let tags = validate_config_tags(custom_headers.config_tags)?; db_conn.transaction::<_, superposition::AppError, _>(|transaction_conn| { let put_response = put(req, transaction_conn, true, &user) .map(|resp| Json(resp)) @@ -328,12 +328,13 @@ async fn put_handler( fn override_helper( state: &Data, + config_tags: Option, req: Json, conn: &mut PooledConnection>, user: &User, ) -> superposition::Result> { use contexts::dsl::contexts; - let tags = req.version_tags.to_owned(); + let tags = validate_config_tags(config_tags)?; let new_ctx = create_ctx_from_put_req(req, conn, user)?; (*conn).transaction::<_, superposition::AppError, _>(|transaction_conn| { let insert = diesel::insert_into(contexts) @@ -356,11 +357,12 @@ fn override_helper( #[put("/overrides")] async fn update_override_handler( state: Data, + custom_headers: CustomHeaders, req: Json, mut db_conn: DbConnection, user: User, ) -> superposition::Result> { - override_helper(&state, req, &mut db_conn, &user).map_err( + override_helper(&state, custom_headers.config_tags, req, &mut db_conn, &user).map_err( |err: superposition::AppError| { log::info!("context put failed with error: {:?}", err); err @@ -452,11 +454,12 @@ fn r#move( async fn move_handler( state: Data, path: Path, + custom_headers: CustomHeaders, req: Json, mut db_conn: DbConnection, user: User, ) -> superposition::Result> { - let tags = req.version_tags.to_owned(); + let tags = validate_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(|resp| Json(resp)) @@ -530,39 +533,46 @@ pub fn delete_context_api( Ok(_) => { log::info!("{ctx_id} context deleted by {}", user.get_email()); Ok(HttpResponse::NoContent().finish()) - }, + } Err(e) => { log::error!("context delete query failed with error: {e}"); Err(unexpected_error!("Something went wrong.")) - } + } } } #[delete("/{ctx_id}")] async fn delete_context( + state: Data, path: Path, + custom_headers: CustomHeaders, user: User, mut db_conn: DbConnection, ) -> superposition::Result { let ctx_id = path.into_inner(); - delete_context_api(ctx_id, user, &mut db_conn) + let tags = validate_config_tags(custom_headers.config_tags)?; + db_conn.transaction::<_, superposition::AppError, _>(|transaction_conn| { + let delete_resp = delete_context_api(ctx_id, user, transaction_conn)?; + add_config_version(&state, tags, transaction_conn)?; + Ok(delete_resp) + }) } #[put("/bulk-operations")] async fn bulk_operations( state: Data, - req: Json, + custom_headers: CustomHeaders, + reqs: Json>, db_conn: DbConnection, user: User, ) -> superposition::Result>> { use contexts::dsl::contexts; let DbConnection(mut conn) = db_conn; - let tags = req.version_tags.to_owned(); - let context_actions = req.into_inner().context_actions.clone(); + let tags = validate_config_tags(custom_headers.config_tags)?; let mut response = Vec::::new(); conn.transaction::<_, superposition::AppError, _>(|transaction_conn| { - for action in context_actions.into_iter() { + 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) @@ -622,7 +632,7 @@ async fn bulk_operations( #[put("/priority/recompute")] async fn priority_recompute( state: Data, - req: Json, + custom_headers: CustomHeaders, db_conn: DbConnection, _user: User, ) -> superposition::Result>> { @@ -636,7 +646,7 @@ async fn priority_recompute( let dimension_schema_map = get_all_dimension_schema_map(&mut conn)?; let mut response: Vec = vec![]; - let tags = req.version_tags.to_owned(); + let tags = validate_config_tags(custom_headers.config_tags)?; let update_contexts = result .clone() diff --git a/crates/context_aware_config/src/api/context/types.rs b/crates/context_aware_config/src/api/context/types.rs index 00cfe87b3..6db85ac87 100644 --- a/crates/context_aware_config/src/api/context/types.rs +++ b/crates/context_aware_config/src/api/context/types.rs @@ -5,18 +5,11 @@ use serde_json::{Map, Value}; pub struct PutReq { pub context: Map, pub r#override: Map, - pub version_tags: Option>, } #[derive(Deserialize, Clone)] pub struct MoveReq { pub context: Map, - pub version_tags: Option>, -} - -#[derive(Deserialize, Clone)] -pub struct DeleteReq { - pub version_tags: Option>, } #[derive(Deserialize, Clone)] @@ -37,12 +30,6 @@ pub struct PaginationParams { pub size: Option, } -#[derive(Deserialize, Clone)] -pub struct BulkOperationReq { - pub context_actions: Vec, - pub version_tags: Option>, -} - #[derive(serde::Deserialize, Clone)] pub enum ContextAction { PUT(PutReq), diff --git a/crates/context_aware_config/src/api/default_config/handlers.rs b/crates/context_aware_config/src/api/default_config/handlers.rs index 7532b3222..9d7d1c8b6 100644 --- a/crates/context_aware_config/src/api/default_config/handlers.rs +++ b/crates/context_aware_config/src/api/default_config/handlers.rs @@ -1,10 +1,10 @@ extern crate base64; -use super::types::{CreateReq, DeleteReq}; +use super::types::CreateReq; use service_utils::{ bad_argument, db_error, - helpers::validation_err_to_str, + helpers::{validate_config_tags, validation_err_to_str}, not_found, result as superposition, - service::types::{AppState, DbConnection}, + service::types::{AppState, CustomHeaders, DbConnection}, unexpected_error, validation_error, }; @@ -42,6 +42,7 @@ pub fn endpoints() -> Scope { async fn create( state: Data, key: web::Path, + custom_headers: CustomHeaders, request: web::Json, db_conn: DbConnection, user: User, @@ -49,6 +50,7 @@ async fn create( let DbConnection(mut conn) = db_conn; let req = request.into_inner(); let key = key.into_inner(); + let tags = validate_config_tags(custom_headers.config_tags)?; if key.ends_with(".") { log::error!("configuration key {key} cannot end with a '.' character."); @@ -162,7 +164,7 @@ async fn create( .do_update() .set(&default_config) .execute(transaction_conn); - add_config_version(&state, req.version_tags, transaction_conn)?; + add_config_version(&state, tags, transaction_conn)?; let ok_resp = match upsert { Ok(_) => Ok(HttpResponse::Ok().json(json!({ "message": "DefaultConfig created/updated successfully." @@ -227,12 +229,12 @@ pub fn get_key_usage_context_ids( async fn delete( state: Data, path: Path, - req: Json, + custom_headers: CustomHeaders, db_conn: DbConnection, user: User, ) -> superposition::Result { let DbConnection(mut conn) = db_conn; - let tags = req.version_tags.to_owned(); + let tags = validate_config_tags(custom_headers.config_tags)?; let key = path.into_inner(); fetch_default_key(&key, &mut conn)?; diff --git a/crates/context_aware_config/src/api/default_config/types.rs b/crates/context_aware_config/src/api/default_config/types.rs index 73f3d1d3b..0e4b1b615 100644 --- a/crates/context_aware_config/src/api/default_config/types.rs +++ b/crates/context_aware_config/src/api/default_config/types.rs @@ -8,7 +8,6 @@ pub struct CreateReq { pub schema: Option>, #[serde(default, deserialize_with = "deserialize_option")] pub function_name: Option, - pub version_tags: Option>, } fn deserialize_option<'de, D>(deserializer: D) -> Result, D::Error> @@ -18,8 +17,3 @@ where let value: Value = Deserialize::deserialize(deserializer)?; Ok(Some(value)) } - -#[derive(Deserialize, Clone)] -pub struct DeleteReq { - pub version_tags: Option>, -} diff --git a/crates/experimentation_platform/Cargo.toml b/crates/experimentation_platform/Cargo.toml index 36f92bb7c..55965bd7b 100644 --- a/crates/experimentation_platform/Cargo.toml +++ b/crates/experimentation_platform/Cargo.toml @@ -11,6 +11,7 @@ dotenv = { workspace = true } # Https server framework actix = { workspace = true } actix-web = { workspace = true } +actix-http = "3.3.1" # To help generate snowflake ids rs-snowflake = { workspace = true } # To help with generating uuids diff --git a/crates/experimentation_platform/src/api/experiments/handlers.rs b/crates/experimentation_platform/src/api/experiments/handlers.rs index 620c088da..afdb2c41b 100644 --- a/crates/experimentation_platform/src/api/experiments/handlers.rs +++ b/crates/experimentation_platform/src/api/experiments/handlers.rs @@ -1,5 +1,6 @@ use std::collections::{HashMap, HashSet}; +use actix_http::header::{HeaderMap, HeaderName, HeaderValue}; use actix_web::{ get, patch, post, put, web::{self, Data, Json, Query}, @@ -14,7 +15,7 @@ use diesel::{ use service_utils::{ bad_argument, - helpers::{construct_request_headers, request, generate_snowflake_id}, + helpers::{construct_request_headers, generate_snowflake_id, request}, response_error, result::{self as superposition, AppError}, unexpected_error, @@ -23,7 +24,7 @@ use service_utils::{ use superposition_types::{SuperpositionUser, User}; use reqwest::{Method, Response, StatusCode}; -use service_utils::service::types::{AppState, DbConnection, Tenant}; +use service_utils::service::types::{AppState, CustomHeaders, DbConnection, Tenant}; use super::{ helpers::{ @@ -32,10 +33,10 @@ use super::{ validate_override_keys, }, types::{ - AuditQueryFilters, ConcludeExperimentRequest, ContextAction, ContextBulkReq, - ContextBulkResponse, ContextMoveReq, ContextPutReq, ExperimentCreateRequest, - ExperimentCreateResponse, ExperimentResponse, ExperimentsResponse, ListFilters, - OverrideKeysUpdateRequest, RampRequest, Variant, + AuditQueryFilters, ConcludeExperimentRequest, ContextAction, ContextBulkResponse, + ContextMoveReq, ContextPutReq, ExperimentCreateRequest, ExperimentCreateResponse, + ExperimentResponse, ExperimentsResponse, ListFilters, OverrideKeysUpdateRequest, + RampRequest, Variant, }, }; @@ -58,6 +59,25 @@ pub fn endpoints(scope: Scope) -> Scope { .service(update_overrides) } +fn construct_header_map( + tenant: &str, + config_tags: Option, +) -> superposition::Result { + let mut headers = HeaderMap::new(); + let tenant_val = HeaderValue::from_str(tenant).map_err(|err| { + log::error!("failed to set header: {}", err); + unexpected_error!("Something went wrong") + })?; + headers.insert(HeaderName::from_static("x-tenant"), tenant_val); + if let Some(val) = config_tags { + let tag_val = HeaderValue::from_str(val.as_str()).map_err(|err| { + log::error!("failed to set header: {}", err); + unexpected_error!("Something went wrong") + })?; + headers.insert(HeaderName::from_static("x-config-tags"), tag_val); + } + Ok(headers) +} async fn parse_error_response( response: reqwest::Response, ) -> superposition::Result<(StatusCode, superposition::ErrorResponse)> { @@ -105,6 +125,7 @@ async fn process_cac_http_response( #[post("")] async fn create( state: Data, + custom_headers: CustomHeaders, req: web::Json, db_conn: DbConnection, tenant: Tenant, @@ -194,20 +215,17 @@ async fn create( // creating variants' context in CAC let http_client = reqwest::Client::new(); let url = state.cac_host.clone() + "/context/bulk-operations"; - let request_body = ContextBulkReq { - context_actions: cac_operations, - version_tags: req.tags.to_owned(), - }; + let headers_map = construct_header_map(tenant.as_str(), custom_headers.config_tags)?; // Step 1: Perform the HTTP request and handle errors let response = http_client .put(&url) - .header("x-tenant", tenant.as_str()) + .headers(headers_map.into()) .header( "Authorization", format!("{} {}", user.get_auth_type(), user.get_auth_token()), ) - .json(&request_body) + .json(&cac_operations) .send() .await; @@ -259,6 +277,7 @@ async fn create( async fn conclude_handler( state: Data, path: web::Path, + custom_headers: CustomHeaders, req: web::Json, db_conn: DbConnection, tenant: Tenant, @@ -268,6 +287,7 @@ async fn conclude_handler( let response = conclude( state, path.into_inner(), + custom_headers.config_tags, req.into_inner(), conn, tenant, @@ -280,6 +300,7 @@ async fn conclude_handler( pub async fn conclude( state: Data, experiment_id: i64, + config_tags: Option, req: ConcludeExperimentRequest, mut conn: PooledConnection>, tenant: Tenant, @@ -371,18 +392,16 @@ pub async fn conclude( // calling CAC bulk api with operations as payload let http_client = reqwest::Client::new(); let url = state.cac_host.clone() + "/context/bulk-operations"; - let request_body = ContextBulkReq { - context_actions: operations, - version_tags: req.version_tags.to_owned(), - }; + let headers_map = construct_header_map(tenant.as_str(), config_tags)?; + let response = http_client .put(&url) - .header("x-tenant", tenant.as_str()) + .headers(headers_map.into()) .header( "Authorization", format!("{} {}", user.get_auth_type(), user.get_auth_token()), ) - .json(&request_body) + .json(&operations) .send() .await; @@ -548,6 +567,7 @@ async fn ramp( async fn update_overrides( params: web::Path, state: Data, + custom_headers: CustomHeaders, db_conn: DbConnection, req: web::Json, tenant: Tenant, @@ -691,19 +711,16 @@ async fn update_overrides( let http_client = reqwest::Client::new(); let url = state.cac_host.clone() + "/context/bulk-operations"; - let request_body = ContextBulkReq { - context_actions: cac_operations, - version_tags: payload.version_tags, - }; + let headers_map = construct_header_map(tenant.as_str(), custom_headers.config_tags)?; let response = http_client .put(&url) - .header("x-tenant", tenant.as_str()) + .headers(headers_map.into()) .header( "Authorization", format!("{} {}", user.get_auth_type(), user.get_auth_token()), ) - .json(&request_body) + .json(&cac_operations) .send() .await; diff --git a/crates/experimentation_platform/src/api/experiments/types.rs b/crates/experimentation_platform/src/api/experiments/types.rs index 23d339a02..3b1da1c29 100644 --- a/crates/experimentation_platform/src/api/experiments/types.rs +++ b/crates/experimentation_platform/src/api/experiments/types.rs @@ -97,7 +97,6 @@ pub struct ExperimentsResponse { #[derive(Deserialize, Debug)] pub struct ConcludeExperimentRequest { pub chosen_variant: String, - pub version_tags: Option>, } /********** Context Bulk API Type *************/ @@ -122,12 +121,6 @@ pub struct ContextPutResp { pub priority: i32, } -#[derive(Deserialize, Serialize, Clone)] -pub struct ContextBulkReq { - pub context_actions: Vec, - pub version_tags: Option>, -} - #[derive(Serialize, Deserialize, Debug)] pub enum ContextBulkResponse { PUT(ContextPutResp), @@ -169,7 +162,6 @@ pub struct VariantUpdateRequest { #[derive(Deserialize, Debug)] pub struct OverrideKeysUpdateRequest { pub variants: Vec, - pub version_tags: Option>, } #[derive(Deserialize, Serialize, Clone)] diff --git a/crates/service_utils/Cargo.toml b/crates/service_utils/Cargo.toml index eb4bd6c94..1dc51c6d3 100644 --- a/crates/service_utils/Cargo.toml +++ b/crates/service_utils/Cargo.toml @@ -34,3 +34,4 @@ derive_more = { workspace = true } reqwest = { workspace = true } thiserror = { workspace = true } once_cell = { workspace = true } +regex = { workspace = true } diff --git a/crates/service_utils/src/helpers.rs b/crates/service_utils/src/helpers.rs index 4e412836e..7d11ba2a3 100644 --- a/crates/service_utils/src/helpers.rs +++ b/crates/service_utils/src/helpers.rs @@ -1,18 +1,20 @@ +use super::result; +use crate::service::types::AppState; use actix_web::{error::ErrorInternalServerError, web::Data, Error}; use anyhow::anyhow; use jsonschema::{error::ValidationErrorKind, ValidationError}; use log::info; +use regex::Regex; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use serde::de::{self, IntoDeserializer}; +use serde_json::{Map, Value}; use std::{ env::VarError, fmt::{self, Display}, str::FromStr, }; -use super::result; -use crate::service::types::AppState; -use serde_json::{Map, Value}; +const CONFIG_TAG_REGEX: &str = "^[a-z0-9].*$"; //WARN Do NOT use this fxn inside api requests, instead add the required //env to AppState and get value from there. As this panics, it should @@ -381,3 +383,30 @@ pub fn generate_snowflake_id(state: &Data) -> result::Result { drop(snowflake_generator); Ok(id) } + +pub fn validate_config_tags( + config_tags: Option, +) -> result::Result>> { + let regex = Regex::new(CONFIG_TAG_REGEX).map_err(|err| { + log::error!("snowflake_id generation failed {}", err); + result::AppError::UnexpectedError(anyhow!("Something went wrong")) + })?; + match config_tags { + None => Ok(None), + Some(val) => { + let tags = val + .split(",") + .map(|s| { + if !regex.is_match(s) { + return Err(result::AppError::BadArgument( + "Invalid config_tags value".to_string(), + )); + } else { + Ok(s.to_owned()) + } + }) + .collect::>>()?; + Ok(Some(tags)) + } + } +} diff --git a/crates/service_utils/src/service/types.rs b/crates/service_utils/src/service/types.rs index 08cf4fa67..f2ec0acec 100644 --- a/crates/service_utils/src/service/types.rs +++ b/crates/service_utils/src/service/types.rs @@ -209,3 +209,24 @@ impl FromRequest for DbConnection { ready(result) } } + +pub struct CustomHeaders { + pub config_tags: Option, +} +impl FromRequest for CustomHeaders { + type Error = Error; + type Future = Ready>; + + fn from_request( + req: &actix_web::HttpRequest, + _: &mut actix_web::dev::Payload, + ) -> Self::Future { + let header_val = req.headers(); + let val = CustomHeaders { + config_tags: header_val.get("x-config-tags").and_then(|header_val| { + header_val.to_str().map_or(None, |v| Some(v.to_string())) + }), + }; + ready(Ok(val)) + } +} diff --git a/postman/cac/Context/Delete Context/request.json b/postman/cac/Context/Delete Context/request.json index ae60143ed..ed19fe4c4 100644 --- a/postman/cac/Context/Delete Context/request.json +++ b/postman/cac/Context/Delete Context/request.json @@ -12,15 +12,6 @@ "type": "default" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": {} - }, "url": { "raw": "{{host}}/context/{{context_id}}", "host": [ diff --git a/postman/cac/Context/Recompute Priority Context/request.json b/postman/cac/Context/Recompute Priority Context/request.json index 839b72746..c94f7bf41 100644 --- a/postman/cac/Context/Recompute Priority Context/request.json +++ b/postman/cac/Context/Recompute Priority Context/request.json @@ -17,15 +17,6 @@ "type": "default" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": {} - }, "url": { "raw": "{{host}}/context/priority/recompute", "host": [ diff --git a/postman/cac/Default Config/Delete default-config key/request.json b/postman/cac/Default Config/Delete default-config key/request.json index 82f36a744..97e1d804f 100644 --- a/postman/cac/Default Config/Delete default-config key/request.json +++ b/postman/cac/Default Config/Delete default-config key/request.json @@ -17,15 +17,6 @@ "type": "default" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": {} - }, "url": { "raw": "{{host}}/default-config/key2", "host": [