Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cat-gateway): config API #981

Merged
merged 47 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
30655de
feat: add config endpoint
bkioshn Oct 9, 2024
241481b
feat: add jsonschema lib
bkioshn Oct 9, 2024
9e15dff
fix: config table
bkioshn Oct 9, 2024
c5aa5ab
fix: sql format
bkioshn Oct 9, 2024
0ec7541
Merge branch 'main' into feat/config-api
bkioshn Oct 10, 2024
e3fd2ce
fix: comment to sql file
bkioshn Oct 10, 2024
3c78d55
feat: add upsert sql
bkioshn Oct 10, 2024
42b35b4
fix: update endpoint
bkioshn Oct 10, 2024
d9748e7
fix: frontend key implementation
bkioshn Oct 10, 2024
8f57b94
fix: config query
bkioshn Oct 10, 2024
bf7f8e0
fix: sql lint
bkioshn Oct 10, 2024
e1ae169
Merge branch 'main' into feat/config-api
bkioshn Oct 10, 2024
798b73f
fix: refactor
bkioshn Oct 11, 2024
0613100
Merge branch 'main' into feat/config-api
bkioshn Oct 11, 2024
939df7e
Merge branch 'main' into feat/config-api
stevenj Oct 15, 2024
c522ce4
fix: config endpoint
bkioshn Oct 15, 2024
eef973f
fix: lazy lock validator and rename function
bkioshn Oct 15, 2024
816447e
fix: frontend default and json schema
bkioshn Oct 15, 2024
087cd96
chore:add license MIT
bkioshn Oct 15, 2024
96e2cb5
fix: remove migration v2 to v9
bkioshn Oct 15, 2024
f0d7766
Merge branch 'main' into feat/config-api
bkioshn Oct 15, 2024
54258ef
fix: format
bkioshn Oct 15, 2024
7114d39
chore: change license to MIT-0
bkioshn Oct 15, 2024
27e5310
chore: remove license
bkioshn Oct 15, 2024
cfa7e55
fix: add mit-0 license to deny.toml and test it
bkioshn Oct 16, 2024
97d69ad
fix: update cat-gateway code gen
bkioshn Oct 16, 2024
5cc62ef
fix: update cat-gateway rust-ci version
bkioshn Oct 16, 2024
fad3cfc
fix: revert change
bkioshn Oct 16, 2024
5fde921
fix: add new endpoint and fix validate json
bkioshn Oct 16, 2024
4e1a2b3
fix: cat-gateway api code gen
bkioshn Oct 16, 2024
e4afeb1
Update catalyst-gateway/bin/src/db/event/config/jsonschema/frontend.json
stevenj Oct 16, 2024
65c587a
Update catalyst-gateway/bin/src/db/event/config/jsonschema/frontend.json
stevenj Oct 16, 2024
c6db422
Merge branch 'main' into feat/config-api
bkioshn Oct 17, 2024
ef78702
fix: frontend default and json schema
bkioshn Oct 17, 2024
80db594
fix: error handling
bkioshn Oct 17, 2024
577aee6
fix: cat-gateway api code gen
bkioshn Oct 17, 2024
a0062c2
fix: openapi lint
bkioshn Oct 17, 2024
ffa2dec
fix: frontend json schema
bkioshn Oct 17, 2024
1d6fa85
fix: error handling
bkioshn Oct 17, 2024
1a6ac40
fix: cat-gateway api code gen
bkioshn Oct 17, 2024
ff1399a
Merge branch 'main' into feat/config-api
bkioshn Oct 17, 2024
e0813d4
fix: remove id
bkioshn Oct 17, 2024
55965fb
fix: error log
bkioshn Oct 18, 2024
20290d9
Merge branch 'main' into feat/config-api
bkioshn Oct 18, 2024
5ea12b9
fix: bump ci to v3.2.18
bkioshn Oct 18, 2024
4dbde99
Merge branch 'main' into feat/config-api
stevenj Oct 18, 2024
a3ba7be
Merge branch 'main' into feat/config-api
stevenj Oct 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Joaquín
jorm
jormungandr
Jörmungandr
jsonschema
junitreport
junitxml
Keyhash
Expand Down
2 changes: 1 addition & 1 deletion catalyst-gateway/Earthfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
VERSION 0.8

IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.16 AS rust-ci
IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.17 AS rust-ci

