serde of jsonb in grpc

This commit is contained in:
Priec
2025-09-14 10:56:38 +02:00
parent 01c4ff2e14
commit d88c239bf6
10 changed files with 327 additions and 74 deletions

View File

@@ -10,10 +10,10 @@ search = { path = "../search" }
anyhow = { workspace = true }
tantivy = { workspace = true }
prost = "0.13.5"
prost-types = { workspace = true }
chrono = { version = "0.4.40", features = ["serde"] }
dotenvy = "0.15.7"
prost = "0.13.5"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
sqlx = { version = "0.8.5", features = ["chrono", "postgres", "runtime-tokio", "runtime-tokio-native-tls", "rust_decimal", "time", "uuid"] }
@@ -40,6 +40,9 @@ regex = { workspace = true }
thiserror = { workspace = true }
steel-decimal = "1.0.0"
[build-dependencies]
prost-build = "0.14.1"
[lib]
name = "server"
path = "src/lib.rs"

View File

@@ -23,7 +23,7 @@ use common::proto::komp_ac::{
search2::search2_server::Search2Server,
};
use search::{SearcherService, SearcherServer};
use crate::table_validation::post::service::TableValidationSvc;
use crate::table_validation::get::service::TableValidationSvc;
pub async fn run_server(db_pool: sqlx::PgPool) -> Result<(), Box<dyn std::error::Error>> {
// Initialize JWT for authentication

View File

@@ -2,28 +2,14 @@
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,
UpdateFieldValidationRequest, UpdateFieldValidationResponse,
FieldValidation,
};
use crate::table_validation::post::repo; // repo still lives in post
#[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,
}
@@ -46,46 +32,71 @@ impl TableValidationService for TableValidationSvc {
.await
.map_err(|e| Status::internal(format!("Failed to fetch rules: {}", e)))?;
// 3. Map JSON -> proto
// 3. Parse JSON directly into proto types
let mut fields_out = Vec::new();
for r in rules {
let cfg: FieldConfig = match serde_json::from_value(r.config) {
Ok(c) => c,
match serde_json::from_value::<FieldValidation>(r.config) {
Ok(mut fv) => {
// Set the data_key from the database row
fv.data_key = r.data_key;
// Skip if limits are all zero
if let Some(lims) = &fv.limits {
if lims.min == 0 && lims.max == 0 && lims.warn_at.is_none() {
continue;
}
}
fields_out.push(fv);
}
Err(e) => {
tracing::warn!("Invalid config for {}: {}", r.data_key, e);
tracing::warn!("Invalid JSON for {}: {}", r.data_key, e);
continue;
}
};
if let Some(cl) = cfg.character_limits {
let min = cl.min.unwrap_or(0);
let max = cl.max.unwrap_or(0);
// Skip "empty" validations (min=0,max=0,warn_at=None)
if min == 0 && max == 0 && cl.warn_at.is_none() {
continue;
}
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,
max,
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 }))
}
async fn update_field_validation(
&self,
req: Request<UpdateFieldValidationRequest>,
) -> Result<Response<UpdateFieldValidationResponse>, Status> {
let req = req.into_inner();
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"))?;
// Check if validation is provided
if let Some(validation) = req.validation {
// Convert proto FieldValidation directly to JSON
let json_value = serde_json::to_value(&validation)
.map_err(|e| Status::internal(format!("serialize error: {e}")))?;
sqlx::query!(
r#"UPDATE table_validation_rules
SET config = $1, updated_at = now()
WHERE table_def_id = $2 AND data_key = $3"#,
json_value,
table_def_id,
req.data_key
)
.execute(&self.db)
.await
.map_err(|e| Status::internal(format!("DB error: {e}")))?;
Ok(Response::new(UpdateFieldValidationResponse {
success: true,
message: format!(
"Validation rules updated for {}.{} column {}",
req.profile_name, req.table_name, req.data_key
),
}))
} else {
Err(Status::invalid_argument("No validation provided"))
}
}
}

View File

@@ -0,0 +1,18 @@
// src/table_validation/post/service.rs
use tonic::{Request, Response, Status};
use sqlx::PgPool;
use common::proto::komp_ac::table_validation::{
UpdateFieldValidationRequest, UpdateFieldValidationResponse,
};
use crate::table_validation::post::repo;
pub struct TableValidationUpdateSvc {
pub db: PgPool,
}
impl TableValidationUpdateSvc {
pub fn new(db: PgPool) -> Self {
Self { db }
}
}