inputing data for the client for the validation
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
|
||||||
|
CREATE TABLE table_validation_rules (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
table_def_id BIGINT NOT NULL
|
||||||
|
REFERENCES table_definitions(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
data_key TEXT NOT NULL,
|
||||||
|
config JSONB NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE (table_def_id, data_key)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_table_validation_rules_table
|
||||||
|
ON table_validation_rules (table_def_id);
|
||||||
@@ -10,6 +10,7 @@ pub mod table_definition;
|
|||||||
pub mod tables_data;
|
pub mod tables_data;
|
||||||
pub mod table_script;
|
pub mod table_script;
|
||||||
pub mod steel;
|
pub mod steel;
|
||||||
|
pub mod table_validation;
|
||||||
|
|
||||||
// Re-export run_server from the inner server module:
|
// Re-export run_server from the inner server module:
|
||||||
pub use server::run_server;
|
pub use server::run_server;
|
||||||
|
|||||||
@@ -351,6 +351,29 @@ async fn execute_table_definition(
|
|||||||
Status::internal(format!("Database error: {}", e))
|
Status::internal(format!("Database error: {}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
for col_def in &columns {
|
||||||
|
// Column string looks like "\"name\" TYPE", split out identifier
|
||||||
|
let col_name = col_def.split_whitespace().next().unwrap_or("");
|
||||||
|
let clean_col = col_name.trim_matches('"');
|
||||||
|
|
||||||
|
// Default empty config — currently only character_limits block, none set.
|
||||||
|
let default_cfg = serde_json::json!({
|
||||||
|
"character_limits": { "min": 0, "max": 0, "warn_at": null, "count_mode": "CHARS" }
|
||||||
|
});
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
r#"INSERT INTO table_validation_rules (table_def_id, data_key, config)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (table_def_id, data_key) DO NOTHING"#,
|
||||||
|
table_def.id,
|
||||||
|
clean_col,
|
||||||
|
default_cfg
|
||||||
|
)
|
||||||
|
.execute(&mut **tx)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("Failed to insert default validation rule for column {}: {}", clean_col, e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
for (linked_id, is_required) in links {
|
for (linked_id, is_required) in links {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO table_definition_links
|
"INSERT INTO table_definition_links
|
||||||
|
|||||||
4
server/src/table_validation/mod.rs
Normal file
4
server/src/table_validation/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// src/table_validation/mod.rs
|
||||||
|
|
||||||
|
pub mod repo;
|
||||||
|
pub mod service;
|
||||||
52
server/src/table_validation/repo.rs
Normal file
52
server/src/table_validation/repo.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// src/table_validation/repo.rs
|
||||||
|
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use anyhow::Result;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
pub struct ValidationRuleRow {
|
||||||
|
pub data_key: String,
|
||||||
|
pub config: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_table_def_id(
|
||||||
|
db: &PgPool,
|
||||||
|
profile_name: &str,
|
||||||
|
table_name: &str,
|
||||||
|
) -> Result<i64> {
|
||||||
|
let rec = sqlx::query!(
|
||||||
|
r#"
|
||||||
|
SELECT td.id
|
||||||
|
FROM table_definitions td
|
||||||
|
JOIN schemas s ON td.schema_id = s.id
|
||||||
|
WHERE s.name = $1 AND td.table_name = $2
|
||||||
|
"#,
|
||||||
|
profile_name,
|
||||||
|
table_name
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(rec.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_validations(
|
||||||
|
db: &PgPool,
|
||||||
|
table_def_id: i64,
|
||||||
|
) -> Result<Vec<ValidationRuleRow>> {
|
||||||
|
let rows = sqlx::query!(
|
||||||
|
r#"
|
||||||
|
SELECT data_key, config
|
||||||
|
FROM table_validation_rules
|
||||||
|
WHERE table_def_id = $1
|
||||||
|
"#,
|
||||||
|
table_def_id
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(rows.into_iter().map(|r| ValidationRuleRow {
|
||||||
|
data_key: r.data_key,
|
||||||
|
config: r.config,
|
||||||
|
}).collect())
|
||||||
|
}
|
||||||
83
server/src/table_validation/service.rs
Normal file
83
server/src/table_validation/service.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// src/table_validation/service.rs
|
||||||
|
|
||||||
|
use tonic::{Request, Response, Status};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use common::proto::komp_ac::table_validation::{
|
||||||
|
table_validation_service_server::TableValidationService,
|
||||||
|
GetTableValidationRequest, TableValidationResponse,
|
||||||
|
FieldValidation, CharacterLimits, CountMode as PbCountMode,
|
||||||
|
};
|
||||||
|
use crate::table_validation::repo;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct FieldConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
character_limits: Option<CharacterLimitsCfg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CharacterLimitsCfg {
|
||||||
|
#[serde(default)] min: Option<u32>,
|
||||||
|
#[serde(default)] max: Option<u32>,
|
||||||
|
#[serde(default)] warn_at: Option<u32>,
|
||||||
|
#[serde(default)] count_mode: Option<String>, // "CHARS"/"BYTES"/"DISPLAY_WIDTH"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TableValidationSvc {
|
||||||
|
pub db: PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl TableValidationService for TableValidationSvc {
|
||||||
|
async fn get_table_validation(
|
||||||
|
&self,
|
||||||
|
req: Request<GetTableValidationRequest>,
|
||||||
|
) -> Result<Response<TableValidationResponse>, Status> {
|
||||||
|
let req = req.into_inner();
|
||||||
|
|
||||||
|
// 1. Get table_def_id
|
||||||
|
let table_def_id = repo::get_table_def_id(&self.db, &req.profile_name, &req.table_name)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Status::not_found("Table definition not found"))?;
|
||||||
|
|
||||||
|
// 2. Get validations
|
||||||
|
let rules = repo::get_validations(&self.db, table_def_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("Failed to fetch rules: {}", e)))?;
|
||||||
|
|
||||||
|
// 3. Map JSON -> proto
|
||||||
|
let mut fields_out = Vec::new();
|
||||||
|
for r in rules {
|
||||||
|
let cfg: FieldConfig = match serde_json::from_value(r.config) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Invalid config for {}: {}", r.data_key, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cl) = cfg.character_limits {
|
||||||
|
let pb_mode = match cl.count_mode.as_deref() {
|
||||||
|
Some("BYTES") => PbCountMode::Bytes as i32,
|
||||||
|
Some("DISPLAY_WIDTH") => PbCountMode::DisplayWidth as i32,
|
||||||
|
_ => PbCountMode::Chars as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
let limits = CharacterLimits {
|
||||||
|
min: cl.min.unwrap_or(0),
|
||||||
|
max: cl.max.unwrap_or(0),
|
||||||
|
warn_at: cl.warn_at,
|
||||||
|
count_mode: pb_mode,
|
||||||
|
};
|
||||||
|
|
||||||
|
fields_out.push(FieldValidation {
|
||||||
|
data_key: r.data_key,
|
||||||
|
limits: Some(limits),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Response::new(TableValidationResponse { fields: fields_out }))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user