#cspell: words rustfmt toolsets USERARCH stdcfgs

Expand Down
1 change: 1 addition & 0 deletions catalyst-gateway/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ base64 = "0.22.1"
dashmap = "6.1.0"
x509-cert = "0.2.5"
der-parser = "9.0.0"
jsonschema = "0.22.3"

[dev-dependencies]
proptest = "1.5.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"sentry_environment": "dev"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$id": "https://www.stephenlewis.me/sentry-schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Frontend JSON schema",
"type": "object",
"properties": {
"sentry_dsn": {
"$ref": "#/definitions/saneUrl",
stevenj marked this conversation as resolved.
Show resolved Hide resolved
"description": "The Data Source Name (DSN) for Sentry."
},
"sentry_release": {
"type": "string",
"description": "A version of the code deployed to an environment"
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
},
"sentry_environment": {
"type": "string",
"description": "The environment in which the application is running, e.g., 'dev', 'qa'."
}
},
"additionalProperties": true,
"definitions": {
"saneUrl": {
stevenj marked this conversation as resolved.
Show resolved Hide resolved
"type": "string",
"format": "uri",
"pattern": "^https?://"
}
}
}
126 changes: 126 additions & 0 deletions catalyst-gateway/bin/src/db/event/config/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! Configuration Key

use std::{net::IpAddr, sync::LazyLock};

use jsonschema::{BasicOutput, Validator};
use serde_json::{json, Value};
use tracing::error;

/// Configuration key
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ConfigKey {
/// Frontend general configuration.
Frontend,
/// Frontend configuration for a specific IP address.
FrontendForIp(IpAddr),
}

static FRONTEND_SCHEMA: LazyLock<Value> =
LazyLock::new(|| load_json_lazy(include_str!("jsonschema/frontend.json")));

/// Frontend schema validator.
static FRONTEND_SCHEMA_VALIDATOR: LazyLock<Validator> =
LazyLock::new(|| schema_validator(&FRONTEND_SCHEMA));

/// Frontend default configuration.
static FRONTEND_DEFAULT: LazyLock<Value> =
LazyLock::new(|| load_json_lazy(include_str!("default/frontend.json")));

/// Frontend specific configuration.
static FRONTEND_IP_DEFAULT: LazyLock<Value> =
LazyLock::new(|| load_json_lazy(include_str!("default/frontend_ip.json")));

/// Helper function to create a JSON validator from a JSON schema.
/// If the schema is invalid, a default JSON validator is created.
fn schema_validator(schema: &Value) -> Validator {
jsonschema::validator_for(schema).unwrap_or_else(|e| {
error!("Error creating JSON validator: {}", e);

// Create a default JSON validator as a fallback
// This should not fail since it is hard coded
#[allow(clippy::expect_used)]
Validator::new(&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
}))
.expect("Failed to create default JSON validator")
})
}

/// Helper function to convert a JSON string to a JSON value.
fn load_json_lazy(data: &str) -> Value {
serde_json::from_str(data).unwrap_or_else(|e| {
error!("Error parsing JSON: {}", e);
json!({})
})
}

impl ConfigKey {
/// Convert a `ConfigKey` to its corresponding IDs.
pub(super) fn to_id(&self) -> (String, String, String) {
match self {
ConfigKey::Frontend => ("frontend".to_string(), String::new(), String::new()),
ConfigKey::FrontendForIp(ip) => {
("frontend".to_string(), "ip".to_string(), ip.to_string())
},
}
}

/// Validate the provided value against the JSON schema.
pub(super) fn validate(&self, value: &Value) -> BasicOutput<'static> {
// Retrieve the validator based on ConfigKey
let validator = match self {
ConfigKey::Frontend | ConfigKey::FrontendForIp(_) => &*FRONTEND_SCHEMA_VALIDATOR,
};

// Validate the value against the schema
validator.apply(value).basic()
}

/// Retrieve the default configuration value.
pub(super) fn default(&self) -> Value {
// Retrieve the default value based on the ConfigKey
match self {
ConfigKey::Frontend => FRONTEND_DEFAULT.clone(),
ConfigKey::FrontendForIp(_) => FRONTEND_IP_DEFAULT.clone(),
}
}

/// Retrieve the JSON schema.
pub(crate) fn schema(&self) -> &Value {
match self {
ConfigKey::Frontend | ConfigKey::FrontendForIp(_) => &FRONTEND_SCHEMA,
}
}
}

#[cfg(test)]
mod tests {
use serde_json::json;

use super::*;

#[test]
fn test_valid_validate() {
let value = json!({
"test": "test"
});
let result = ConfigKey::Frontend.validate(&value);
assert!(result.is_valid());
println!("{:?}", serde_json::to_value(result).unwrap());
}

#[test]
fn test_invalid_validate() {
let value = json!([]);
let result = ConfigKey::Frontend.validate(&value);
assert!(!result.is_valid());
println!("{:?}", serde_json::to_value(result).unwrap());
}

#[test]
fn test_default() {
let result = ConfigKey::Frontend.default();
assert!(result.is_object());
}
}
65 changes: 65 additions & 0 deletions catalyst-gateway/bin/src/db/event/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Configuration query

use jsonschema::BasicOutput;
use key::ConfigKey;
use serde_json::Value;
use tracing::error;

use crate::db::event::EventDB;

pub(crate) mod key;

/// Configuration struct
pub(crate) struct Config {}

/// SQL get configuration.
const GET_CONFIG: &str = include_str!("sql/get.sql");
/// SQL update if exist or else insert configuration.
const UPSERT_CONFIG: &str = include_str!("sql/upsert.sql");

impl Config {
/// Retrieve configuration based on the given `ConfigKey`.
///
/// # Returns
///
/// - A JSON value of the configuration, if not found, returns the default value.
/// - Error if the query fails.
pub(crate) async fn get(id: ConfigKey) -> anyhow::Result<Value> {
let (id1, id2, id3) = id.to_id();
let rows = EventDB::query(GET_CONFIG, &[&id1, &id2, &id3]).await?;

if let Some(row) = rows.first() {
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
let value: Value = row.get(0);
match id.validate(&value) {
BasicOutput::Valid(_) => Ok(value),
BasicOutput::Invalid(errors) => {
// This should not happen; expecting the schema to be valid
error!("Validate schema failed: {:?}", errors);
stevenj marked this conversation as resolved.
Show resolved Hide resolved
Err(anyhow::anyhow!("Validate schema failed"))
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
},
}
} else {
// If data is not found, return the default config value
Ok(id.default())
}
}

/// Set the configuration for the given `ConfigKey`.
///
/// # Returns
///
/// - A `BasicOutput` of the validation result, which can be valid or invalid.
/// - Error if the query fails.
pub(crate) async fn set(id: ConfigKey, value: Value) -> anyhow::Result<BasicOutput<'static>> {
let validate = id.validate(&value);
// Validate schema failed, return immediately with JSON schema error
if !validate.is_valid() {
return Ok(validate);
}

let (id1, id2, id3) = id.to_id();
EventDB::query(UPSERT_CONFIG, &[&id1, &id2, &id3, &value]).await?;

Ok(validate)
}
}
7 changes: 7 additions & 0 deletions catalyst-gateway/bin/src/db/event/config/sql/get.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Select the 'value' column from the 'config' table
SELECT value
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
FROM config
WHERE
id1 = $1 -- Match rows where 'id1' equals the first parameter
AND id2 = $2 -- Match rows where 'id2' equals the second parameter
AND id3 = $3; -- Match rows where 'id3' equals the third parameter
9 changes: 9 additions & 0 deletions catalyst-gateway/bin/src/db/event/config/sql/upsert.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Insert a new configuration entry into the 'config' table
INSERT INTO config (id1, id2, id3, value)
VALUES ($1, $2, $3, $4) -- Values to insert for each column

-- Handle conflicts when attempting to insert a row that would violate the unique constraint
ON CONFLICT (id1, id2, id3) -- Specify the unique constraint columns that identify conflicts

-- If a conflict occurs, update the existing row 'value' column with the new value provided
DO UPDATE SET value = excluded.value; -- 'EXCLUDED' refers to the values that were proposed for insertion
1 change: 1 addition & 0 deletions catalyst-gateway/bin/src/db/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use tracing::{debug, debug_span, error, Instrument};

use crate::settings::Settings;

pub(crate) mod config;
pub(crate) mod error;
pub(crate) mod legacy;
pub(crate) mod schema_check;
Expand Down
Loading
Loading