Compare commits

..

45 Commits

Author SHA1 Message Date
filipriec
8127c7bb1b renamed again and fixed some minor stuff 2025-07-25 18:18:00 +02:00
filipriec
7437908baf removal of @ syntax as reference from the backend 2025-07-25 12:13:51 +02:00
filipriec
9eb46cb5d3 kompAC 2025-07-25 11:59:18 +02:00
filipriec
38a70128b0 prod code 2, time for new functionality 2025-07-25 00:08:16 +02:00
filipriec
c58ce52b33 fixing warnings and making prod code 2025-07-24 23:57:21 +02:00
filipriec
c82813185f code cleanup in post endpoint 2025-07-24 22:18:36 +02:00
filipriec
a96681e9d6 cleaned up code 2025-07-24 22:04:55 +02:00
filipriec
4df6c40034 removal of hardcoded fix, now working in general 2025-07-24 21:56:07 +02:00
filipriec
089d728cc7 passers 2025-07-24 10:55:03 +02:00
filipriec
aca3d718b5 CHECK THIS COMMIT I HAVE NO CLUE IF ITS CORRECT 2025-07-23 23:30:56 +02:00
filipriec
8a6a584cf3 compiletime error in test fixed 2025-07-23 00:43:11 +02:00
filipriec
00ed0cf796 test is now passing 2025-07-22 18:31:26 +02:00
filipriec
7e54b2fe43 we got a full passer now 2025-07-20 14:58:39 +02:00
filipriec
84871faad4 another passer, 2 more to go 2025-07-20 11:57:46 +02:00
filipriec
bcb433d7b2 fixing post table script 2025-07-20 11:46:00 +02:00
filipriec
7d1b130b68 we have a passer 2025-07-17 22:55:54 +02:00
filipriec
24c2376ea1 crucial self reference allowed 2025-07-17 22:06:53 +02:00
filipriec
810ef5fc10 post table script is now aware of a type in the database 2025-07-17 21:16:49 +02:00
filipriec
fe246b1fe6 reject boolean and text in math functions at the post table script 2025-07-16 22:31:55 +02:00
filipriec
de42bb48aa deprecated function removed, no need for backup 2025-07-13 08:51:47 +02:00
filipriec
17495c49ac steel scripts now have far better logic than before 2025-07-12 23:06:21 +02:00
filipriec
0e3a7a06a3 put needs more adjustements 2025-07-12 11:06:11 +02:00
filipriec
e0ee48eb9c put endpoint is now stronq boiii 2025-07-11 23:24:28 +02:00
filipriec
d2053b1d5a SCRIPTS only scripts reference to a linked table from this commit 2025-07-11 08:55:32 +02:00
filipriec
fbe8e53858 circular dependency fix in post script logic 2025-07-11 08:50:10 +02:00
filipriec
8fe2581b3f put request at halt until script fixes 2025-07-10 21:11:42 +02:00
filipriec
60cc0e562e adjusted tests for the put request 2025-07-09 22:24:33 +02:00
filipriec
26898d474f tests for steel in post request is fixed with all the errors found in the codebase 2025-07-08 22:04:11 +02:00
filipriec
2311fbaa3b post steel tests 2025-07-08 20:26:48 +02:00
filipriec
be99cd9423 forgotten lock 2025-07-08 18:07:28 +02:00
filipriec
a3dd6fa95b now fully functional without a steel decimal crate, we are only importing it via cargo, because steel decimal is a separate published crate now 2025-07-08 18:02:32 +02:00
filipriec
433d87c96d stuff around publishing crate successfuly done 2025-07-07 22:08:38 +02:00
filipriec
aff4383671 tests are passing well now 2025-07-07 20:29:51 +02:00
filipriec
b7c8f6b1a2 tests in the steel decimal crate with serious issue fixed 2025-07-07 19:24:08 +02:00
filipriec
3443839ba4 placing them properly 2025-07-07 18:47:50 +02:00
filipriec
6c31d48f3b steel decimal needs readme before being published to the crates io 2025-07-07 18:08:45 +02:00
filipriec
1770292fd8 all the tests are now passing perfectly well 2025-07-07 15:35:33 +02:00
filipriec
afdd5c5740 parser finally fixed 2025-07-07 15:07:08 +02:00
filipriec
11487f0833 more fixes, still not done yet 2025-07-07 13:32:06 +02:00
filipriec
4d5d22d0c2 precision to steel decimal crate implemented 2025-07-07 00:31:13 +02:00
filipriec
314a957922 fixes, now .0 from rust decimal is being discussed 2025-07-06 20:53:40 +02:00
filipriec
4c57b562e6 more fixes, not there yet tho 2025-07-05 10:00:04 +02:00
filipriec
a757acf51c we are now fully using steel decimal crate inside of the server crate 2025-07-04 13:11:47 +02:00
filipriec
f4a23be1a2 steel decimal crate with a proper setup, needs so much work to be done to be finished 2025-07-04 11:56:21 +02:00
filipriec
93c67ffa14 steel decimal crate implemented 2025-07-02 16:31:15 +02:00
120 changed files with 11481 additions and 1453 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
.env
/tantivy_indexes
server/tantivy_indexes
steel_decimal/tests/property_tests.proptest-regressions

888
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,14 @@ resolver = "2"
[workspace.package]
# TODO: idk how to do the name, fix later
# name = "Multieko2"
# name = "komp_ac"
version = "0.3.13"
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["Filip Priečinský <filippriec@gmail.com>"]
description = "Poriadny uctovnicky software."
readme = "README.md"
repository = "https://gitlab.com/filipriec/multieko2"
repository = "https://gitlab.com/filipriec/komp_ac"
categories = ["command-line-interface"]
# [workspace.metadata]
@@ -40,4 +40,10 @@ tracing = "0.1.41"
# Search crate
tantivy = "0.24.1"
# Steel_decimal crate
rust_decimal = { version = "1.37.2", features = ["maths", "serde"] }
rust_decimal_macros = "1.37.1"
thiserror = "2.0.12"
regex = "1.11.1"
common = { path = "./common" }

View File

@@ -4,7 +4,7 @@ use crate::config::colors::themes::Theme;
use crate::state::pages::auth::AuthState;
use crate::state::app::state::AppState;
use crate::state::pages::admin::AdminState;
use common::proto::multieko2::table_definition::ProfileTreeResponse;
use common::proto::komp_ac::table_definition::ProfileTreeResponse;
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::Style,

View File

@@ -2,7 +2,7 @@
use crate::config::colors::themes::Theme;
use crate::state::pages::form::FormState;
use common::proto::multieko2::search::search_response::Hit;
use common::proto::komp_ac::search::search_response::Hit;
use ratatui::{
layout::Rect,
style::{Color, Modifier, Style},

View File

@@ -47,7 +47,7 @@ pub fn render_status_line(
}
// --- The normal status line rendering logic (unchanged) ---
let program_info = format!("multieko2 v{}", env!("CARGO_PKG_VERSION"));
let program_info = format!("komp_ac v{}", env!("CARGO_PKG_VERSION"));
let mode_text = if is_edit_mode { "[EDIT]" } else { "[READ-ONLY]" };
let home_dir = dirs::home_dir()

View File

@@ -6,7 +6,7 @@ use ratatui::{
Frame,
};
use crate::config::colors::themes::Theme;
use common::proto::multieko2::table_definition::{ProfileTreeResponse};
use common::proto::komp_ac::table_definition::{ProfileTreeResponse};
use ratatui::text::{Span, Line};
use crate::components::utils::text::truncate_string;

View File

@@ -32,7 +32,7 @@ pub fn render_intro(f: &mut Frame, intro_state: &IntroState, area: Rect, theme:
// Title
let title = Line::from(vec![
Span::styled("multieko2", Style::default().fg(theme.highlight)),
Span::styled("komp_ac", Style::default().fg(theme.highlight)),
Span::styled(" v", Style::default().fg(theme.fg)),
Span::styled(env!("CARGO_PKG_VERSION"), Style::default().fg(theme.secondary)),
]);

View File

@@ -9,7 +9,7 @@ use tracing::{error, info};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
pub const APP_NAME: &str = "multieko2_client";
pub const APP_NAME: &str = "komp_ac_client";
pub const TOKEN_FILE_NAME: &str = "auth.token";
#[derive(Serialize, Deserialize, Debug, Clone)]

View File

@@ -13,7 +13,7 @@ use crate::state::pages::{
form::FormState,
};
use anyhow::Result;
use common::proto::multieko2::search::search_response::Hit;
use common::proto::komp_ac::search::search_response::Hit;
use crossterm::event::{KeyCode, KeyEvent};
use tokio::sync::mpsc;
use tracing::{debug, info};

View File

@@ -2,7 +2,7 @@
use crate::config::binds::config::Config;
use crate::modes::handlers::event::EventOutcome;
use anyhow::Result;
use common::proto::multieko2::table_definition::ProfileTreeResponse;
use common::proto::komp_ac::table_definition::ProfileTreeResponse;
use crossterm::event::{KeyCode, KeyEvent};
use std::collections::{HashMap, HashSet};

View File

@@ -42,7 +42,7 @@ use crate::tui::{
use crate::ui::handlers::context::UiContext;
use crate::ui::handlers::rat_state::UiStateHandler;
use anyhow::Result;
use common::proto::multieko2::search::search_response::Hit;
use common::proto::komp_ac::search::search_response::Hit;
use crossterm::cursor::SetCursorStyle;
use crossterm::event::{Event, KeyCode, KeyEvent};
use tokio::sync::mpsc;

View File

@@ -1,6 +1,6 @@
// src/services/auth.rs
use tonic::transport::Channel;
use common::proto::multieko2::auth::{
use common::proto::komp_ac::auth::{
auth_service_client::AuthServiceClient,
LoginRequest, LoginResponse,
RegisterRequest, AuthResponse,

View File

@@ -1,17 +1,17 @@
// src/services/grpc_client.rs
use common::proto::multieko2::common::Empty;
use common::proto::multieko2::table_structure::table_structure_service_client::TableStructureServiceClient;
use common::proto::multieko2::table_structure::{GetTableStructureRequest, TableStructureResponse};
use common::proto::multieko2::table_definition::{
use common::proto::komp_ac::common::Empty;
use common::proto::komp_ac::table_structure::table_structure_service_client::TableStructureServiceClient;
use common::proto::komp_ac::table_structure::{GetTableStructureRequest, TableStructureResponse};
use common::proto::komp_ac::table_definition::{
table_definition_client::TableDefinitionClient,
PostTableDefinitionRequest, ProfileTreeResponse, TableDefinitionResponse,
};
use common::proto::multieko2::table_script::{
use common::proto::komp_ac::table_script::{
table_script_client::TableScriptClient,
PostTableScriptRequest, TableScriptResponse,
};
use common::proto::multieko2::tables_data::{
use common::proto::komp_ac::tables_data::{
tables_data_client::TablesDataClient,
GetTableDataByPositionRequest,
GetTableDataRequest, // ADD THIS
@@ -22,7 +22,7 @@ use common::proto::multieko2::tables_data::{
PostTableDataRequest, PostTableDataResponse, PutTableDataRequest,
PutTableDataResponse,
};
use common::proto::multieko2::search::{
use common::proto::komp_ac::search::{
searcher_client::SearcherClient, SearchRequest, SearchResponse,
};
use anyhow::{Context, Result};

View File

@@ -98,7 +98,7 @@ impl UiService {
pub async fn initialize_add_logic_table_data(
grpc_client: &mut GrpcClient,
add_logic_state: &mut AddLogicState,
profile_tree: &common::proto::multieko2::table_definition::ProfileTreeResponse,
profile_tree: &common::proto::komp_ac::table_definition::ProfileTreeResponse,
) -> Result<String> {
let profile_name_clone_opt = Some(add_logic_state.profile_name.clone());
let table_name_opt_clone = add_logic_state.selected_table_name.clone();

View File

@@ -1,6 +1,6 @@
// src/state/app/search.rs
use common::proto::multieko2::search::search_response::Hit;
use common::proto::komp_ac::search::search_response::Hit;
/// Holds the complete state for the search palette.
pub struct SearchState {

View File

@@ -1,9 +1,9 @@
// src/state/app/state.rs
use anyhow::Result;
use common::proto::multieko2::table_definition::ProfileTreeResponse;
use common::proto::komp_ac::table_definition::ProfileTreeResponse;
// NEW: Import the types we need for the cache
use common::proto::multieko2::table_structure::TableStructureResponse;
use common::proto::komp_ac::table_structure::TableStructureResponse;
use crate::modes::handlers::mode_manager::AppMode;
use crate::state::app::search::SearchState;
use crate::ui::handlers::context::DialogPurpose;

View File

@@ -1,6 +1,6 @@
// src/state/pages/canvas_state.rs
use common::proto::multieko2::search::search_response::Hit;
use common::proto::komp_ac::search::search_response::Hit;
pub trait CanvasState {
// --- Existing methods (unchanged) ---

View File

@@ -3,7 +3,7 @@
use crate::config::colors::themes::Theme;
use crate::state::app::highlight::HighlightState;
use crate::state::pages::canvas_state::CanvasState;
use common::proto::multieko2::search::search_response::Hit;
use common::proto::komp_ac::search::search_response::Hit;
use ratatui::layout::Rect;
use ratatui::Frame;
use std::collections::HashMap;

View File

@@ -4,7 +4,7 @@ use crate::state::pages::add_table::{
};
use crate::services::GrpcClient;
use anyhow::{anyhow, Result};
use common::proto::multieko2::table_definition::{
use common::proto::komp_ac::table_definition::{
PostTableDefinitionRequest,
ColumnDefinition as ProtoColumnDefinition,
TableLink as ProtoTableLink,

View File

@@ -8,7 +8,7 @@ use crate::state::app::buffer::{AppView, BufferState};
use crate::config::storage::storage::{StoredAuthData, save_auth_data};
use crate::state::pages::canvas_state::CanvasState;
use crate::ui::handlers::context::DialogPurpose;
use common::proto::multieko2::auth::LoginResponse;
use common::proto::komp_ac::auth::LoginResponse;
use anyhow::{Context, Result};
use tokio::spawn;
use tokio::sync::mpsc;

View File

@@ -8,7 +8,7 @@ use crate::state::{
};
use crate::ui::handlers::context::DialogPurpose;
use crate::state::app::buffer::{AppView, BufferState};
use common::proto::multieko2::auth::AuthResponse;
use common::proto::komp_ac::auth::AuthResponse;
use anyhow::Context;
use tokio::spawn;
use tokio::sync::mpsc;

View File

@@ -1,6 +1,6 @@
// src/utils/data_converter.rs
use common::proto::multieko2::table_structure::TableStructureResponse;
use common::proto::komp_ac::table_structure::TableStructureResponse;
use prost_types::{value::Kind, NullValue, Value};
use std::collections::HashMap;

View File

@@ -1,6 +1,6 @@
// proto/adresar.proto
syntax = "proto3";
package multieko2.adresar;
package komp_ac.adresar;
import "common.proto";
// import "table_structure.proto";

View File

@@ -1,6 +1,6 @@
// proto/auth.proto
syntax = "proto3";
package multieko2.auth;
package komp_ac.auth;
import "common.proto";

View File

@@ -1,6 +1,6 @@
// proto/common.proto
syntax = "proto3";
package multieko2.common;
package komp_ac.common;
message Empty {}
message CountResponse { int64 count = 1; }

View File

@@ -1,6 +1,6 @@
// In common/proto/search.proto
syntax = "proto3";
package multieko2.search;
package komp_ac.search;
service Searcher {
rpc SearchTable(SearchRequest) returns (SearchResponse);

View File

@@ -1,12 +1,12 @@
// common/proto/table_definition.proto
syntax = "proto3";
package multieko2.table_definition;
package komp_ac.table_definition;
import "common.proto";
service TableDefinition {
rpc PostTableDefinition (PostTableDefinitionRequest) returns (TableDefinitionResponse);
rpc GetProfileTree (multieko2.common.Empty) returns (ProfileTreeResponse);
rpc GetProfileTree (komp_ac.common.Empty) returns (ProfileTreeResponse);
rpc DeleteTable (DeleteTableRequest) returns (DeleteTableResponse);
}

View File

@@ -1,5 +1,5 @@
syntax = "proto3";
package multieko2.table_script;
package komp_ac.table_script;
service TableScript {
rpc PostTableScript(PostTableScriptRequest) returns (TableScriptResponse);

View File

@@ -1,6 +1,6 @@
// proto/table_structure.proto
syntax = "proto3";
package multieko2.table_structure;
package komp_ac.table_structure;
import "common.proto";

View File

@@ -1,6 +1,6 @@
// common/proto/tables_data.proto
syntax = "proto3";
package multieko2.tables_data;
package komp_ac.tables_data;
import "common.proto";
import "google/protobuf/struct.proto";
@@ -10,7 +10,7 @@ service TablesData {
rpc PutTableData (PutTableDataRequest) returns (PutTableDataResponse);
rpc DeleteTableData (DeleteTableDataRequest) returns (DeleteTableDataResponse);
rpc GetTableData(GetTableDataRequest) returns (GetTableDataResponse);
rpc GetTableDataCount(GetTableDataCountRequest) returns (multieko2.common.CountResponse);
rpc GetTableDataCount(GetTableDataCountRequest) returns (komp_ac.common.CountResponse);
rpc GetTableDataByPosition(GetTableDataByPositionRequest) returns (GetTableDataResponse);
}

View File

@@ -1,6 +1,6 @@
// proto/uctovnictvo.proto
syntax = "proto3";
package multieko2.uctovnictvo;
package komp_ac.uctovnictvo;
import "common.proto";

View File

@@ -3,33 +3,33 @@
pub mod search;
pub mod proto {
pub mod multieko2 {
pub mod komp_ac {
pub mod adresar {
include!("proto/multieko2.adresar.rs");
include!("proto/komp_ac.adresar.rs");
}
pub mod auth {
include!("proto/multieko2.auth.rs");
include!("proto/komp_ac.auth.rs");
}
pub mod common {
include!("proto/multieko2.common.rs");
include!("proto/komp_ac.common.rs");
}
pub mod table_structure {
include!("proto/multieko2.table_structure.rs");
include!("proto/komp_ac.table_structure.rs");
}
pub mod uctovnictvo {
include!("proto/multieko2.uctovnictvo.rs");
include!("proto/komp_ac.uctovnictvo.rs");
}
pub mod table_definition {
include!("proto/multieko2.table_definition.rs");
include!("proto/komp_ac.table_definition.rs");
}
pub mod tables_data {
include!("proto/multieko2.tables_data.rs");
include!("proto/komp_ac.tables_data.rs");
}
pub mod table_script {
include!("proto/multieko2.table_script.rs");
include!("proto/komp_ac.table_script.rs");
}
pub mod search {
include!("proto/multieko2.search.rs");
include!("proto/komp_ac.search.rs");
}
pub const FILE_DESCRIPTOR_SET: &[u8] =
include_bytes!("proto/descriptor.bin");

Binary file not shown.

View File

@@ -0,0 +1,791 @@
// This file is @generated by prost-build.
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct GetAdresarRequest {
#[prost(int64, tag = "1")]
pub id: i64,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct DeleteAdresarRequest {
#[prost(int64, tag = "1")]
pub id: i64,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PostAdresarRequest {
#[prost(string, tag = "1")]
pub firma: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub kz: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub drc: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub ulica: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub psc: ::prost::alloc::string::String,
#[prost(string, tag = "6")]
pub mesto: ::prost::alloc::string::String,
#[prost(string, tag = "7")]
pub stat: ::prost::alloc::string::String,
#[prost(string, tag = "8")]
pub banka: ::prost::alloc::string::String,
#[prost(string, tag = "9")]
pub ucet: ::prost::alloc::string::String,
#[prost(string, tag = "10")]
pub skladm: ::prost::alloc::string::String,
#[prost(string, tag = "11")]
pub ico: ::prost::alloc::string::String,
#[prost(string, tag = "12")]
pub kontakt: ::prost::alloc::string::String,
#[prost(string, tag = "13")]
pub telefon: ::prost::alloc::string::String,
#[prost(string, tag = "14")]
pub skladu: ::prost::alloc::string::String,
#[prost(string, tag = "15")]
pub fax: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AdresarResponse {
#[prost(int64, tag = "1")]
pub id: i64,
#[prost(string, tag = "2")]
pub firma: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub kz: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub drc: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub ulica: ::prost::alloc::string::String,
#[prost(string, tag = "6")]
pub psc: ::prost::alloc::string::String,
#[prost(string, tag = "7")]
pub mesto: ::prost::alloc::string::String,
#[prost(string, tag = "8")]
pub stat: ::prost::alloc::string::String,
#[prost(string, tag = "9")]
pub banka: ::prost::alloc::string::String,
#[prost(string, tag = "10")]
pub ucet: ::prost::alloc::string::String,
#[prost(string, tag = "11")]
pub skladm: ::prost::alloc::string::String,
#[prost(string, tag = "12")]
pub ico: ::prost::alloc::string::String,
#[prost(string, tag = "13")]
pub kontakt: ::prost::alloc::string::String,
#[prost(string, tag = "14")]
pub telefon: ::prost::alloc::string::String,
#[prost(string, tag = "15")]
pub skladu: ::prost::alloc::string::String,
#[prost(string, tag = "16")]
pub fax: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PutAdresarRequest {
#[prost(int64, tag = "1")]
pub id: i64,
#[prost(string, tag = "2")]
pub firma: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub kz: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub drc: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub ulica: ::prost::alloc::string::String,
#[prost(string, tag = "6")]
pub psc: ::prost::alloc::string::String,
#[prost(string, tag = "7")]
pub mesto: ::prost::alloc::string::String,
#[prost(string, tag = "8")]
pub stat: ::prost::alloc::string::String,
#[prost(string, tag = "9")]
pub banka: ::prost::alloc::string::String,
#[prost(string, tag = "10")]
pub ucet: ::prost::alloc::string::String,
#[prost(string, tag = "11")]
pub skladm: ::prost::alloc::string::String,
#[prost(string, tag = "12")]
pub ico: ::prost::alloc::string::String,
#[prost(string, tag = "13")]
pub kontakt: ::prost::alloc::string::String,
#[prost(string, tag = "14")]
pub telefon: ::prost::alloc::string::String,
#[prost(string, tag = "15")]
pub skladu: ::prost::alloc::string::String,
#[prost(string, tag = "16")]
pub fax: ::prost::alloc::string::String,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct DeleteAdresarResponse {
#[prost(bool, tag = "1")]
pub success: bool,
}
/// Generated client implementations.
pub mod adresar_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct AdresarClient<T> {
inner: tonic::client::Grpc<T>,
}
impl AdresarClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> AdresarClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> AdresarClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
AdresarClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn post_adresar(
&mut self,
request: impl tonic::IntoRequest<super::PostAdresarRequest>,
) -> std::result::Result<
tonic::Response<super::AdresarResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.adresar.Adresar/PostAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PostAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn get_adresar(
&mut self,
request: impl tonic::IntoRequest<super::GetAdresarRequest>,
) -> std::result::Result<
tonic::Response<super::AdresarResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.adresar.Adresar/GetAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn put_adresar(
&mut self,
request: impl tonic::IntoRequest<super::PutAdresarRequest>,
) -> std::result::Result<
tonic::Response<super::AdresarResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.adresar.Adresar/PutAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PutAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn delete_adresar(
&mut self,
request: impl tonic::IntoRequest<super::DeleteAdresarRequest>,
) -> std::result::Result<
tonic::Response<super::DeleteAdresarResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.adresar.Adresar/DeleteAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "DeleteAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn get_adresar_count(
&mut self,
request: impl tonic::IntoRequest<super::super::common::Empty>,
) -> std::result::Result<
tonic::Response<super::super::common::CountResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.adresar.Adresar/GetAdresarCount",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarCount"));
self.inner.unary(req, path, codec).await
}
pub async fn get_adresar_by_position(
&mut self,
request: impl tonic::IntoRequest<super::super::common::PositionRequest>,
) -> std::result::Result<
tonic::Response<super::AdresarResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.adresar.Adresar/GetAdresarByPosition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarByPosition"),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod adresar_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with AdresarServer.
#[async_trait]
pub trait Adresar: std::marker::Send + std::marker::Sync + 'static {
async fn post_adresar(
&self,
request: tonic::Request<super::PostAdresarRequest>,
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
async fn get_adresar(
&self,
request: tonic::Request<super::GetAdresarRequest>,
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
async fn put_adresar(
&self,
request: tonic::Request<super::PutAdresarRequest>,
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
async fn delete_adresar(
&self,
request: tonic::Request<super::DeleteAdresarRequest>,
) -> std::result::Result<
tonic::Response<super::DeleteAdresarResponse>,
tonic::Status,
>;
async fn get_adresar_count(
&self,
request: tonic::Request<super::super::common::Empty>,
) -> std::result::Result<
tonic::Response<super::super::common::CountResponse>,
tonic::Status,
>;
async fn get_adresar_by_position(
&self,
request: tonic::Request<super::super::common::PositionRequest>,
) -> std::result::Result<tonic::Response<super::AdresarResponse>, tonic::Status>;
}
#[derive(Debug)]
pub struct AdresarServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> AdresarServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for AdresarServer<T>
where
T: Adresar,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.adresar.Adresar/PostAdresar" => {
#[allow(non_camel_case_types)]
struct PostAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
T: Adresar,
> tonic::server::UnaryService<super::PostAdresarRequest>
for PostAdresarSvc<T> {
type Response = super::AdresarResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PostAdresarRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Adresar>::post_adresar(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PostAdresarSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.adresar.Adresar/GetAdresar" => {
#[allow(non_camel_case_types)]
struct GetAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
T: Adresar,
> tonic::server::UnaryService<super::GetAdresarRequest>
for GetAdresarSvc<T> {
type Response = super::AdresarResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::GetAdresarRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Adresar>::get_adresar(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetAdresarSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.adresar.Adresar/PutAdresar" => {
#[allow(non_camel_case_types)]
struct PutAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
T: Adresar,
> tonic::server::UnaryService<super::PutAdresarRequest>
for PutAdresarSvc<T> {
type Response = super::AdresarResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PutAdresarRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Adresar>::put_adresar(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PutAdresarSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.adresar.Adresar/DeleteAdresar" => {
#[allow(non_camel_case_types)]
struct DeleteAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
T: Adresar,
> tonic::server::UnaryService<super::DeleteAdresarRequest>
for DeleteAdresarSvc<T> {
type Response = super::DeleteAdresarResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::DeleteAdresarRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Adresar>::delete_adresar(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = DeleteAdresarSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.adresar.Adresar/GetAdresarCount" => {
#[allow(non_camel_case_types)]
struct GetAdresarCountSvc<T: Adresar>(pub Arc<T>);
impl<
T: Adresar,
> tonic::server::UnaryService<super::super::common::Empty>
for GetAdresarCountSvc<T> {
type Response = super::super::common::CountResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::super::common::Empty>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Adresar>::get_adresar_count(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetAdresarCountSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.adresar.Adresar/GetAdresarByPosition" => {
#[allow(non_camel_case_types)]
struct GetAdresarByPositionSvc<T: Adresar>(pub Arc<T>);
impl<
T: Adresar,
> tonic::server::UnaryService<super::super::common::PositionRequest>
for GetAdresarByPositionSvc<T> {
type Response = super::AdresarResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<
super::super::common::PositionRequest,
>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Adresar>::get_adresar_by_position(&inner, request)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetAdresarByPositionSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for AdresarServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.adresar.Adresar";
impl<T> tonic::server::NamedService for AdresarServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -0,0 +1,418 @@
// This file is @generated by prost-build.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RegisterRequest {
#[prost(string, tag = "1")]
pub username: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub email: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub password: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub password_confirmation: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub role: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AuthResponse {
/// UUID in string format
#[prost(string, tag = "1")]
pub id: ::prost::alloc::string::String,
/// Registered username
#[prost(string, tag = "2")]
pub username: ::prost::alloc::string::String,
/// Registered email (if provided)
#[prost(string, tag = "3")]
pub email: ::prost::alloc::string::String,
/// Default role: 'accountant'
#[prost(string, tag = "4")]
pub role: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LoginRequest {
/// Can be username or email
#[prost(string, tag = "1")]
pub identifier: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub password: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LoginResponse {
/// JWT token
#[prost(string, tag = "1")]
pub access_token: ::prost::alloc::string::String,
/// Usually "Bearer"
#[prost(string, tag = "2")]
pub token_type: ::prost::alloc::string::String,
/// Expiration in seconds (86400 for 24 hours)
#[prost(int32, tag = "3")]
pub expires_in: i32,
/// User's UUID in string format
#[prost(string, tag = "4")]
pub user_id: ::prost::alloc::string::String,
/// User's role
#[prost(string, tag = "5")]
pub role: ::prost::alloc::string::String,
#[prost(string, tag = "6")]
pub username: ::prost::alloc::string::String,
}
/// Generated client implementations.
pub mod auth_service_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct AuthServiceClient<T> {
inner: tonic::client::Grpc<T>,
}
impl AuthServiceClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> AuthServiceClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> AuthServiceClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
AuthServiceClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn register(
&mut self,
request: impl tonic::IntoRequest<super::RegisterRequest>,
) -> std::result::Result<tonic::Response<super::AuthResponse>, tonic::Status> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.auth.AuthService/Register",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Register"));
self.inner.unary(req, path, codec).await
}
pub async fn login(
&mut self,
request: impl tonic::IntoRequest<super::LoginRequest>,
) -> std::result::Result<tonic::Response<super::LoginResponse>, tonic::Status> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.auth.AuthService/Login",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Login"));
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod auth_service_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with AuthServiceServer.
#[async_trait]
pub trait AuthService: std::marker::Send + std::marker::Sync + 'static {
async fn register(
&self,
request: tonic::Request<super::RegisterRequest>,
) -> std::result::Result<tonic::Response<super::AuthResponse>, tonic::Status>;
async fn login(
&self,
request: tonic::Request<super::LoginRequest>,
) -> std::result::Result<tonic::Response<super::LoginResponse>, tonic::Status>;
}
#[derive(Debug)]
pub struct AuthServiceServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> AuthServiceServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for AuthServiceServer<T>
where
T: AuthService,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.auth.AuthService/Register" => {
#[allow(non_camel_case_types)]
struct RegisterSvc<T: AuthService>(pub Arc<T>);
impl<
T: AuthService,
> tonic::server::UnaryService<super::RegisterRequest>
for RegisterSvc<T> {
type Response = super::AuthResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::RegisterRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as AuthService>::register(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = RegisterSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.auth.AuthService/Login" => {
#[allow(non_camel_case_types)]
struct LoginSvc<T: AuthService>(pub Arc<T>);
impl<T: AuthService> tonic::server::UnaryService<super::LoginRequest>
for LoginSvc<T> {
type Response = super::LoginResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::LoginRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as AuthService>::login(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = LoginSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for AuthServiceServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.auth.AuthService";
impl<T> tonic::server::NamedService for AuthServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -0,0 +1,13 @@
// This file is @generated by prost-build.
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct Empty {}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct CountResponse {
#[prost(int64, tag = "1")]
pub count: i64,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct PositionRequest {
#[prost(int64, tag = "1")]
pub position: i64,
}

View File

@@ -0,0 +1,317 @@
// This file is @generated by prost-build.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SearchRequest {
#[prost(string, tag = "1")]
pub table_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub query: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SearchResponse {
#[prost(message, repeated, tag = "1")]
pub hits: ::prost::alloc::vec::Vec<search_response::Hit>,
}
/// Nested message and enum types in `SearchResponse`.
pub mod search_response {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Hit {
/// PostgreSQL row ID
#[prost(int64, tag = "1")]
pub id: i64,
#[prost(float, tag = "2")]
pub score: f32,
#[prost(string, tag = "3")]
pub content_json: ::prost::alloc::string::String,
}
}
/// Generated client implementations.
pub mod searcher_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct SearcherClient<T> {
inner: tonic::client::Grpc<T>,
}
impl SearcherClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> SearcherClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> SearcherClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
SearcherClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn search_table(
&mut self,
request: impl tonic::IntoRequest<super::SearchRequest>,
) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.search.Searcher/SearchTable",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("komp_ac.search.Searcher", "SearchTable"));
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod searcher_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with SearcherServer.
#[async_trait]
pub trait Searcher: std::marker::Send + std::marker::Sync + 'static {
async fn search_table(
&self,
request: tonic::Request<super::SearchRequest>,
) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status>;
}
#[derive(Debug)]
pub struct SearcherServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> SearcherServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for SearcherServer<T>
where
T: Searcher,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.search.Searcher/SearchTable" => {
#[allow(non_camel_case_types)]
struct SearchTableSvc<T: Searcher>(pub Arc<T>);
impl<T: Searcher> tonic::server::UnaryService<super::SearchRequest>
for SearchTableSvc<T> {
type Response = super::SearchResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::SearchRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Searcher>::search_table(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = SearchTableSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for SearcherServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.search.Searcher";
impl<T> tonic::server::NamedService for SearcherServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -0,0 +1,544 @@
// This file is @generated by prost-build.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TableLink {
#[prost(string, tag = "1")]
pub linked_table_name: ::prost::alloc::string::String,
#[prost(bool, tag = "2")]
pub required: bool,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PostTableDefinitionRequest {
#[prost(string, tag = "1")]
pub table_name: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "2")]
pub links: ::prost::alloc::vec::Vec<TableLink>,
#[prost(message, repeated, tag = "3")]
pub columns: ::prost::alloc::vec::Vec<ColumnDefinition>,
#[prost(string, repeated, tag = "4")]
pub indexes: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(string, tag = "5")]
pub profile_name: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ColumnDefinition {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub field_type: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TableDefinitionResponse {
#[prost(bool, tag = "1")]
pub success: bool,
#[prost(string, tag = "2")]
pub sql: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProfileTreeResponse {
#[prost(message, repeated, tag = "1")]
pub profiles: ::prost::alloc::vec::Vec<profile_tree_response::Profile>,
}
/// Nested message and enum types in `ProfileTreeResponse`.
pub mod profile_tree_response {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Table {
#[prost(int64, tag = "1")]
pub id: i64,
#[prost(string, tag = "2")]
pub name: ::prost::alloc::string::String,
#[prost(string, repeated, tag = "3")]
pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Profile {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "2")]
pub tables: ::prost::alloc::vec::Vec<Table>,
}
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DeleteTableRequest {
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DeleteTableResponse {
#[prost(bool, tag = "1")]
pub success: bool,
#[prost(string, tag = "2")]
pub message: ::prost::alloc::string::String,
}
/// Generated client implementations.
pub mod table_definition_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct TableDefinitionClient<T> {
inner: tonic::client::Grpc<T>,
}
impl TableDefinitionClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> TableDefinitionClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> TableDefinitionClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
TableDefinitionClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn post_table_definition(
&mut self,
request: impl tonic::IntoRequest<super::PostTableDefinitionRequest>,
) -> std::result::Result<
tonic::Response<super::TableDefinitionResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.table_definition.TableDefinition/PostTableDefinition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.table_definition.TableDefinition",
"PostTableDefinition",
),
);
self.inner.unary(req, path, codec).await
}
pub async fn get_profile_tree(
&mut self,
request: impl tonic::IntoRequest<super::super::common::Empty>,
) -> std::result::Result<
tonic::Response<super::ProfileTreeResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.table_definition.TableDefinition/GetProfileTree",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.table_definition.TableDefinition",
"GetProfileTree",
),
);
self.inner.unary(req, path, codec).await
}
pub async fn delete_table(
&mut self,
request: impl tonic::IntoRequest<super::DeleteTableRequest>,
) -> std::result::Result<
tonic::Response<super::DeleteTableResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.table_definition.TableDefinition/DeleteTable",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.table_definition.TableDefinition",
"DeleteTable",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod table_definition_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with TableDefinitionServer.
#[async_trait]
pub trait TableDefinition: std::marker::Send + std::marker::Sync + 'static {
async fn post_table_definition(
&self,
request: tonic::Request<super::PostTableDefinitionRequest>,
) -> std::result::Result<
tonic::Response<super::TableDefinitionResponse>,
tonic::Status,
>;
async fn get_profile_tree(
&self,
request: tonic::Request<super::super::common::Empty>,
) -> std::result::Result<
tonic::Response<super::ProfileTreeResponse>,
tonic::Status,
>;
async fn delete_table(
&self,
request: tonic::Request<super::DeleteTableRequest>,
) -> std::result::Result<
tonic::Response<super::DeleteTableResponse>,
tonic::Status,
>;
}
#[derive(Debug)]
pub struct TableDefinitionServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> TableDefinitionServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for TableDefinitionServer<T>
where
T: TableDefinition,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.table_definition.TableDefinition/PostTableDefinition" => {
#[allow(non_camel_case_types)]
struct PostTableDefinitionSvc<T: TableDefinition>(pub Arc<T>);
impl<
T: TableDefinition,
> tonic::server::UnaryService<super::PostTableDefinitionRequest>
for PostTableDefinitionSvc<T> {
type Response = super::TableDefinitionResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PostTableDefinitionRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TableDefinition>::post_table_definition(
&inner,
request,
)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PostTableDefinitionSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.table_definition.TableDefinition/GetProfileTree" => {
#[allow(non_camel_case_types)]
struct GetProfileTreeSvc<T: TableDefinition>(pub Arc<T>);
impl<
T: TableDefinition,
> tonic::server::UnaryService<super::super::common::Empty>
for GetProfileTreeSvc<T> {
type Response = super::ProfileTreeResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::super::common::Empty>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TableDefinition>::get_profile_tree(&inner, request)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetProfileTreeSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.table_definition.TableDefinition/DeleteTable" => {
#[allow(non_camel_case_types)]
struct DeleteTableSvc<T: TableDefinition>(pub Arc<T>);
impl<
T: TableDefinition,
> tonic::server::UnaryService<super::DeleteTableRequest>
for DeleteTableSvc<T> {
type Response = super::DeleteTableResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::DeleteTableRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TableDefinition>::delete_table(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = DeleteTableSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for TableDefinitionServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.table_definition.TableDefinition";
impl<T> tonic::server::NamedService for TableDefinitionServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -0,0 +1,323 @@
// This file is @generated by prost-build.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PostTableScriptRequest {
#[prost(int64, tag = "1")]
pub table_definition_id: i64,
#[prost(string, tag = "2")]
pub target_column: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub script: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub description: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TableScriptResponse {
#[prost(int64, tag = "1")]
pub id: i64,
#[prost(string, tag = "2")]
pub warnings: ::prost::alloc::string::String,
}
/// Generated client implementations.
pub mod table_script_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct TableScriptClient<T> {
inner: tonic::client::Grpc<T>,
}
impl TableScriptClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> TableScriptClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> TableScriptClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
TableScriptClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn post_table_script(
&mut self,
request: impl tonic::IntoRequest<super::PostTableScriptRequest>,
) -> std::result::Result<
tonic::Response<super::TableScriptResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.table_script.TableScript/PostTableScript",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.table_script.TableScript",
"PostTableScript",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod table_script_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with TableScriptServer.
#[async_trait]
pub trait TableScript: std::marker::Send + std::marker::Sync + 'static {
async fn post_table_script(
&self,
request: tonic::Request<super::PostTableScriptRequest>,
) -> std::result::Result<
tonic::Response<super::TableScriptResponse>,
tonic::Status,
>;
}
#[derive(Debug)]
pub struct TableScriptServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> TableScriptServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for TableScriptServer<T>
where
T: TableScript,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.table_script.TableScript/PostTableScript" => {
#[allow(non_camel_case_types)]
struct PostTableScriptSvc<T: TableScript>(pub Arc<T>);
impl<
T: TableScript,
> tonic::server::UnaryService<super::PostTableScriptRequest>
for PostTableScriptSvc<T> {
type Response = super::TableScriptResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PostTableScriptRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TableScript>::post_table_script(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PostTableScriptSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for TableScriptServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.table_script.TableScript";
impl<T> tonic::server::NamedService for TableScriptServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -0,0 +1,336 @@
// This file is @generated by prost-build.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetTableStructureRequest {
/// e.g., "default"
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
/// e.g., "2025_adresar6"
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TableStructureResponse {
#[prost(message, repeated, tag = "1")]
pub columns: ::prost::alloc::vec::Vec<TableColumn>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TableColumn {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
/// e.g., "TEXT", "BIGINT", "VARCHAR(255)", "TIMESTAMPTZ"
#[prost(string, tag = "2")]
pub data_type: ::prost::alloc::string::String,
#[prost(bool, tag = "3")]
pub is_nullable: bool,
#[prost(bool, tag = "4")]
pub is_primary_key: bool,
}
/// Generated client implementations.
pub mod table_structure_service_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct TableStructureServiceClient<T> {
inner: tonic::client::Grpc<T>,
}
impl TableStructureServiceClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> TableStructureServiceClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> TableStructureServiceClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
TableStructureServiceClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn get_table_structure(
&mut self,
request: impl tonic::IntoRequest<super::GetTableStructureRequest>,
) -> std::result::Result<
tonic::Response<super::TableStructureResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.table_structure.TableStructureService/GetTableStructure",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.table_structure.TableStructureService",
"GetTableStructure",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod table_structure_service_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with TableStructureServiceServer.
#[async_trait]
pub trait TableStructureService: std::marker::Send + std::marker::Sync + 'static {
async fn get_table_structure(
&self,
request: tonic::Request<super::GetTableStructureRequest>,
) -> std::result::Result<
tonic::Response<super::TableStructureResponse>,
tonic::Status,
>;
}
#[derive(Debug)]
pub struct TableStructureServiceServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> TableStructureServiceServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>>
for TableStructureServiceServer<T>
where
T: TableStructureService,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.table_structure.TableStructureService/GetTableStructure" => {
#[allow(non_camel_case_types)]
struct GetTableStructureSvc<T: TableStructureService>(pub Arc<T>);
impl<
T: TableStructureService,
> tonic::server::UnaryService<super::GetTableStructureRequest>
for GetTableStructureSvc<T> {
type Response = super::TableStructureResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::GetTableStructureRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TableStructureService>::get_table_structure(
&inner,
request,
)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetTableStructureSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for TableStructureServiceServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.table_structure.TableStructureService";
impl<T> tonic::server::NamedService for TableStructureServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -0,0 +1,794 @@
// This file is @generated by prost-build.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PostTableDataRequest {
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
#[prost(map = "string, message", tag = "3")]
pub data: ::std::collections::HashMap<
::prost::alloc::string::String,
::prost_types::Value,
>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PostTableDataResponse {
#[prost(bool, tag = "1")]
pub success: bool,
#[prost(string, tag = "2")]
pub message: ::prost::alloc::string::String,
#[prost(int64, tag = "3")]
pub inserted_id: i64,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PutTableDataRequest {
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
#[prost(int64, tag = "3")]
pub id: i64,
#[prost(map = "string, message", tag = "4")]
pub data: ::std::collections::HashMap<
::prost::alloc::string::String,
::prost_types::Value,
>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PutTableDataResponse {
#[prost(bool, tag = "1")]
pub success: bool,
#[prost(string, tag = "2")]
pub message: ::prost::alloc::string::String,
#[prost(int64, tag = "3")]
pub updated_id: i64,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DeleteTableDataRequest {
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
#[prost(int64, tag = "3")]
pub record_id: i64,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct DeleteTableDataResponse {
#[prost(bool, tag = "1")]
pub success: bool,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetTableDataRequest {
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
#[prost(int64, tag = "3")]
pub id: i64,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetTableDataResponse {
#[prost(map = "string, string", tag = "1")]
pub data: ::std::collections::HashMap<
::prost::alloc::string::String,
::prost::alloc::string::String,
>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetTableDataCountRequest {
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetTableDataByPositionRequest {
#[prost(string, tag = "1")]
pub profile_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub table_name: ::prost::alloc::string::String,
#[prost(int32, tag = "3")]
pub position: i32,
}
/// Generated client implementations.
pub mod tables_data_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct TablesDataClient<T> {
inner: tonic::client::Grpc<T>,
}
impl TablesDataClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> TablesDataClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> TablesDataClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
TablesDataClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn post_table_data(
&mut self,
request: impl tonic::IntoRequest<super::PostTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::PostTableDataResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.tables_data.TablesData/PostTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.tables_data.TablesData", "PostTableData"),
);
self.inner.unary(req, path, codec).await
}
pub async fn put_table_data(
&mut self,
request: impl tonic::IntoRequest<super::PutTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::PutTableDataResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.tables_data.TablesData/PutTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.tables_data.TablesData", "PutTableData"),
);
self.inner.unary(req, path, codec).await
}
pub async fn delete_table_data(
&mut self,
request: impl tonic::IntoRequest<super::DeleteTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::DeleteTableDataResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.tables_data.TablesData/DeleteTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.tables_data.TablesData", "DeleteTableData"),
);
self.inner.unary(req, path, codec).await
}
pub async fn get_table_data(
&mut self,
request: impl tonic::IntoRequest<super::GetTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::GetTableDataResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.tables_data.TablesData/GetTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.tables_data.TablesData", "GetTableData"),
);
self.inner.unary(req, path, codec).await
}
pub async fn get_table_data_count(
&mut self,
request: impl tonic::IntoRequest<super::GetTableDataCountRequest>,
) -> std::result::Result<
tonic::Response<super::super::common::CountResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.tables_data.TablesData/GetTableDataCount",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.tables_data.TablesData",
"GetTableDataCount",
),
);
self.inner.unary(req, path, codec).await
}
pub async fn get_table_data_by_position(
&mut self,
request: impl tonic::IntoRequest<super::GetTableDataByPositionRequest>,
) -> std::result::Result<
tonic::Response<super::GetTableDataResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.tables_data.TablesData/GetTableDataByPosition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.tables_data.TablesData",
"GetTableDataByPosition",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod tables_data_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with TablesDataServer.
#[async_trait]
pub trait TablesData: std::marker::Send + std::marker::Sync + 'static {
async fn post_table_data(
&self,
request: tonic::Request<super::PostTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::PostTableDataResponse>,
tonic::Status,
>;
async fn put_table_data(
&self,
request: tonic::Request<super::PutTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::PutTableDataResponse>,
tonic::Status,
>;
async fn delete_table_data(
&self,
request: tonic::Request<super::DeleteTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::DeleteTableDataResponse>,
tonic::Status,
>;
async fn get_table_data(
&self,
request: tonic::Request<super::GetTableDataRequest>,
) -> std::result::Result<
tonic::Response<super::GetTableDataResponse>,
tonic::Status,
>;
async fn get_table_data_count(
&self,
request: tonic::Request<super::GetTableDataCountRequest>,
) -> std::result::Result<
tonic::Response<super::super::common::CountResponse>,
tonic::Status,
>;
async fn get_table_data_by_position(
&self,
request: tonic::Request<super::GetTableDataByPositionRequest>,
) -> std::result::Result<
tonic::Response<super::GetTableDataResponse>,
tonic::Status,
>;
}
#[derive(Debug)]
pub struct TablesDataServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> TablesDataServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for TablesDataServer<T>
where
T: TablesData,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.tables_data.TablesData/PostTableData" => {
#[allow(non_camel_case_types)]
struct PostTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
T: TablesData,
> tonic::server::UnaryService<super::PostTableDataRequest>
for PostTableDataSvc<T> {
type Response = super::PostTableDataResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PostTableDataRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TablesData>::post_table_data(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PostTableDataSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.tables_data.TablesData/PutTableData" => {
#[allow(non_camel_case_types)]
struct PutTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
T: TablesData,
> tonic::server::UnaryService<super::PutTableDataRequest>
for PutTableDataSvc<T> {
type Response = super::PutTableDataResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PutTableDataRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TablesData>::put_table_data(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PutTableDataSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.tables_data.TablesData/DeleteTableData" => {
#[allow(non_camel_case_types)]
struct DeleteTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
T: TablesData,
> tonic::server::UnaryService<super::DeleteTableDataRequest>
for DeleteTableDataSvc<T> {
type Response = super::DeleteTableDataResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::DeleteTableDataRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TablesData>::delete_table_data(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = DeleteTableDataSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.tables_data.TablesData/GetTableData" => {
#[allow(non_camel_case_types)]
struct GetTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
T: TablesData,
> tonic::server::UnaryService<super::GetTableDataRequest>
for GetTableDataSvc<T> {
type Response = super::GetTableDataResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::GetTableDataRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TablesData>::get_table_data(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetTableDataSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.tables_data.TablesData/GetTableDataCount" => {
#[allow(non_camel_case_types)]
struct GetTableDataCountSvc<T: TablesData>(pub Arc<T>);
impl<
T: TablesData,
> tonic::server::UnaryService<super::GetTableDataCountRequest>
for GetTableDataCountSvc<T> {
type Response = super::super::common::CountResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::GetTableDataCountRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TablesData>::get_table_data_count(&inner, request)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetTableDataCountSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.tables_data.TablesData/GetTableDataByPosition" => {
#[allow(non_camel_case_types)]
struct GetTableDataByPositionSvc<T: TablesData>(pub Arc<T>);
impl<
T: TablesData,
> tonic::server::UnaryService<super::GetTableDataByPositionRequest>
for GetTableDataByPositionSvc<T> {
type Response = super::GetTableDataResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::GetTableDataByPositionRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TablesData>::get_table_data_by_position(
&inner,
request,
)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetTableDataByPositionSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for TablesDataServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.tables_data.TablesData";
impl<T> tonic::server::NamedService for TablesDataServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -0,0 +1,712 @@
// This file is @generated by prost-build.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PostUctovnictvoRequest {
#[prost(int64, tag = "1")]
pub adresar_id: i64,
#[prost(string, tag = "2")]
pub c_dokladu: ::prost::alloc::string::String,
/// Use string for simplicity, or use google.protobuf.Timestamp for better date handling
#[prost(string, tag = "3")]
pub datum: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub c_faktury: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub obsah: ::prost::alloc::string::String,
#[prost(string, tag = "6")]
pub stredisko: ::prost::alloc::string::String,
#[prost(string, tag = "7")]
pub c_uctu: ::prost::alloc::string::String,
#[prost(string, tag = "8")]
pub md: ::prost::alloc::string::String,
#[prost(string, tag = "9")]
pub identif: ::prost::alloc::string::String,
#[prost(string, tag = "10")]
pub poznanka: ::prost::alloc::string::String,
#[prost(string, tag = "11")]
pub firma: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UctovnictvoResponse {
#[prost(int64, tag = "1")]
pub id: i64,
#[prost(int64, tag = "2")]
pub adresar_id: i64,
#[prost(string, tag = "3")]
pub c_dokladu: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub datum: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub c_faktury: ::prost::alloc::string::String,
#[prost(string, tag = "6")]
pub obsah: ::prost::alloc::string::String,
#[prost(string, tag = "7")]
pub stredisko: ::prost::alloc::string::String,
#[prost(string, tag = "8")]
pub c_uctu: ::prost::alloc::string::String,
#[prost(string, tag = "9")]
pub md: ::prost::alloc::string::String,
#[prost(string, tag = "10")]
pub identif: ::prost::alloc::string::String,
#[prost(string, tag = "11")]
pub poznanka: ::prost::alloc::string::String,
#[prost(string, tag = "12")]
pub firma: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PutUctovnictvoRequest {
#[prost(int64, tag = "1")]
pub id: i64,
#[prost(int64, tag = "2")]
pub adresar_id: i64,
#[prost(string, tag = "3")]
pub c_dokladu: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub datum: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub c_faktury: ::prost::alloc::string::String,
#[prost(string, tag = "6")]
pub obsah: ::prost::alloc::string::String,
#[prost(string, tag = "7")]
pub stredisko: ::prost::alloc::string::String,
#[prost(string, tag = "8")]
pub c_uctu: ::prost::alloc::string::String,
#[prost(string, tag = "9")]
pub md: ::prost::alloc::string::String,
#[prost(string, tag = "10")]
pub identif: ::prost::alloc::string::String,
#[prost(string, tag = "11")]
pub poznanka: ::prost::alloc::string::String,
#[prost(string, tag = "12")]
pub firma: ::prost::alloc::string::String,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct GetUctovnictvoRequest {
#[prost(int64, tag = "1")]
pub id: i64,
}
/// Generated client implementations.
pub mod uctovnictvo_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct UctovnictvoClient<T> {
inner: tonic::client::Grpc<T>,
}
impl UctovnictvoClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> UctovnictvoClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> UctovnictvoClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
UctovnictvoClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn post_uctovnictvo(
&mut self,
request: impl tonic::IntoRequest<super::PostUctovnictvoRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.uctovnictvo.Uctovnictvo", "PostUctovnictvo"),
);
self.inner.unary(req, path, codec).await
}
pub async fn get_uctovnictvo(
&mut self,
request: impl tonic::IntoRequest<super::GetUctovnictvoRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.uctovnictvo.Uctovnictvo", "GetUctovnictvo"),
);
self.inner.unary(req, path, codec).await
}
pub async fn get_uctovnictvo_count(
&mut self,
request: impl tonic::IntoRequest<super::super::common::Empty>,
) -> std::result::Result<
tonic::Response<super::super::common::CountResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.uctovnictvo.Uctovnictvo",
"GetUctovnictvoCount",
),
);
self.inner.unary(req, path, codec).await
}
pub async fn get_uctovnictvo_by_position(
&mut self,
request: impl tonic::IntoRequest<super::super::common::PositionRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"komp_ac.uctovnictvo.Uctovnictvo",
"GetUctovnictvoByPosition",
),
);
self.inner.unary(req, path, codec).await
}
pub async fn put_uctovnictvo(
&mut self,
request: impl tonic::IntoRequest<super::PutUctovnictvoRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("komp_ac.uctovnictvo.Uctovnictvo", "PutUctovnictvo"),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
pub mod uctovnictvo_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with UctovnictvoServer.
#[async_trait]
pub trait Uctovnictvo: std::marker::Send + std::marker::Sync + 'static {
async fn post_uctovnictvo(
&self,
request: tonic::Request<super::PostUctovnictvoRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
>;
async fn get_uctovnictvo(
&self,
request: tonic::Request<super::GetUctovnictvoRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
>;
async fn get_uctovnictvo_count(
&self,
request: tonic::Request<super::super::common::Empty>,
) -> std::result::Result<
tonic::Response<super::super::common::CountResponse>,
tonic::Status,
>;
async fn get_uctovnictvo_by_position(
&self,
request: tonic::Request<super::super::common::PositionRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
>;
async fn put_uctovnictvo(
&self,
request: tonic::Request<super::PutUctovnictvoRequest>,
) -> std::result::Result<
tonic::Response<super::UctovnictvoResponse>,
tonic::Status,
>;
}
#[derive(Debug)]
pub struct UctovnictvoServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> UctovnictvoServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for UctovnictvoServer<T>
where
T: Uctovnictvo,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo" => {
#[allow(non_camel_case_types)]
struct PostUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
T: Uctovnictvo,
> tonic::server::UnaryService<super::PostUctovnictvoRequest>
for PostUctovnictvoSvc<T> {
type Response = super::UctovnictvoResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PostUctovnictvoRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Uctovnictvo>::post_uctovnictvo(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PostUctovnictvoSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo" => {
#[allow(non_camel_case_types)]
struct GetUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
T: Uctovnictvo,
> tonic::server::UnaryService<super::GetUctovnictvoRequest>
for GetUctovnictvoSvc<T> {
type Response = super::UctovnictvoResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::GetUctovnictvoRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Uctovnictvo>::get_uctovnictvo(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetUctovnictvoSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount" => {
#[allow(non_camel_case_types)]
struct GetUctovnictvoCountSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
T: Uctovnictvo,
> tonic::server::UnaryService<super::super::common::Empty>
for GetUctovnictvoCountSvc<T> {
type Response = super::super::common::CountResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::super::common::Empty>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Uctovnictvo>::get_uctovnictvo_count(&inner, request)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetUctovnictvoCountSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition" => {
#[allow(non_camel_case_types)]
struct GetUctovnictvoByPositionSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
T: Uctovnictvo,
> tonic::server::UnaryService<super::super::common::PositionRequest>
for GetUctovnictvoByPositionSvc<T> {
type Response = super::UctovnictvoResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<
super::super::common::PositionRequest,
>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Uctovnictvo>::get_uctovnictvo_by_position(
&inner,
request,
)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = GetUctovnictvoByPositionSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo" => {
#[allow(non_camel_case_types)]
struct PutUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
T: Uctovnictvo,
> tonic::server::UnaryService<super::PutUctovnictvoRequest>
for PutUctovnictvoSvc<T> {
type Response = super::UctovnictvoResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::PutUctovnictvoRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Uctovnictvo>::put_uctovnictvo(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = PutUctovnictvoSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for UctovnictvoServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "komp_ac.uctovnictvo.Uctovnictvo";
impl<T> tonic::server::NamedService for UctovnictvoServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}

View File

@@ -225,11 +225,11 @@ pub mod adresar_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.adresar.Adresar/PostAdresar",
"/komp_ac.adresar.Adresar/PostAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "PostAdresar"));
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PostAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn get_adresar(
@@ -249,11 +249,11 @@ pub mod adresar_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.adresar.Adresar/GetAdresar",
"/komp_ac.adresar.Adresar/GetAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "GetAdresar"));
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn put_adresar(
@@ -273,11 +273,11 @@ pub mod adresar_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.adresar.Adresar/PutAdresar",
"/komp_ac.adresar.Adresar/PutAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "PutAdresar"));
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "PutAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn delete_adresar(
@@ -297,11 +297,11 @@ pub mod adresar_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.adresar.Adresar/DeleteAdresar",
"/komp_ac.adresar.Adresar/DeleteAdresar",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "DeleteAdresar"));
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "DeleteAdresar"));
self.inner.unary(req, path, codec).await
}
pub async fn get_adresar_count(
@@ -321,11 +321,11 @@ pub mod adresar_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.adresar.Adresar/GetAdresarCount",
"/komp_ac.adresar.Adresar/GetAdresarCount",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.adresar.Adresar", "GetAdresarCount"));
.insert(GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarCount"));
self.inner.unary(req, path, codec).await
}
pub async fn get_adresar_by_position(
@@ -345,12 +345,12 @@ pub mod adresar_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.adresar.Adresar/GetAdresarByPosition",
"/komp_ac.adresar.Adresar/GetAdresarByPosition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("multieko2.adresar.Adresar", "GetAdresarByPosition"),
GrpcMethod::new("komp_ac.adresar.Adresar", "GetAdresarByPosition"),
);
self.inner.unary(req, path, codec).await
}
@@ -476,7 +476,7 @@ pub mod adresar_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.adresar.Adresar/PostAdresar" => {
"/komp_ac.adresar.Adresar/PostAdresar" => {
#[allow(non_camel_case_types)]
struct PostAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
@@ -521,7 +521,7 @@ pub mod adresar_server {
};
Box::pin(fut)
}
"/multieko2.adresar.Adresar/GetAdresar" => {
"/komp_ac.adresar.Adresar/GetAdresar" => {
#[allow(non_camel_case_types)]
struct GetAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
@@ -566,7 +566,7 @@ pub mod adresar_server {
};
Box::pin(fut)
}
"/multieko2.adresar.Adresar/PutAdresar" => {
"/komp_ac.adresar.Adresar/PutAdresar" => {
#[allow(non_camel_case_types)]
struct PutAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
@@ -611,7 +611,7 @@ pub mod adresar_server {
};
Box::pin(fut)
}
"/multieko2.adresar.Adresar/DeleteAdresar" => {
"/komp_ac.adresar.Adresar/DeleteAdresar" => {
#[allow(non_camel_case_types)]
struct DeleteAdresarSvc<T: Adresar>(pub Arc<T>);
impl<
@@ -656,7 +656,7 @@ pub mod adresar_server {
};
Box::pin(fut)
}
"/multieko2.adresar.Adresar/GetAdresarCount" => {
"/komp_ac.adresar.Adresar/GetAdresarCount" => {
#[allow(non_camel_case_types)]
struct GetAdresarCountSvc<T: Adresar>(pub Arc<T>);
impl<
@@ -701,7 +701,7 @@ pub mod adresar_server {
};
Box::pin(fut)
}
"/multieko2.adresar.Adresar/GetAdresarByPosition" => {
"/komp_ac.adresar.Adresar/GetAdresarByPosition" => {
#[allow(non_camel_case_types)]
struct GetAdresarByPositionSvc<T: Adresar>(pub Arc<T>);
impl<
@@ -784,7 +784,7 @@ pub mod adresar_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.adresar.Adresar";
pub const SERVICE_NAME: &str = "komp_ac.adresar.Adresar";
impl<T> tonic::server::NamedService for AdresarServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -160,11 +160,11 @@ pub mod auth_service_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.auth.AuthService/Register",
"/komp_ac.auth.AuthService/Register",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.auth.AuthService", "Register"));
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Register"));
self.inner.unary(req, path, codec).await
}
pub async fn login(
@@ -181,11 +181,11 @@ pub mod auth_service_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.auth.AuthService/Login",
"/komp_ac.auth.AuthService/Login",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.auth.AuthService", "Login"));
.insert(GrpcMethod::new("komp_ac.auth.AuthService", "Login"));
self.inner.unary(req, path, codec).await
}
}
@@ -288,7 +288,7 @@ pub mod auth_service_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.auth.AuthService/Register" => {
"/komp_ac.auth.AuthService/Register" => {
#[allow(non_camel_case_types)]
struct RegisterSvc<T: AuthService>(pub Arc<T>);
impl<
@@ -333,7 +333,7 @@ pub mod auth_service_server {
};
Box::pin(fut)
}
"/multieko2.auth.AuthService/Login" => {
"/komp_ac.auth.AuthService/Login" => {
#[allow(non_camel_case_types)]
struct LoginSvc<T: AuthService>(pub Arc<T>);
impl<T: AuthService> tonic::server::UnaryService<super::LoginRequest>
@@ -411,7 +411,7 @@ pub mod auth_service_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.auth.AuthService";
pub const SERVICE_NAME: &str = "komp_ac.auth.AuthService";
impl<T> tonic::server::NamedService for AuthServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -129,11 +129,11 @@ pub mod searcher_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.search.Searcher/SearchTable",
"/komp_ac.search.Searcher/SearchTable",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("multieko2.search.Searcher", "SearchTable"));
.insert(GrpcMethod::new("komp_ac.search.Searcher", "SearchTable"));
self.inner.unary(req, path, codec).await
}
}
@@ -232,7 +232,7 @@ pub mod searcher_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.search.Searcher/SearchTable" => {
"/komp_ac.search.Searcher/SearchTable" => {
#[allow(non_camel_case_types)]
struct SearchTableSvc<T: Searcher>(pub Arc<T>);
impl<T: Searcher> tonic::server::UnaryService<super::SearchRequest>
@@ -310,7 +310,7 @@ pub mod searcher_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.search.Searcher";
pub const SERVICE_NAME: &str = "komp_ac.search.Searcher";
impl<T> tonic::server::NamedService for SearcherServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -179,13 +179,13 @@ pub mod table_definition_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.table_definition.TableDefinition/PostTableDefinition",
"/komp_ac.table_definition.TableDefinition/PostTableDefinition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.table_definition.TableDefinition",
"komp_ac.table_definition.TableDefinition",
"PostTableDefinition",
),
);
@@ -208,13 +208,13 @@ pub mod table_definition_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.table_definition.TableDefinition/GetProfileTree",
"/komp_ac.table_definition.TableDefinition/GetProfileTree",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.table_definition.TableDefinition",
"komp_ac.table_definition.TableDefinition",
"GetProfileTree",
),
);
@@ -237,13 +237,13 @@ pub mod table_definition_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.table_definition.TableDefinition/DeleteTable",
"/komp_ac.table_definition.TableDefinition/DeleteTable",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.table_definition.TableDefinition",
"komp_ac.table_definition.TableDefinition",
"DeleteTable",
),
);
@@ -362,7 +362,7 @@ pub mod table_definition_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.table_definition.TableDefinition/PostTableDefinition" => {
"/komp_ac.table_definition.TableDefinition/PostTableDefinition" => {
#[allow(non_camel_case_types)]
struct PostTableDefinitionSvc<T: TableDefinition>(pub Arc<T>);
impl<
@@ -411,7 +411,7 @@ pub mod table_definition_server {
};
Box::pin(fut)
}
"/multieko2.table_definition.TableDefinition/GetProfileTree" => {
"/komp_ac.table_definition.TableDefinition/GetProfileTree" => {
#[allow(non_camel_case_types)]
struct GetProfileTreeSvc<T: TableDefinition>(pub Arc<T>);
impl<
@@ -457,7 +457,7 @@ pub mod table_definition_server {
};
Box::pin(fut)
}
"/multieko2.table_definition.TableDefinition/DeleteTable" => {
"/komp_ac.table_definition.TableDefinition/DeleteTable" => {
#[allow(non_camel_case_types)]
struct DeleteTableSvc<T: TableDefinition>(pub Arc<T>);
impl<
@@ -537,7 +537,7 @@ pub mod table_definition_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.table_definition.TableDefinition";
pub const SERVICE_NAME: &str = "komp_ac.table_definition.TableDefinition";
impl<T> tonic::server::NamedService for TableDefinitionServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -125,13 +125,13 @@ pub mod table_script_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.table_script.TableScript/PostTableScript",
"/komp_ac.table_script.TableScript/PostTableScript",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.table_script.TableScript",
"komp_ac.table_script.TableScript",
"PostTableScript",
),
);
@@ -236,7 +236,7 @@ pub mod table_script_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.table_script.TableScript/PostTableScript" => {
"/komp_ac.table_script.TableScript/PostTableScript" => {
#[allow(non_camel_case_types)]
struct PostTableScriptSvc<T: TableScript>(pub Arc<T>);
impl<
@@ -316,7 +316,7 @@ pub mod table_script_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.table_script.TableScript";
pub const SERVICE_NAME: &str = "komp_ac.table_script.TableScript";
impl<T> tonic::server::NamedService for TableScriptServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -133,13 +133,13 @@ pub mod table_structure_service_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.table_structure.TableStructureService/GetTableStructure",
"/komp_ac.table_structure.TableStructureService/GetTableStructure",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.table_structure.TableStructureService",
"komp_ac.table_structure.TableStructureService",
"GetTableStructure",
),
);
@@ -245,7 +245,7 @@ pub mod table_structure_service_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.table_structure.TableStructureService/GetTableStructure" => {
"/komp_ac.table_structure.TableStructureService/GetTableStructure" => {
#[allow(non_camel_case_types)]
struct GetTableStructureSvc<T: TableStructureService>(pub Arc<T>);
impl<
@@ -329,7 +329,7 @@ pub mod table_structure_service_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.table_structure.TableStructureService";
pub const SERVICE_NAME: &str = "komp_ac.table_structure.TableStructureService";
impl<T> tonic::server::NamedService for TableStructureServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -198,12 +198,12 @@ pub mod tables_data_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.tables_data.TablesData/PostTableData",
"/komp_ac.tables_data.TablesData/PostTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("multieko2.tables_data.TablesData", "PostTableData"),
GrpcMethod::new("komp_ac.tables_data.TablesData", "PostTableData"),
);
self.inner.unary(req, path, codec).await
}
@@ -224,12 +224,12 @@ pub mod tables_data_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.tables_data.TablesData/PutTableData",
"/komp_ac.tables_data.TablesData/PutTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("multieko2.tables_data.TablesData", "PutTableData"),
GrpcMethod::new("komp_ac.tables_data.TablesData", "PutTableData"),
);
self.inner.unary(req, path, codec).await
}
@@ -250,13 +250,13 @@ pub mod tables_data_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.tables_data.TablesData/DeleteTableData",
"/komp_ac.tables_data.TablesData/DeleteTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.tables_data.TablesData",
"komp_ac.tables_data.TablesData",
"DeleteTableData",
),
);
@@ -279,12 +279,12 @@ pub mod tables_data_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.tables_data.TablesData/GetTableData",
"/komp_ac.tables_data.TablesData/GetTableData",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("multieko2.tables_data.TablesData", "GetTableData"),
GrpcMethod::new("komp_ac.tables_data.TablesData", "GetTableData"),
);
self.inner.unary(req, path, codec).await
}
@@ -305,13 +305,13 @@ pub mod tables_data_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.tables_data.TablesData/GetTableDataCount",
"/komp_ac.tables_data.TablesData/GetTableDataCount",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.tables_data.TablesData",
"komp_ac.tables_data.TablesData",
"GetTableDataCount",
),
);
@@ -334,13 +334,13 @@ pub mod tables_data_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.tables_data.TablesData/GetTableDataByPosition",
"/komp_ac.tables_data.TablesData/GetTableDataByPosition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.tables_data.TablesData",
"komp_ac.tables_data.TablesData",
"GetTableDataByPosition",
),
);
@@ -480,7 +480,7 @@ pub mod tables_data_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.tables_data.TablesData/PostTableData" => {
"/komp_ac.tables_data.TablesData/PostTableData" => {
#[allow(non_camel_case_types)]
struct PostTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
@@ -525,7 +525,7 @@ pub mod tables_data_server {
};
Box::pin(fut)
}
"/multieko2.tables_data.TablesData/PutTableData" => {
"/komp_ac.tables_data.TablesData/PutTableData" => {
#[allow(non_camel_case_types)]
struct PutTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
@@ -570,7 +570,7 @@ pub mod tables_data_server {
};
Box::pin(fut)
}
"/multieko2.tables_data.TablesData/DeleteTableData" => {
"/komp_ac.tables_data.TablesData/DeleteTableData" => {
#[allow(non_camel_case_types)]
struct DeleteTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
@@ -615,7 +615,7 @@ pub mod tables_data_server {
};
Box::pin(fut)
}
"/multieko2.tables_data.TablesData/GetTableData" => {
"/komp_ac.tables_data.TablesData/GetTableData" => {
#[allow(non_camel_case_types)]
struct GetTableDataSvc<T: TablesData>(pub Arc<T>);
impl<
@@ -660,7 +660,7 @@ pub mod tables_data_server {
};
Box::pin(fut)
}
"/multieko2.tables_data.TablesData/GetTableDataCount" => {
"/komp_ac.tables_data.TablesData/GetTableDataCount" => {
#[allow(non_camel_case_types)]
struct GetTableDataCountSvc<T: TablesData>(pub Arc<T>);
impl<
@@ -706,7 +706,7 @@ pub mod tables_data_server {
};
Box::pin(fut)
}
"/multieko2.tables_data.TablesData/GetTableDataByPosition" => {
"/komp_ac.tables_data.TablesData/GetTableDataByPosition" => {
#[allow(non_camel_case_types)]
struct GetTableDataByPositionSvc<T: TablesData>(pub Arc<T>);
impl<
@@ -790,7 +790,7 @@ pub mod tables_data_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.tables_data.TablesData";
pub const SERVICE_NAME: &str = "komp_ac.tables_data.TablesData";
impl<T> tonic::server::NamedService for TablesDataServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -192,13 +192,13 @@ pub mod uctovnictvo_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.uctovnictvo.Uctovnictvo/PostUctovnictvo",
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.uctovnictvo.Uctovnictvo",
"komp_ac.uctovnictvo.Uctovnictvo",
"PostUctovnictvo",
),
);
@@ -221,13 +221,13 @@ pub mod uctovnictvo_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvo",
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.uctovnictvo.Uctovnictvo",
"komp_ac.uctovnictvo.Uctovnictvo",
"GetUctovnictvo",
),
);
@@ -250,13 +250,13 @@ pub mod uctovnictvo_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoCount",
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.uctovnictvo.Uctovnictvo",
"komp_ac.uctovnictvo.Uctovnictvo",
"GetUctovnictvoCount",
),
);
@@ -279,13 +279,13 @@ pub mod uctovnictvo_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition",
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.uctovnictvo.Uctovnictvo",
"komp_ac.uctovnictvo.Uctovnictvo",
"GetUctovnictvoByPosition",
),
);
@@ -308,13 +308,13 @@ pub mod uctovnictvo_client {
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/multieko2.uctovnictvo.Uctovnictvo/PutUctovnictvo",
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"multieko2.uctovnictvo.Uctovnictvo",
"komp_ac.uctovnictvo.Uctovnictvo",
"PutUctovnictvo",
),
);
@@ -447,7 +447,7 @@ pub mod uctovnictvo_server {
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/multieko2.uctovnictvo.Uctovnictvo/PostUctovnictvo" => {
"/komp_ac.uctovnictvo.Uctovnictvo/PostUctovnictvo" => {
#[allow(non_camel_case_types)]
struct PostUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
@@ -492,7 +492,7 @@ pub mod uctovnictvo_server {
};
Box::pin(fut)
}
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvo" => {
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvo" => {
#[allow(non_camel_case_types)]
struct GetUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
@@ -537,7 +537,7 @@ pub mod uctovnictvo_server {
};
Box::pin(fut)
}
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoCount" => {
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoCount" => {
#[allow(non_camel_case_types)]
struct GetUctovnictvoCountSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
@@ -583,7 +583,7 @@ pub mod uctovnictvo_server {
};
Box::pin(fut)
}
"/multieko2.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition" => {
"/komp_ac.uctovnictvo.Uctovnictvo/GetUctovnictvoByPosition" => {
#[allow(non_camel_case_types)]
struct GetUctovnictvoByPositionSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
@@ -634,7 +634,7 @@ pub mod uctovnictvo_server {
};
Box::pin(fut)
}
"/multieko2.uctovnictvo.Uctovnictvo/PutUctovnictvo" => {
"/komp_ac.uctovnictvo.Uctovnictvo/PutUctovnictvo" => {
#[allow(non_camel_case_types)]
struct PutUctovnictvoSvc<T: Uctovnictvo>(pub Arc<T>);
impl<
@@ -714,7 +714,7 @@ pub mod uctovnictvo_server {
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "multieko2.uctovnictvo.Uctovnictvo";
pub const SERVICE_NAME: &str = "komp_ac.uctovnictvo.Uctovnictvo";
impl<T> tonic::server::NamedService for UctovnictvoServer<T> {
const NAME: &'static str = SERVICE_NAME;
}

View File

@@ -71,6 +71,8 @@ feature-depth = 1
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
{ id = "RUSTSEC-2024-0014", reason = "generational-arena is archived but no safe upgrade path exists; accepted for now" },
{ id = "RUSTSEC-2024-0436", reason = "paste is archived; no immediate alternative in dependency tree; accepted for now" },
#"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
@@ -100,6 +102,7 @@ allow = [
"HPND",
"ISC",
"LGPL-3.0",
"AGPL-3.0",
"MIT",
"MPL-2.0",
"NCSA",

View File

@@ -11,11 +11,11 @@ use tantivy::schema::{IndexRecordOption, Value};
use tantivy::{Index, TantivyDocument, Term};
use tonic::{Request, Response, Status};
use common::proto::multieko2::search::{
use common::proto::komp_ac::search::{
search_response::Hit, SearchRequest, SearchResponse,
};
pub use common::proto::multieko2::search::searcher_server::SearcherServer;
use common::proto::multieko2::search::searcher_server::Searcher;
pub use common::proto::komp_ac::search::searcher_server::SearcherServer;
use common::proto::komp_ac::search::searcher_server::Searcher;
use common::search::register_slovak_tokenizers;
use sqlx::{PgPool, Row};
use tracing::info;

View File

@@ -22,19 +22,23 @@ tonic = "0.13.0"
tonic-reflection = "0.13.0"
tracing = "0.1.41"
time = { version = "0.3.41", features = ["local-offset"] }
steel-derive = { git = "https://github.com/mattwparas/steel.git", branch = "master", package = "steel-derive" }
steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.6.0", features = ["anyhow", "dylibs", "sync"] }
thiserror = "2.0.12"
steel-derive = "0.6.0"
steel-core = { version = "0.7.0", features = ["anyhow", "dylibs", "sync"] }
dashmap = "6.1.0"
lazy_static = "1.5.0"
regex = "1.11.1"
bcrypt = "0.17.0"
validator = { version = "0.20.0", features = ["derive"] }
uuid = { version = "1.16.0", features = ["serde", "v4"] }
jsonwebtoken = "9.3.1"
rust-stemmers = "1.2.0"
rust_decimal = { version = "1.37.2", features = ["maths", "serde"] }
rust_decimal_macros = "1.37.1"
rust_decimal = { workspace = true }
rust_decimal_macros = { workspace = true }
regex = { workspace = true }
thiserror = { workspace = true }
steel-decimal = "1.0.0"
[lib]
name = "server"
@@ -46,3 +50,4 @@ rstest = "0.25.0"
lazy_static = "1.5.0"
rand = "0.9.1"
futures = "0.3.31"
tokio-test = "0.4.4"

View File

@@ -0,0 +1,44 @@
-- migrations/20250710201933_create_script_dependencies.sql
-- This table stores the dependency graph for table scripts
-- More efficient design with better indexing
CREATE TABLE script_dependencies (
id BIGSERIAL PRIMARY KEY,
-- The script that creates this dependency
script_id BIGINT NOT NULL REFERENCES table_scripts(id) ON DELETE CASCADE,
-- The table that depends on another (source of dependency)
source_table_id BIGINT NOT NULL REFERENCES table_definitions(id) ON DELETE CASCADE,
-- The table being depended upon (target of dependency)
target_table_id BIGINT NOT NULL REFERENCES table_definitions(id) ON DELETE CASCADE,
-- What type of dependency (for better debugging)
dependency_type TEXT NOT NULL CHECK (dependency_type IN ('column_access', 'sql_query', 'indexed_access')),
-- Additional context (column name, query snippet, etc.)
context_info JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Optimized indexes for fast cycle detection
CREATE INDEX idx_script_deps_source_target ON script_dependencies (source_table_id, target_table_id);
CREATE INDEX idx_script_deps_by_script ON script_dependencies (script_id);
CREATE INDEX idx_script_deps_by_target ON script_dependencies (target_table_id);
-- View for easy dependency analysis
CREATE VIEW dependency_graph AS
SELECT
sd.source_table_id,
sd.target_table_id,
st.table_name AS source_table,
tt.table_name AS target_table,
sd.dependency_type,
sd.context_info,
s.name AS schema_name
FROM script_dependencies sd
JOIN table_definitions st ON sd.source_table_id = st.id
JOIN table_definitions tt ON sd.target_table_id = tt.id
JOIN schemas s ON st.schema_id = s.id;

View File

@@ -1,7 +1,7 @@
grpcurl -plaintext -d '{
"username": "testuser2",
"email": "test2@example.com"
}' localhost:50051 multieko2.auth.AuthService/Register
}' localhost:50051 komp_ac.auth.AuthService/Register
{
"id": "5fa9bbce-85e0-4b06-8364-b561770c2fdd",
"username": "testuser2",

View File

@@ -3,7 +3,7 @@
"email": "test3@example.com",
"password": "your_password",
"password_confirmation": "your_password"
}' localhost:50051 multieko2.auth.AuthService/Register
}' localhost:50051 komp_ac.auth.AuthService/Register
{
"id": "96d2fd35-b39d-4c05-916a-66134453d34c",
"username": "testuser3",
@@ -12,14 +12,14 @@
}
grpcurl -plaintext -d '{
"identifier": "testuser3"
}' localhost:50051 multieko2.auth.AuthService/Login
}' localhost:50051 komp_ac.auth.AuthService/Login
ERROR:
Code: Unauthenticated
Message: Invalid credentials
grpcurl -plaintext -d '{
"identifier": "testuser3",
"password": "your_password"
}' localhost:50051 multieko2.auth.AuthService/Login
}' localhost:50051 komp_ac.auth.AuthService/Login
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI5NmQyZmQzNS1iMzlkLTRjMDUtOTE2YS02NjEzNDQ1M2QzNGMiLCJleHAiOjE3NDI5ODE2MTAsInJvbGUiOiJhY2NvdW50YW50In0.78VIR3X4QZohzeI5x3xmkmqcICTusOC6PELPohMV-k8",
"tokenType": "Bearer",
@@ -30,7 +30,7 @@ ERROR:
grpcurl -plaintext -d '{
"username": "testuser4",
"email": "test4@example.com"
}' localhost:50051 multieko2.auth.AuthService/Register
}' localhost:50051 komp_ac.auth.AuthService/Register
{
"id": "413d7ecc-f231-48af-8c5a-566b1dc2bf0b",
"username": "testuser4",
@@ -39,7 +39,7 @@ ERROR:
}
grpcurl -plaintext -d '{
"identifier": "test4@example.com"
}' localhost:50051 multieko2.auth.AuthService/Login
}' localhost:50051 komp_ac.auth.AuthService/Login
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MTNkN2VjYy1mMjMxLTQ4YWYtOGM1YS01NjZiMWRjMmJmMGIiLCJleHAiOjE3NDI5ODE3MDEsInJvbGUiOiJhY2NvdW50YW50In0.4Hzu3tTZRNGHnBSgeCbGy2tFTl8EzpPdXBhcW8kuIc8",
"tokenType": "Bearer",
@@ -47,5 +47,5 @@ ERROR:
"userId": "413d7ecc-f231-48af-8c5a-566b1dc2bf0b",
"role": "accountant"
}
╭─    ~/Doc/pr/multieko2/server    auth ······ ✔
╭─    ~/Doc/pr/komp_ac/server    auth ······ ✔
╰─

View File

@@ -3,7 +3,7 @@
"email": "default@example.com",
"password": "password",
"password_confirmation": "password"
}' localhost:50051 multieko2.auth.AuthService/Register
}' localhost:50051 komp_ac.auth.AuthService/Register
{
"id": "73017288-af41-49d8-b4cb-2ac2109b38a1",
"username": "testuser_default_role",
@@ -16,7 +16,7 @@
"password": "password",
"password_confirmation": "password",
"role": "admin"
}' localhost:50051 multieko2.auth.AuthService/Register
}' localhost:50051 komp_ac.auth.AuthService/Register
{
"id": "9437e318-818b-4c34-822d-c2d6601b835e",
"username": "testuser_admin_role",
@@ -29,9 +29,9 @@
"password": "password",
"password_confirmation": "password",
"role": "invalid_role_name"
}' localhost:50051 multieko2.auth.AuthService/Register
}' localhost:50051 komp_ac.auth.AuthService/Register
ERROR:
Code: InvalidArgument
Message: Invalid role specified: 'invalid_role_name'
╭─    ~/Doc/pr/multieko2/server    main +3 !1 
╭─    ~/Doc/pr/komp_ac/server    main +3 !1 
╰─

View File

@@ -3,7 +3,7 @@ use bcrypt::verify;
use tonic::{Response, Status};
use crate::db::PgPool;
use crate::auth::logic::jwt; // Fixed import path
use common::proto::multieko2::auth::{LoginRequest, LoginResponse};
use common::proto::komp_ac::auth::{LoginRequest, LoginResponse};
pub async fn login(
pool: &PgPool,

View File

@@ -1,7 +1,7 @@
// src/auth/handlers/register.rs
use bcrypt::{hash, DEFAULT_COST};
use tonic::{Response, Status};
use common::proto::multieko2::auth::{RegisterRequest, AuthResponse};
use common::proto::komp_ac::auth::{RegisterRequest, AuthResponse};
use crate::db::PgPool;
use crate::auth::models::AuthError;

View File

@@ -4,7 +4,7 @@ use tonic_reflection::server::Builder as ReflectionBuilder;
use tokio::sync::mpsc;
use crate::indexer::{indexer_task, IndexCommand};
use common::proto::multieko2::FILE_DESCRIPTOR_SET;
use common::proto::komp_ac::FILE_DESCRIPTOR_SET;
use crate::server::services::{
TableStructureHandler,
TableDefinitionService,
@@ -12,7 +12,7 @@ use crate::server::services::{
TableScriptService,
AuthServiceImpl
};
use common::proto::multieko2::{
use common::proto::komp_ac::{
table_structure::table_structure_service_server::TableStructureServiceServer,
table_definition::table_definition_server::TableDefinitionServer,
tables_data::tables_data_server::TablesDataServer,

View File

@@ -1,6 +1,6 @@
// src/server/services/auth_service.rs
use tonic::{Request, Response, Status};
use common::proto::multieko2::auth::{
use common::proto::komp_ac::auth::{
auth_service_server::AuthService,
RegisterRequest, AuthResponse,
LoginRequest, LoginResponse

View File

@@ -1,6 +1,6 @@
// src/server/services/table_definition_service.rs
use tonic::{Request, Response, Status};
use common::proto::multieko2::{
use common::proto::komp_ac::{
common::Empty,
table_definition::{
table_definition_server::TableDefinition,

View File

@@ -1,6 +1,6 @@
// src/server/services/table_script_service.rs
use tonic::{Request, Response, Status};
use common::proto::multieko2::table_script::{
use common::proto::komp_ac::table_script::{
table_script_server::TableScript,
PostTableScriptRequest, TableScriptResponse
};

View File

@@ -1,8 +1,8 @@
// src/server/services/table_structure_service.rs
use tonic::{Request, Response, Status};
// Correct the import path for the TableStructureService trait
use common::proto::multieko2::table_structure::table_structure_service_server::TableStructureService;
use common::proto::multieko2::table_structure::{
use common::proto::komp_ac::table_structure::table_structure_service_server::TableStructureService;
use common::proto::komp_ac::table_structure::{
GetTableStructureRequest,
TableStructureResponse,
};

View File

@@ -5,9 +5,9 @@ use tonic::{Request, Response, Status};
use tokio::sync::mpsc;
use crate::indexer::IndexCommand;
use common::proto::multieko2::tables_data::tables_data_server::TablesData;
use common::proto::multieko2::common::CountResponse;
use common::proto::multieko2::tables_data::{
use common::proto::komp_ac::tables_data::tables_data_server::TablesData;
use common::proto::komp_ac::common::CountResponse;
use common::proto::komp_ac::tables_data::{
PostTableDataRequest, PostTableDataResponse,
PutTableDataRequest, PutTableDataResponse,
DeleteTableDataRequest, DeleteTableDataResponse,

View File

@@ -3,7 +3,7 @@
"target_column": "idea_name",
"script": "(begin (define idea_name \"aaa\") idea_name)",
"description": "Set idea_name to Awesome Idea"
}' localhost:50051 multieko2.table_script.TableScript/PostTableScript
}' localhost:50051 komp_ac.table_script.TableScript/PostTableScript
{
"id": "7"
}
@@ -17,7 +17,7 @@
"is_feasible": "true",
"last_updated": "2025-03-11T12:34:56Z"
}
}' localhost:50051 multieko2.tables_data.TablesData/PostTableData
}' localhost:50051 komp_ac.tables_data.TablesData/PostTableData
ERROR:
Code: InvalidArgument
Message: Validation failed for 'idea_name'. Expected: 'aaa', Received: 'sdfsfs'
@@ -31,7 +31,7 @@ ERROR:
"is_feasible": "true",
"last_updated": "2025-03-11T12:34:56Z"
}
}' localhost:50051 multieko2.tables_data.TablesData/PostTableData
}' localhost:50051 komp_ac.tables_data.TablesData/PostTableData
{
"success": true,
"message": "Data inserted successfully",

View File

@@ -6,7 +6,7 @@ Creation of the tables:
{"name": "yearly_goal", "field_type": "text"}
],
"profile_name": "finance"
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
{
"success": true,
"sql": "CREATE TABLE \"2025_department\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n \"yearly_goal\" TEXT,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\n"
@@ -22,7 +22,7 @@ Creation of the tables:
"links": [
{"linked_table_name": "2025_department", "required": true}
]
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
{
"success": true,
"sql": "CREATE TABLE \"2025_project\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n \"department_id\" BIGINT NOT NULL REFERENCES \"2025_department\"(id),\n \"name\" TEXT,\n \"budget_estimate\" TEXT,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_project_department_fk ON \"2025_project\" (\"department_id\")\nCREATE INDEX idx_2025_project_name ON \"2025_project\" (\"name\")"

View File

@@ -1,190 +0,0 @@
// src/steel/server/decimal_math.rs
use rust_decimal::prelude::*;
use rust_decimal::MathematicalOps;
use steel::rvals::SteelVal;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DecimalMathError {
#[error("Invalid decimal format: {0}")]
InvalidDecimal(String),
#[error("Math operation failed: {0}")]
MathError(String),
#[error("Division by zero")]
DivisionByZero,
}
/// Converts a SteelVal to a Decimal
fn steel_val_to_decimal(val: &SteelVal) -> Result<Decimal, DecimalMathError> {
match val {
SteelVal::StringV(s) => {
Decimal::from_str(&s.to_string())
.map_err(|e| DecimalMathError::InvalidDecimal(format!("{}: {}", s, e)))
}
SteelVal::NumV(n) => {
Decimal::try_from(*n)
.map_err(|e| DecimalMathError::InvalidDecimal(format!("{}: {}", n, e)))
}
SteelVal::IntV(i) => {
Ok(Decimal::from(*i))
}
_ => Err(DecimalMathError::InvalidDecimal(format!("Unsupported type: {:?}", val)))
}
}
/// Converts a Decimal back to a SteelVal string
fn decimal_to_steel_val(decimal: Decimal) -> SteelVal {
SteelVal::StringV(decimal.to_string().into())
}
// Basic arithmetic operations
pub fn decimal_add(a: String, b: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok((a_dec + b_dec).to_string())
}
pub fn decimal_sub(a: String, b: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok((a_dec - b_dec).to_string())
}
pub fn decimal_mul(a: String, b: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok((a_dec * b_dec).to_string())
}
pub fn decimal_div(a: String, b: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
if b_dec.is_zero() {
return Err("Division by zero".to_string());
}
Ok((a_dec / b_dec).to_string())
}
// Advanced mathematical functions (requires maths feature)
pub fn decimal_pow(base: String, exp: String) -> Result<String, String> {
let base_dec = Decimal::from_str(&base).map_err(|e| format!("Invalid decimal '{}': {}", base, e))?;
let exp_dec = Decimal::from_str(&exp).map_err(|e| format!("Invalid decimal '{}': {}", exp, e))?;
base_dec.checked_powd(exp_dec)
.map(|result| result.to_string())
.ok_or_else(|| "Power operation failed or overflowed".to_string())
}
pub fn decimal_sqrt(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
a_dec.sqrt()
.map(|result| result.to_string())
.ok_or_else(|| "Square root failed (negative number?)".to_string())
}
pub fn decimal_ln(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
a_dec.checked_ln()
.map(|result| result.to_string())
.ok_or_else(|| "Natural log failed (non-positive number?)".to_string())
}
pub fn decimal_log10(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
a_dec.checked_log10()
.map(|result| result.to_string())
.ok_or_else(|| "Log10 failed (non-positive number?)".to_string())
}
pub fn decimal_exp(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
a_dec.checked_exp()
.map(|result| result.to_string())
.ok_or_else(|| "Exponential failed or overflowed".to_string())
}
// Trigonometric functions (input in radians)
pub fn decimal_sin(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
a_dec.checked_sin()
.map(|result| result.to_string())
.ok_or_else(|| "Sine calculation failed or overflowed".to_string())
}
pub fn decimal_cos(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
a_dec.checked_cos()
.map(|result| result.to_string())
.ok_or_else(|| "Cosine calculation failed or overflowed".to_string())
}
pub fn decimal_tan(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
a_dec.checked_tan()
.map(|result| result.to_string())
.ok_or_else(|| "Tangent calculation failed or overflowed".to_string())
}
// Comparison functions
pub fn decimal_gt(a: String, b: String) -> Result<bool, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok(a_dec > b_dec)
}
pub fn decimal_lt(a: String, b: String) -> Result<bool, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok(a_dec < b_dec)
}
pub fn decimal_eq(a: String, b: String) -> Result<bool, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok(a_dec == b_dec)
}
// Utility functions
pub fn decimal_abs(a: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
Ok(a_dec.abs().to_string())
}
pub fn decimal_round(a: String, places: i32) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
Ok(a_dec.round_dp(places as u32).to_string())
}
pub fn decimal_min(a: String, b: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok(a_dec.min(b_dec).to_string())
}
pub fn decimal_max(a: String, b: String) -> Result<String, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok(a_dec.max(b_dec).to_string())
}
pub fn decimal_gte(a: String, b: String) -> Result<bool, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok(a_dec >= b_dec)
}
pub fn decimal_lte(a: String, b: String) -> Result<bool, String> {
let a_dec = Decimal::from_str(&a).map_err(|e| format!("Invalid decimal '{}': {}", a, e))?;
let b_dec = Decimal::from_str(&b).map_err(|e| format!("Invalid decimal '{}': {}", b, e))?;
Ok(a_dec <= b_dec)
}

View File

@@ -1,20 +1,23 @@
// Updated src/steel/server/execution.rs
// src/steel/server/execution.rs
use steel::steel_vm::engine::Engine;
use steel::steel_vm::register_fn::RegisterFn;
use steel::rvals::SteelVal;
use super::functions::SteelContext;
use super::decimal_math::*;
use super::functions::{SteelContext, convert_row_data_for_steel};
use steel_decimal::registry::FunctionRegistry;
use sqlx::PgPool;
use std::sync::Arc;
use std::collections::HashMap;
use thiserror::Error;
use tracing::{debug, error};
/// Represents different types of values that can be returned from Steel script execution.
#[derive(Debug)]
pub enum Value {
Strings(Vec<String>),
Numbers(Vec<i64>),
Mixed(Vec<SteelVal>),
}
/// Errors that can occur during Steel script execution.
#[derive(Debug, Error)]
pub enum ExecutionError {
#[error("Script execution failed: {0}")]
@@ -25,155 +28,162 @@ pub enum ExecutionError {
UnsupportedType(String),
}
pub fn execute_script(
/// Creates a Steel execution context with proper boolean value conversion.
pub async fn create_steel_context_with_boolean_conversion(
current_table: String,
schema_id: i64,
schema_name: String,
mut row_data: HashMap<String, String>,
db_pool: Arc<PgPool>,
) -> Result<SteelContext, ExecutionError> {
// Convert boolean values in row_data to Steel format
convert_row_data_for_steel(&db_pool, schema_id, &current_table, &mut row_data)
.await
.map_err(|e| {
error!("Failed to convert row data for Steel: {}", e);
ExecutionError::RuntimeError(format!("Failed to convert row data: {}", e))
})?;
Ok(SteelContext {
current_table,
schema_id,
schema_name,
row_data,
db_pool,
})
}
/// Executes a Steel script with database context and type-safe result processing.
pub async fn execute_script(
script: String,
target_type: &str,
_db_pool: Arc<PgPool>, // Passed to the SteelContext
context: SteelContext,
db_pool: Arc<PgPool>,
schema_id: i64,
schema_name: String,
current_table: String,
row_data: HashMap<String, String>,
) -> Result<Value, ExecutionError> {
let mut vm = Engine::new();
// Create execution context with proper boolean value conversion
let context = create_steel_context_with_boolean_conversion(
current_table,
schema_id,
schema_name,
row_data,
db_pool.clone(),
).await?;
let context = Arc::new(context);
// Register existing Steel functions
// Register database access functions
register_steel_functions(&mut vm, context.clone());
// Register all decimal math functions
// Register decimal math operations
register_decimal_math_functions(&mut vm);
// Execute script and process results
let results = vm.compile_and_run_raw_program(script)
.map_err(|e| ExecutionError::RuntimeError(e.to_string()))?;
// Register row data as variables in the Steel VM for get-var access
let mut define_script = String::new();
// Convert results to target type
for (key, value) in &context.row_data {
// Register only bare variable names for get-var access
define_script.push_str(&format!("(define {} \"{}\")\n", key, value));
}
// Execute variable definitions if any exist
if !define_script.is_empty() {
vm.compile_and_run_raw_program(define_script)
.map_err(|e| ExecutionError::RuntimeError(format!("Failed to register variables: {}", e)))?;
}
// Also register variables using the decimal registry as backup method
FunctionRegistry::register_variables(&mut vm, context.row_data.clone());
// Execute the main script
let results = vm.compile_and_run_raw_program(script.clone())
.map_err(|e| {
error!("Steel script execution failed: {}", e);
error!("Script was: {}", script);
error!("Available variables were: {:?}", context.row_data);
ExecutionError::RuntimeError(e.to_string())
})?;
// Convert results to the requested target type
match target_type {
"STRINGS" => process_string_results(results),
_ => Err(ExecutionError::UnsupportedType(target_type.into()))
}
}
/// Registers Steel functions for database access within the VM context.
fn register_steel_functions(vm: &mut Engine, context: Arc<SteelContext>) {
// Register steel_get_column with row context
debug!("Registering Steel functions with context");
// Register column access function for current and related tables
vm.register_fn("steel_get_column", {
let ctx = context.clone();
move |table: String, column: String| {
debug!("steel_get_column called with table: '{}', column: '{}'", table, column);
ctx.steel_get_column(&table, &column)
.map_err(|e| e.to_string())
.map_err(|e| {
error!("steel_get_column failed: {:?}", e);
e.to_string()
})
}
});
// Register steel_get_column_with_index
// Register indexed column access for comma-separated values
vm.register_fn("steel_get_column_with_index", {
let ctx = context.clone();
move |table: String, index: i64, column: String| {
debug!("steel_get_column_with_index called with table: '{}', index: {}, column: '{}'", table, index, column);
ctx.steel_get_column_with_index(&table, index, &column)
.map_err(|e| e.to_string())
.map_err(|e| {
error!("steel_get_column_with_index failed: {:?}", e);
e.to_string()
})
}
});
// SQL query registration
// Register safe SQL query execution
vm.register_fn("steel_query_sql", {
let ctx = context.clone();
move |query: String| {
debug!("steel_query_sql called with query: '{}'", query);
ctx.steel_query_sql(&query)
.map_err(|e| e.to_string())
.map_err(|e| {
error!("steel_query_sql failed: {:?}", e);
e.to_string()
})
}
});
}
/// Registers decimal mathematics functions in the Steel VM.
fn register_decimal_math_functions(vm: &mut Engine) {
// Basic arithmetic operations
vm.register_fn("decimal-add", decimal_add);
vm.register_fn("decimal-sub", decimal_sub);
vm.register_fn("decimal-mul", decimal_mul);
vm.register_fn("decimal-div", decimal_div);
// Advanced mathematical functions
vm.register_fn("decimal-pow", decimal_pow);
vm.register_fn("decimal-sqrt", decimal_sqrt);
vm.register_fn("decimal-ln", decimal_ln);
vm.register_fn("decimal-log10", decimal_log10);
vm.register_fn("decimal-exp", decimal_exp);
// Trigonometric functions
vm.register_fn("decimal-sin", decimal_sin);
vm.register_fn("decimal-cos", decimal_cos);
vm.register_fn("decimal-tan", decimal_tan);
// Comparison functions
vm.register_fn("decimal-gt", decimal_gt);
vm.register_fn("decimal-lt", decimal_lt);
vm.register_fn("decimal-eq", decimal_eq);
// Utility functions
vm.register_fn("decimal-abs", decimal_abs);
vm.register_fn("decimal-round", decimal_round);
vm.register_fn("decimal-min", decimal_min);
vm.register_fn("decimal-max", decimal_max);
// Additional convenience functions
vm.register_fn("decimal-zero", || "0".to_string());
vm.register_fn("decimal-one", || "1".to_string());
vm.register_fn("decimal-pi", || "3.1415926535897932384626433833".to_string());
vm.register_fn("decimal-e", || "2.7182818284590452353602874714".to_string());
// Type conversion helpers
vm.register_fn("to-decimal", |s: String| -> Result<String, String> {
use rust_decimal::prelude::*;
use std::str::FromStr;
Decimal::from_str(&s)
.map(|d| d.to_string())
.map_err(|e| format!("Invalid decimal: {}", e))
});
// Financial functions
vm.register_fn("decimal-percentage", |amount: String, percentage: String| -> Result<String, String> {
use rust_decimal::prelude::*;
use std::str::FromStr;
let amount_dec = Decimal::from_str(&amount)
.map_err(|e| format!("Invalid amount: {}", e))?;
let percentage_dec = Decimal::from_str(&percentage)
.map_err(|e| format!("Invalid percentage: {}", e))?;
let hundred = Decimal::from(100);
Ok((amount_dec * percentage_dec / hundred).to_string())
});
vm.register_fn("decimal-compound", |principal: String, rate: String, time: String| -> Result<String, String> {
use rust_decimal::prelude::*;
use rust_decimal::MathematicalOps;
use std::str::FromStr;
let principal_dec = Decimal::from_str(&principal)
.map_err(|e| format!("Invalid principal: {}", e))?;
let rate_dec = Decimal::from_str(&rate)
.map_err(|e| format!("Invalid rate: {}", e))?;
let time_dec = Decimal::from_str(&time)
.map_err(|e| format!("Invalid time: {}", e))?;
let one = Decimal::ONE;
let compound_factor = (one + rate_dec).checked_powd(time_dec)
.ok_or("Compound calculation overflow")?;
Ok((principal_dec * compound_factor).to_string())
});
debug!("Registering decimal math functions");
FunctionRegistry::register_all(vm);
}
/// Processes Steel script results into string format for consistent output.
fn process_string_results(results: Vec<SteelVal>) -> Result<Value, ExecutionError> {
let mut strings = Vec::new();
for result in results {
match result {
SteelVal::StringV(s) => strings.push(s.to_string()),
SteelVal::NumV(n) => strings.push(n.to_string()),
SteelVal::IntV(i) => strings.push(i.to_string()),
SteelVal::BoolV(b) => strings.push(b.to_string()),
let result_str = match result {
SteelVal::StringV(s) => s.to_string(),
SteelVal::NumV(n) => n.to_string(),
SteelVal::IntV(i) => i.to_string(),
SteelVal::BoolV(b) => b.to_string(),
_ => {
error!("Unexpected result type: {:?}", result);
return Err(ExecutionError::TypeConversionError(
format!("Expected string-convertible type, got {:?}", result)
));
}
}
};
strings.push(result_str);
}
Ok(Value::Strings(strings))
}

View File

@@ -1,4 +1,5 @@
// src/steel/server/functions.rs
use steel::rvals::SteelVal;
use sqlx::PgPool;
use std::collections::HashMap;
@@ -16,8 +17,14 @@ pub enum FunctionError {
TableNotFound(String),
#[error("Database error: {0}")]
DatabaseError(String),
#[error("Prohibited data type access: {0}")]
ProhibitedTypeAccess(String),
}
/// Data types that Steel scripts are prohibited from accessing for security reasons
const PROHIBITED_TYPES: &[&str] = &["BIGINT", "DATE", "TIMESTAMPTZ"];
/// Execution context for Steel scripts with database access capabilities.
#[derive(Clone)]
pub struct SteelContext {
pub current_table: String,
@@ -28,6 +35,8 @@ pub struct SteelContext {
}
impl SteelContext {
/// Resolves a base table name to its full qualified name in the current schema.
/// Used for foreign key relationship traversal in Steel scripts.
pub async fn get_related_table_name(&self, base_name: &str) -> Result<String, FunctionError> {
let table_def = sqlx::query!(
r#"SELECT table_name FROM table_definitions
@@ -43,13 +52,108 @@ impl SteelContext {
Ok(table_def.table_name)
}
/// Retrieves the SQL data type for a specific column in a table.
/// Parses the JSON column definitions to find type information.
async fn get_column_type(&self, table_name: &str, column_name: &str) -> Result<String, FunctionError> {
let table_def = sqlx::query!(
r#"SELECT columns FROM table_definitions
WHERE schema_id = $1 AND table_name = $2"#,
self.schema_id,
table_name
)
.fetch_optional(&*self.db_pool)
.await
.map_err(|e| FunctionError::DatabaseError(e.to_string()))?
.ok_or_else(|| FunctionError::TableNotFound(table_name.to_string()))?;
let columns: Vec<String> = serde_json::from_value(table_def.columns)
.map_err(|e| FunctionError::DatabaseError(format!("Invalid column data: {}", e)))?;
// Parse column definitions to find the requested column type
for column_def in columns {
let mut parts = column_def.split_whitespace();
if let (Some(name), Some(data_type)) = (parts.next(), parts.next()) {
let column_name_clean = name.trim_matches('"');
if column_name_clean == column_name {
return Ok(data_type.to_string());
}
}
}
Err(FunctionError::ColumnNotFound(format!(
"Column '{}' not found in table '{}'",
column_name,
table_name
)))
}
/// Converts database values to Steel script format based on column type.
/// Currently handles boolean conversion to Steel's #true/#false syntax.
fn convert_value_to_steel_format(&self, value: &str, column_type: &str) -> String {
let normalized_type = normalize_data_type(column_type);
match normalized_type.as_str() {
"BOOLEAN" | "BOOL" => {
// Convert database boolean representations to Steel boolean syntax
match value.to_lowercase().as_str() {
"true" | "t" | "1" | "yes" | "on" => "#true".to_string(),
"false" | "f" | "0" | "no" | "off" => "#false".to_string(),
_ => value.to_string(), // Return as-is if not a recognized boolean
}
}
"INTEGER" => value.to_string(),
_ => value.to_string(), // Return as-is for other types
}
}
/// Validates that a column type is allowed for Steel script access.
/// Returns the column type if validation passes, error if prohibited.
async fn validate_column_type_and_get_type(&self, table_name: &str, column_name: &str) -> Result<String, FunctionError> {
let column_type = self.get_column_type(table_name, column_name).await?;
if is_prohibited_type(&column_type) {
return Err(FunctionError::ProhibitedTypeAccess(format!(
"Cannot access column '{}' in table '{}' because it has prohibited type '{}'. Steel scripts cannot access columns of type: {}",
column_name,
table_name,
column_type,
PROHIBITED_TYPES.join(", ")
)));
}
Ok(column_type)
}
/// Retrieves column value from current table or related tables via foreign keys.
///
/// # Behavior
/// - Current table: Returns value directly from row_data with type conversion
/// - Related table: Follows foreign key relationship and queries database
/// - All accesses are subject to prohibited type validation
pub fn steel_get_column(&self, table: &str, column: &str) -> Result<SteelVal, SteelVal> {
if table == self.current_table {
// Access current table data with type validation
let column_type = tokio::task::block_in_place(|| {
let handle = tokio::runtime::Handle::current();
handle.block_on(async {
self.validate_column_type_and_get_type(table, column).await
})
});
let column_type = match column_type {
Ok(ct) => ct,
Err(e) => return Err(SteelVal::StringV(e.to_string().into())),
};
return self.row_data.get(column)
.map(|v| SteelVal::StringV(v.clone().into()))
.map(|v| {
let converted_value = self.convert_value_to_steel_format(v, &column_type);
SteelVal::StringV(converted_value.into())
})
.ok_or_else(|| SteelVal::StringV(format!("Column {} not found", column).into()));
}
// Access related table via foreign key relationship
let base_name = table.split_once('_')
.map(|(_, rest)| rest)
.unwrap_or(table);
@@ -58,60 +162,103 @@ impl SteelContext {
let fk_value = self.row_data.get(&fk_column)
.ok_or_else(|| SteelVal::StringV(format!("Foreign key {} not found", fk_column).into()))?;
// Use `tokio::task::block_in_place` to safely block the thread
let result = tokio::task::block_in_place(|| {
let handle = tokio::runtime::Handle::current();
handle.block_on(async {
let actual_table = self.get_related_table_name(base_name).await
.map_err(|e| SteelVal::StringV(e.to_string().into()))?;
// Add quotes around the table name
sqlx::query_scalar::<_, String>(
// Validate column type and get type information
let column_type = self.validate_column_type_and_get_type(&actual_table, column).await
.map_err(|e| SteelVal::StringV(e.to_string().into()))?;
// Query the related table for the column value
let raw_value = sqlx::query_scalar::<_, String>(
&format!("SELECT {} FROM \"{}\".\"{}\" WHERE id = $1", column, self.schema_name, actual_table)
)
.bind(fk_value.parse::<i64>().map_err(|_|
SteelVal::StringV("Invalid foreign key format".into()))?)
.fetch_one(&*self.db_pool)
.await
.map_err(|e| SteelVal::StringV(e.to_string().into()))
.map_err(|e| SteelVal::StringV(e.to_string().into()))?;
// Convert to appropriate Steel format
let converted_value = self.convert_value_to_steel_format(&raw_value, &column_type);
Ok(converted_value)
})
});
result.map(|v| SteelVal::StringV(v.into()))
}
/// Retrieves a specific indexed element from a comma-separated column value.
/// Useful for accessing elements from array-like string representations.
pub fn steel_get_column_with_index(
&self,
table: &str,
index: i64,
column: &str
) -> Result<SteelVal, SteelVal> {
// Get the full value with proper type conversion
let value = self.steel_get_column(table, column)?;
if let SteelVal::StringV(s) = value {
let parts: Vec<_> = s.split(',').collect();
parts.get(index as usize)
.map(|v| SteelVal::StringV(v.trim().into()))
.ok_or_else(|| SteelVal::StringV("Index out of bounds".into()))
if let Some(part) = parts.get(index as usize) {
let trimmed_part = part.trim();
// Apply type conversion to the indexed part based on original column type
let column_type = tokio::task::block_in_place(|| {
let handle = tokio::runtime::Handle::current();
handle.block_on(async {
self.get_column_type(table, column).await
})
});
match column_type {
Ok(ct) => {
let converted_part = self.convert_value_to_steel_format(trimmed_part, &ct);
Ok(SteelVal::StringV(converted_part.into()))
}
Err(_) => {
// If type cannot be determined, return value as-is
Ok(SteelVal::StringV(trimmed_part.into()))
}
}
} else {
Err(SteelVal::StringV("Index out of bounds".into()))
}
} else {
Err(SteelVal::StringV("Expected comma-separated string".into()))
}
}
/// Executes read-only SQL queries from Steel scripts with safety restrictions.
///
/// # Security Features
/// - Only SELECT, SHOW, and EXPLAIN queries allowed
/// - Prohibited column type access validation
/// - Returns first column of all rows as comma-separated string
pub fn steel_query_sql(&self, query: &str) -> Result<SteelVal, SteelVal> {
// Validate query is read-only
if !is_read_only_query(query) {
return Err(SteelVal::StringV(
"Only SELECT queries are allowed".into()
));
}
if contains_prohibited_column_access(query) {
return Err(SteelVal::StringV(format!(
"SQL query may access prohibited column types. Steel scripts cannot access columns of type: {}",
PROHIBITED_TYPES.join(", ")
).into()));
}
let pool = self.db_pool.clone();
// Use `tokio::task::block_in_place` to safely block the thread
let result = tokio::task::block_in_place(|| {
let handle = tokio::runtime::Handle::current();
handle.block_on(async {
// Execute and get first column of all rows as strings
let rows = sqlx::query(query)
.fetch_all(&*pool)
.await
@@ -132,9 +279,85 @@ impl SteelContext {
}
}
/// Checks if a data type is prohibited for Steel script access.
fn is_prohibited_type(data_type: &str) -> bool {
let normalized_type = normalize_data_type(data_type);
PROHIBITED_TYPES.iter().any(|&prohibited| normalized_type.starts_with(prohibited))
}
/// Normalizes data type strings for consistent comparison.
/// Handles variations like NUMERIC(10,2) by extracting base type.
fn normalize_data_type(data_type: &str) -> String {
data_type.to_uppercase()
.split('(') // Remove precision/scale from NUMERIC(x,y)
.next()
.unwrap_or(data_type)
.trim()
.to_string()
}
/// Performs basic heuristic check for prohibited column type access in SQL queries.
/// Looks for common patterns that might indicate access to restricted types.
fn contains_prohibited_column_access(query: &str) -> bool {
let query_upper = query.to_uppercase();
let patterns = [
"EXTRACT(", // Common with DATE/TIMESTAMPTZ
"DATE_PART(", // Common with DATE/TIMESTAMPTZ
"::DATE",
"::TIMESTAMPTZ",
"::BIGINT",
];
patterns.iter().any(|pattern| query_upper.contains(pattern))
}
/// Validates that a query is read-only and safe for Steel script execution.
fn is_read_only_query(query: &str) -> bool {
let query = query.trim_start().to_uppercase();
query.starts_with("SELECT") ||
query.starts_with("SHOW") ||
query.starts_with("EXPLAIN")
}
/// Converts row data boolean values to Steel script format during context initialization.
pub async fn convert_row_data_for_steel(
db_pool: &PgPool,
schema_id: i64,
table_name: &str,
row_data: &mut HashMap<String, String>,
) -> Result<(), sqlx::Error> {
let table_def = sqlx::query!(
r#"SELECT columns FROM table_definitions
WHERE schema_id = $1 AND table_name = $2"#,
schema_id,
table_name
)
.fetch_optional(db_pool)
.await?
.ok_or_else(|| sqlx::Error::RowNotFound)?;
// Parse column definitions to identify boolean columns for conversion
if let Ok(columns) = serde_json::from_value::<Vec<String>>(table_def.columns) {
for column_def in columns {
let mut parts = column_def.split_whitespace();
if let (Some(name), Some(data_type)) = (parts.next(), parts.next()) {
let column_name = name.trim_matches('"');
let normalized_type = normalize_data_type(data_type);
if normalized_type == "BOOLEAN" || normalized_type == "BOOL" {
if let Some(value) = row_data.get_mut(column_name) {
// Convert boolean value to Steel format
*value = match value.to_lowercase().as_str() {
"true" | "t" | "1" | "yes" | "on" => "#true".to_string(),
"false" | "f" | "0" | "no" | "off" => "#false".to_string(),
_ => value.clone(), // Keep original if not recognized
};
}
}
}
}
}
Ok(())
}

View File

@@ -1,10 +1,6 @@
// src/steel/server/mod.rs
pub mod execution;
pub mod syntax_parser;
pub mod functions;
pub mod decimal_math;
pub use execution::*;
pub use syntax_parser::*;
pub use functions::*;
pub use decimal_math::*;

View File

@@ -1,154 +0,0 @@
use regex::Regex;
use std::collections::HashSet;
pub struct SyntaxParser {
// Existing patterns for column/SQL integration
current_table_column_re: Regex,
different_table_column_re: Regex,
one_to_many_indexed_re: Regex,
sql_integration_re: Regex,
// Simple math operation replacement patterns
math_operators: Vec<(Regex, &'static str)>,
number_literal_re: Regex,
}
impl SyntaxParser {
pub fn new() -> Self {
// Define math operator replacements
let math_operators = vec![
// Basic arithmetic
(Regex::new(r"\(\s*\+\s+").unwrap(), "(decimal-add "),
(Regex::new(r"\(\s*-\s+").unwrap(), "(decimal-sub "),
(Regex::new(r"\(\s*\*\s+").unwrap(), "(decimal-mul "),
(Regex::new(r"\(\s*/\s+").unwrap(), "(decimal-div "),
// Power and advanced operations
(Regex::new(r"\(\s*\^\s+").unwrap(), "(decimal-pow "),
(Regex::new(r"\(\s*\*\*\s+").unwrap(), "(decimal-pow "),
(Regex::new(r"\(\s*pow\s+").unwrap(), "(decimal-pow "),
(Regex::new(r"\(\s*sqrt\s+").unwrap(), "(decimal-sqrt "),
// Logarithmic functions
(Regex::new(r"\(\s*ln\s+").unwrap(), "(decimal-ln "),
(Regex::new(r"\(\s*log\s+").unwrap(), "(decimal-ln "),
(Regex::new(r"\(\s*log10\s+").unwrap(), "(decimal-log10 "),
(Regex::new(r"\(\s*exp\s+").unwrap(), "(decimal-exp "),
// Trigonometric functions
(Regex::new(r"\(\s*sin\s+").unwrap(), "(decimal-sin "),
(Regex::new(r"\(\s*cos\s+").unwrap(), "(decimal-cos "),
(Regex::new(r"\(\s*tan\s+").unwrap(), "(decimal-tan "),
// Comparison operators
(Regex::new(r"\(\s*>\s+").unwrap(), "(decimal-gt "),
(Regex::new(r"\(\s*<\s+").unwrap(), "(decimal-lt "),
(Regex::new(r"\(\s*=\s+").unwrap(), "(decimal-eq "),
(Regex::new(r"\(\s*>=\s+").unwrap(), "(decimal-gte "),
(Regex::new(r"\(\s*<=\s+").unwrap(), "(decimal-lte "),
// Utility functions
(Regex::new(r"\(\s*abs\s+").unwrap(), "(decimal-abs "),
(Regex::new(r"\(\s*min\s+").unwrap(), "(decimal-min "),
(Regex::new(r"\(\s*max\s+").unwrap(), "(decimal-max "),
(Regex::new(r"\(\s*round\s+").unwrap(), "(decimal-round "),
];
SyntaxParser {
current_table_column_re: Regex::new(r"@(\w+)").unwrap(),
different_table_column_re: Regex::new(r"@(\w+)\.(\w+)").unwrap(),
one_to_many_indexed_re: Regex::new(r"@(\w+)\[(\d+)\]\.(\w+)").unwrap(),
sql_integration_re: Regex::new(r#"@sql\((['"])(.*?)['"]\)"#).unwrap(),
// FIXED: Match negative numbers and avoid already quoted strings
number_literal_re: Regex::new(r#"(?<!")(-?\d+\.?\d*(?:[eE][+-]?\d+)?)(?!")"#).unwrap(),
math_operators,
}
}
pub fn parse(&self, script: &str, current_table: &str) -> String {
let mut transformed = script.to_string();
// Step 1: Convert all numeric literals to strings (FIXED to handle negative numbers)
transformed = self.convert_numbers_to_strings(&transformed);
// Step 2: Replace math function calls with decimal equivalents (SIMPLIFIED)
transformed = self.replace_math_functions(&transformed);
// Step 3: Handle existing column and SQL integrations (unchanged)
transformed = self.process_column_integrations(&transformed, current_table);
transformed
}
/// Convert all unquoted numeric literals to quoted strings
fn convert_numbers_to_strings(&self, script: &str) -> String {
// This regex matches numbers that are NOT already inside quotes
self.number_literal_re.replace_all(script, |caps: &regex::Captures| {
format!("\"{}\"", &caps[1])
}).to_string()
}
/// Replace math function calls with decimal equivalents (SIMPLIFIED)
fn replace_math_functions(&self, script: &str) -> String {
let mut result = script.to_string();
// Apply all math operator replacements
for (pattern, replacement) in &self.math_operators {
result = pattern.replace_all(&result, *replacement).to_string();
}
result
}
/// Process existing column and SQL integrations (unchanged logic)
fn process_column_integrations(&self, script: &str, current_table: &str) -> String {
let mut transformed = script.to_string();
// Process indexed access first to avoid overlap with relationship matches
transformed = self.one_to_many_indexed_re.replace_all(&transformed, |caps: &regex::Captures| {
format!("(steel_get_column_with_index \"{}\" {} \"{}\")",
&caps[1], &caps[2], &caps[3])
}).to_string();
// Process relationships
transformed = self.different_table_column_re.replace_all(&transformed, |caps: &regex::Captures| {
format!("(steel_get_column \"{}\" \"{}\")", &caps[1], &caps[2])
}).to_string();
// Process basic column access
transformed = self.current_table_column_re.replace_all(&transformed, |caps: &regex::Captures| {
format!("(steel_get_column \"{}\" \"{}\")", current_table, &caps[1])
}).to_string();
// Process SQL integration
transformed = self.sql_integration_re.replace_all(&transformed, |caps: &regex::Captures| {
format!("(steel_query_sql \"{}\")", &caps[2])
}).to_string();
transformed
}
pub fn extract_dependencies(&self, script: &str, current_table: &str) -> (HashSet<String>, HashSet<String>) {
let mut tables = HashSet::new();
let mut columns = HashSet::new();
for cap in self.current_table_column_re.captures_iter(script) {
tables.insert(current_table.to_string());
columns.insert(cap[1].to_string());
}
for cap in self.different_table_column_re.captures_iter(script) {
tables.insert(cap[1].to_string());
columns.insert(cap[2].to_string());
}
for cap in self.one_to_many_indexed_re.captures_iter(script) {
tables.insert(cap[1].to_string());
columns.insert(cap[3].to_string());
}
(tables, columns)
}
}

View File

@@ -15,7 +15,7 @@
"indexes": ["company_name", "is_active"],
"profile_name": "default",
"linked_table_name": ""
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
ERROR:
Code: NotFound
Message: Linked table not found in profile
@@ -35,12 +35,12 @@ ERROR:
],
"indexes": ["company_name", "is_active"],
"profile_name": "default"
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
{
"success": true,
"sql": "CREATE TABLE \"2025_company_data1\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n firma TEXT NOT NULL,\n \"company_name\" TEXT,\n \"textfield\" TEXT,\n \"textfield2\" TEXT,\n \"textfield3\" TEXT,\n \"headquarters_psc\" TEXT,\n \"contact_phone\" VARCHAR(15),\n \"office_address\" TEXT,\n \"support_email\" VARCHAR(255),\n \"is_active\" BOOLEAN,\n \"last_updated\" TIMESTAMPTZ,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_company_data1_firma ON \"2025_company_data1\" (firma)\nCREATE INDEX idx_2025_company_data1_company_name ON \"2025_company_data1\" (\"company_name\")\nCREATE INDEX idx_2025_company_data1_is_active ON \"2025_company_data1\" (\"is_active\")"
}
grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
{
"profiles": [
{
@@ -56,12 +56,12 @@ ERROR:
grpcurl -plaintext -d '{
"profile_name": "default",
"table_name": "2025_company_data1"
}' localhost:50051 multieko2.table_definition.TableDefinition/DeleteTable
}' localhost:50051 komp_ac.table_definition.TableDefinition/DeleteTable
{
"success": true,
"message": "Table '2025_company_data1' and its definition were successfully removed"
}
grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
{}
╭─    ~/Doc/pr/multieko2/server    main ⇡1 ········· ✔
╭─    ~/Doc/pr/komp_ac/server    main ⇡1 ········· ✔
╰─

View File

@@ -1,4 +1,4 @@
grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
{
"profiles": [
{
@@ -80,12 +80,12 @@
"indexes": ["column1", "columnd"],
"profile_name": "new_profile",
"linked_table_name": "2025_multi_dependent_table3"
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
{
"success": true,
"sql": "CREATE TABLE \"2025_multi_dependent_table5\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n firma TEXT NOT NULL,\n \"multi_dependent_table3_id\" BIGINT NOT NULL REFERENCES \"2025_multi_dependent_table3\"(id),\n \"2025_column1\" TEXT,\n \"2025_columnx\" TEXT,\n \"2025_columny\" TEXT,\n \"2025_columnz\" TEXT,\n \"2025_columna\" TEXT,\n \"2025_columnb\" TEXT,\n \"2025_columnc\" TEXT,\n \"2025_columnd\" TEXT,\n \"2025_column2\" INTEGER,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_multi_dependent_table5_firma ON \"2025_multi_dependent_table5\" (firma)\nCREATE INDEX idx_2025_multi_dependent_table5_multi_dependent_table3_id ON \"2025_multi_dependent_table5\" (\"multi_dependent_table3_id\")\nCREATE INDEX idx_2025_multi_dependent_table5_2025_column1 ON \"2025_multi_dependent_table5\" (\"2025_column1\")\nCREATE INDEX idx_2025_multi_dependent_table5_2025_columnd ON \"2025_multi_dependent_table5\" (\"2025_columnd\")"
}
grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
{
"profiles": [
{

View File

@@ -1,4 +1,4 @@
grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
{
"profiles": [
{
@@ -44,12 +44,12 @@
],
"indexes": ["firma", "mesto"],
"profile_name": "default"
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition
{
"success": true,
"sql": "CREATE TABLE \"2025_adresar6\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n \"firma\" TEXT,\n \"kz\" TEXT,\n \"drc\" TEXT,\n \"ulica\" TEXT,\n \"psc\" TEXT,\n \"mesto\" TEXT,\n \"stat\" TEXT,\n \"banka\" TEXT,\n \"ucet\" TEXT,\n \"skladm\" TEXT,\n \"ico\" TEXT,\n \"kontakt\" TEXT,\n \"telefon\" VARCHAR(15),\n \"skladu\" TEXT,\n \"fax\" TEXT,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_adresar6_firma ON \"2025_adresar6\" (\"firma\")\nCREATE INDEX idx_2025_adresar6_mesto ON \"2025_adresar6\" (\"mesto\")"
}
grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
{
"profiles": [
{
@@ -154,4 +154,4 @@ grpcurl -plaintext -d '{
{"linked_table_name": "2025_shipping_provider", "required": false},
{"linked_table_name": "2025_ecom_product", "required": false}
]
}' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition
}' localhost:50051 komp_ac.table_definition.TableDefinition/PostTableDefinition

View File

@@ -1,7 +1,7 @@
// src/table_definition/handlers/delete_table.rs
use tonic::Status;
use sqlx::PgPool;
use common::proto::multieko2::table_definition::{DeleteTableRequest, DeleteTableResponse};
use common::proto::komp_ac::table_definition::{DeleteTableRequest, DeleteTableResponse};
pub async fn delete_table(
db_pool: &PgPool,

View File

@@ -1,7 +1,7 @@
// src/table_definition/handlers/get_profile_tree.rs
use tonic::{Request, Response, Status};
use sqlx::PgPool;
use common::proto::multieko2::{
use common::proto::komp_ac::{
common::Empty,
table_definition::{
ProfileTreeResponse,

View File

@@ -3,7 +3,7 @@
use tonic::Status;
use sqlx::{PgPool, Transaction, Postgres};
use serde_json::json;
use common::proto::multieko2::table_definition::{PostTableDefinitionRequest, TableDefinitionResponse};
use common::proto::komp_ac::table_definition::{PostTableDefinitionRequest, TableDefinitionResponse};
const PREDEFINED_FIELD_TYPES: &[(&str, &str)] = &[
("text", "TEXT"),

View File

@@ -4,7 +4,7 @@ This is how we can push script into the database, now its stored after a push in
"target_column": "fax",
"script": "(set! fax telefon)",
"description": "Copy telefon value to fax column"
}' localhost:50051 multieko2.table_script.TableScript/PostTableScript
}' localhost:50051 komp_ac.table_script.TableScript/PostTableScript
{
"id": "2"
}

View File

@@ -1,4 +1,8 @@
// src/table_script/handlers.rs
pub mod dependency_analyzer;
pub mod dependency_utils;
pub mod post_table_script;
pub use post_table_script::post_table_script;
pub use dependency_analyzer::{DependencyAnalyzer, DependencyError, Dependency, DependencyType};
pub use dependency_utils::*;
pub use post_table_script::*;

View File

@@ -0,0 +1,566 @@
// src/table_script/handlers/dependency_analyzer.rs
use std::collections::HashMap;
use tonic::Status;
use serde_json::{json, Value};
/// Represents the state of a node during dependency graph traversal.
#[derive(Clone, Copy, PartialEq)]
enum NodeState {
Unvisited,
Visiting, // Currently in recursion stack
Visited, // Completely processed
}
/// Represents a dependency relationship between tables in Steel scripts.
#[derive(Debug, Clone)]
pub struct Dependency {
pub target_table: String,
pub dependency_type: DependencyType,
pub context: Option<Value>,
}
/// Types of dependencies that can exist between tables in Steel scripts.
#[derive(Debug, Clone)]
pub enum DependencyType {
/// Direct column access via steel_get_column
ColumnAccess { column: String },
/// Indexed column access via steel_get_column_with_index
IndexedAccess { column: String, index: i64 },
/// Raw SQL query access via steel_query_sql
SqlQuery { query_fragment: String },
}
impl DependencyType {
/// Returns the string representation used in the database.
pub fn as_str(&self) -> &'static str {
match self {
DependencyType::ColumnAccess { .. } => "column_access",
DependencyType::IndexedAccess { .. } => "indexed_access",
DependencyType::SqlQuery { .. } => "sql_query",
}
}
/// Generates context JSON for database storage.
pub fn context_json(&self) -> Value {
match self {
DependencyType::ColumnAccess { column } => {
json!({ "column": column })
}
DependencyType::IndexedAccess { column, index } => {
json!({ "column": column, "index": index })
}
DependencyType::SqlQuery { query_fragment } => {
json!({ "query_fragment": query_fragment })
}
}
}
}
/// Errors that can occur during dependency analysis.
#[derive(Debug)]
pub enum DependencyError {
CircularDependency {
cycle_path: Vec<String>,
involving_script: String
},
InvalidTableReference {
table_name: String,
script_context: String
},
ScriptParseError {
error: String
},
DatabaseError {
error: String
},
}
impl From<DependencyError> for Status {
fn from(error: DependencyError) -> Self {
match error {
DependencyError::CircularDependency { cycle_path, involving_script } => {
Status::failed_precondition(format!(
"Circular dependency detected in script for '{}': {}",
involving_script,
cycle_path.join(" -> ")
))
}
DependencyError::InvalidTableReference { table_name, script_context } => {
Status::not_found(format!(
"Table '{}' referenced in script '{}' does not exist",
table_name, script_context
))
}
DependencyError::ScriptParseError { error } => {
Status::invalid_argument(format!("Script parsing failed: {}", error))
}
DependencyError::DatabaseError { error } => {
Status::internal(format!("Database error: {}", error))
}
}
}
}
/// Analyzes Steel scripts to extract table dependencies and validate referential integrity.
///
/// This analyzer identifies how tables reference each other through Steel function calls
/// and ensures that dependency graphs remain acyclic while respecting table link constraints.
pub struct DependencyAnalyzer {
schema_id: i64,
}
impl DependencyAnalyzer {
/// Creates a new dependency analyzer for the specified schema.
pub fn new(schema_id: i64) -> Self {
Self { schema_id }
}
/// Analyzes a Steel script to extract all table dependencies.
///
/// Uses regex patterns to identify function calls that create dependencies:
/// - `steel_get_column` calls for direct column access
/// - `steel_get_column_with_index` calls for indexed access
/// - `steel_query_sql` calls for raw SQL access
/// - `get-var` calls in transformed scripts
///
/// # Arguments
/// * `script` - The Steel script code to analyze
/// * `current_table_name` - Name of the table this script belongs to (for self-references)
///
/// # Returns
/// * `Ok(Vec<Dependency>)` - List of identified dependencies
/// * `Err(DependencyError)` - If script parsing fails
pub fn analyze_script_dependencies(&self, script: &str, current_table_name: &str) -> Result<Vec<Dependency>, DependencyError> {
let mut dependencies = Vec::new();
// Extract different types of dependencies using regex patterns
dependencies.extend(self.extract_function_calls(script)?);
dependencies.extend(self.extract_sql_dependencies(script)?);
dependencies.extend(self.extract_get_var_calls(script, current_table_name)?);
Ok(dependencies)
}
/// Extracts Steel function calls that create table dependencies.
fn extract_function_calls(&self, script: &str) -> Result<Vec<Dependency>, DependencyError> {
let mut dependencies = Vec::new();
// Pattern: (steel_get_column "table" "column")
let column_pattern = regex::Regex::new(r#"\(\s*steel_get_column\s+"([^"]+)"\s+"([^"]+)""#)
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
for caps in column_pattern.captures_iter(script) {
let table = caps[1].to_string();
let column = caps[2].to_string();
dependencies.push(Dependency {
target_table: table,
dependency_type: DependencyType::ColumnAccess { column },
context: None,
});
}
// Pattern: (steel_get_column_with_index "table" index "column")
let indexed_pattern = regex::Regex::new(r#"\(\s*steel_get_column_with_index\s+"([^"]+)"\s+(\d+)\s+"([^"]+)""#)
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
for caps in indexed_pattern.captures_iter(script) {
let table = caps[1].to_string();
let index: i64 = caps[2].parse()
.map_err(|e: std::num::ParseIntError| DependencyError::ScriptParseError { error: e.to_string() })?;
let column = caps[3].to_string();
dependencies.push(Dependency {
target_table: table,
dependency_type: DependencyType::IndexedAccess { column, index },
context: None,
});
}
Ok(dependencies)
}
/// Extracts get-var calls as dependencies for transformed scripts.
/// These represent self-references to the current table.
fn extract_get_var_calls(&self, script: &str, current_table_name: &str) -> Result<Vec<Dependency>, DependencyError> {
let mut dependencies = Vec::new();
// Pattern: (get-var "variable")
let get_var_pattern = regex::Regex::new(r#"\(get-var\s+"([^"]+)"\)"#)
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
for caps in get_var_pattern.captures_iter(script) {
let variable_name = caps[1].to_string();
dependencies.push(Dependency {
target_table: current_table_name.to_string(),
dependency_type: DependencyType::ColumnAccess { column: variable_name },
context: None,
});
}
Ok(dependencies)
}
/// Extracts table references from SQL queries in steel_query_sql calls.
fn extract_sql_dependencies(&self, script: &str) -> Result<Vec<Dependency>, DependencyError> {
let mut dependencies = Vec::new();
// Pattern: (steel_query_sql "SELECT ... FROM table ...")
let sql_pattern = regex::Regex::new(r#"\(\s*steel_query_sql\s+"([^"]+)""#)
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
for caps in sql_pattern.captures_iter(script) {
let query = caps[1].to_string();
let table_refs = self.extract_table_references_from_sql(&query)?;
for table in table_refs {
dependencies.push(Dependency {
target_table: table.clone(),
dependency_type: DependencyType::SqlQuery {
query_fragment: query.clone()
},
context: None,
});
}
}
Ok(dependencies)
}
/// Extracts table names from SQL query text using regex patterns.
/// Looks for FROM and JOIN clauses to identify table references.
fn extract_table_references_from_sql(&self, sql: &str) -> Result<Vec<String>, DependencyError> {
let mut tables = Vec::new();
// Pattern: FROM table_name or JOIN table_name
let table_pattern = regex::Regex::new(r#"(?i)\b(?:FROM|JOIN)\s+(?:"([^"]+)"|(\w+))"#)
.map_err(|e: regex::Error| DependencyError::ScriptParseError { error: e.to_string() })?;
for caps in table_pattern.captures_iter(sql) {
let table = caps.get(1)
.or_else(|| caps.get(2))
.map(|m| m.as_str().to_string());
if let Some(table_name) = table {
tables.push(table_name);
}
}
Ok(tables)
}
/// Checks for circular dependencies in the dependency graph.
///
/// This function validates that adding new dependencies won't create cycles
/// that could lead to infinite loops during script execution. Self-references
/// are explicitly allowed and filtered out from cycle detection.
///
/// # Arguments
/// * `tx` - Database transaction for querying existing dependencies
/// * `table_id` - ID of the table adding new dependencies
/// * `new_dependencies` - Dependencies to be added
///
/// # Returns
/// * `Ok(())` - No cycles detected, safe to add dependencies
/// * `Err(DependencyError)` - Cycle detected or validation failed
pub async fn check_for_cycles(
&self,
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
table_id: i64,
new_dependencies: &[Dependency],
) -> Result<(), DependencyError> {
// Validate that structured table access respects link constraints
self.validate_link_constraints(tx, table_id, new_dependencies).await?;
// Build current dependency graph excluding self-references
let current_deps = sqlx::query!(
r#"SELECT sd.source_table_id, sd.target_table_id, st.table_name as source_name, tt.table_name as target_name
FROM script_dependencies sd
JOIN table_definitions st ON sd.source_table_id = st.id
JOIN table_definitions tt ON sd.target_table_id = tt.id
WHERE st.schema_id = $1"#,
self.schema_id
)
.fetch_all(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
let mut table_names: HashMap<i64, String> = HashMap::new();
// Build adjacency list excluding self-references
for dep in current_deps {
if dep.source_table_id != dep.target_table_id {
graph.entry(dep.source_table_id).or_default().push(dep.target_table_id);
}
table_names.insert(dep.source_table_id, dep.source_name);
table_names.insert(dep.target_table_id, dep.target_name);
}
// Add new dependencies to test (excluding self-references)
for dep in new_dependencies {
let target_id = sqlx::query_scalar!(
"SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
self.schema_id,
dep.target_table
)
.fetch_optional(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?
.ok_or_else(|| DependencyError::InvalidTableReference {
table_name: dep.target_table.clone(),
script_context: format!("table_id_{}", table_id),
})?;
// Only add to cycle detection if not a self-reference
if table_id != target_id {
graph.entry(table_id).or_default().push(target_id);
}
// Ensure table names are available for error reporting
if !table_names.contains_key(&table_id) {
let source_name = sqlx::query_scalar!(
"SELECT table_name FROM table_definitions WHERE id = $1",
table_id
)
.fetch_one(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
table_names.insert(table_id, source_name);
}
}
// Detect cycles using DFS algorithm
self.detect_cycles_dfs(&graph, &table_names, table_id)?;
Ok(())
}
/// Performs depth-first search to detect cycles in the dependency graph.
fn dfs_visit(
&self,
node: i64,
states: &mut HashMap<i64, NodeState>,
graph: &HashMap<i64, Vec<i64>>,
path: &mut Vec<i64>,
table_names: &HashMap<i64, String>,
starting_table: i64,
) -> Result<(), DependencyError> {
states.insert(node, NodeState::Visiting);
path.push(node);
if let Some(neighbors) = graph.get(&node) {
for &neighbor in neighbors {
if !states.contains_key(&neighbor) {
states.insert(neighbor, NodeState::Unvisited);
}
match states.get(&neighbor).copied().unwrap_or(NodeState::Unvisited) {
NodeState::Visiting => {
// Skip self-references as they're allowed
if neighbor == node {
continue;
}
// Found a cycle - build the cycle path
let cycle_start_idx = path.iter().position(|&x| x == neighbor).unwrap_or(0);
let cycle_path: Vec<String> = path[cycle_start_idx..]
.iter()
.chain(std::iter::once(&neighbor))
.map(|&id| table_names.get(&id).cloned().unwrap_or_else(|| id.to_string()))
.collect();
// Only report as error if cycle involves multiple tables
if cycle_path.len() > 2 || (cycle_path.len() == 2 && cycle_path[0] != cycle_path[1]) {
let involving_script = table_names.get(&starting_table)
.cloned()
.unwrap_or_else(|| starting_table.to_string());
return Err(DependencyError::CircularDependency {
cycle_path,
involving_script,
});
}
}
NodeState::Unvisited => {
self.dfs_visit(neighbor, states, graph, path, table_names, starting_table)?;
}
NodeState::Visited => {
// Already processed, no cycle through this path
}
}
}
}
path.pop();
states.insert(node, NodeState::Visited);
Ok(())
}
/// Validates that structured table access respects table link constraints.
///
/// # Access Rules
/// - Self-references are always allowed (table can access its own columns)
/// - Structured access (steel_get_column functions) requires explicit table links
/// - Raw SQL access (steel_query_sql) is unrestricted
///
/// # Arguments
/// * `tx` - Database transaction for querying table links
/// * `source_table_id` - ID of the table with the script
/// * `dependencies` - Dependencies to validate
async fn validate_link_constraints(
&self,
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
source_table_id: i64,
dependencies: &[Dependency],
) -> Result<(), DependencyError> {
let current_table_name = sqlx::query_scalar!(
"SELECT table_name FROM table_definitions WHERE id = $1",
source_table_id
)
.fetch_one(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
// Get all valid linked tables for the source table
let linked_tables = sqlx::query!(
r#"SELECT td.table_name, tdl.is_required
FROM table_definition_links tdl
JOIN table_definitions td ON tdl.linked_table_id = td.id
WHERE tdl.source_table_id = $1"#,
source_table_id
)
.fetch_all(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
let mut allowed_tables: std::collections::HashSet<String> = linked_tables
.into_iter()
.map(|row| row.table_name)
.collect();
// Self-references are always allowed
allowed_tables.insert(current_table_name.clone());
// Validate each dependency
for dep in dependencies {
match &dep.dependency_type {
DependencyType::ColumnAccess { column } | DependencyType::IndexedAccess { column, .. } => {
// Allow self-references
if dep.target_table == current_table_name {
continue;
}
// Check if table is linked
if !allowed_tables.contains(&dep.target_table) {
return Err(DependencyError::InvalidTableReference {
table_name: dep.target_table.clone(),
script_context: format!(
"Table '{}' is not linked to '{}'. Add a link in the table definition to access '{}' via steel_get_column functions. Column attempted: '{}'. Note: Self-references are always allowed.",
dep.target_table,
current_table_name,
dep.target_table,
column
),
});
}
}
DependencyType::SqlQuery { .. } => {
// Raw SQL access is unrestricted
}
}
}
Ok(())
}
/// Runs DFS-based cycle detection on the dependency graph.
fn detect_cycles_dfs(
&self,
graph: &HashMap<i64, Vec<i64>>,
table_names: &HashMap<i64, String>,
starting_table: i64,
) -> Result<(), DependencyError> {
let mut states: HashMap<i64, NodeState> = HashMap::new();
// Initialize all nodes as unvisited
for &node in graph.keys() {
states.insert(node, NodeState::Unvisited);
}
// Run DFS from each unvisited node
for &node in graph.keys() {
if states.get(&node) == Some(&NodeState::Unvisited) {
let mut path = Vec::new();
if let Err(cycle_error) = self.dfs_visit(
node,
&mut states,
graph,
&mut path,
table_names,
starting_table
) {
return Err(cycle_error);
}
}
}
Ok(())
}
/// Saves dependencies to the database within an existing transaction.
///
/// This function replaces all existing dependencies for a script with the new set,
/// ensuring the database reflects the current script analysis results.
///
/// # Arguments
/// * `tx` - Database transaction for atomic updates
/// * `script_id` - ID of the script these dependencies belong to
/// * `table_id` - ID of the table containing the script
/// * `dependencies` - Dependencies to save
pub async fn save_dependencies(
&self,
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
script_id: i64,
table_id: i64,
dependencies: &[Dependency],
) -> Result<(), DependencyError> {
// Clear existing dependencies for this script
sqlx::query!("DELETE FROM script_dependencies WHERE script_id = $1", script_id)
.execute(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
// Insert new dependencies
for dep in dependencies {
let target_id = sqlx::query_scalar!(
"SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
self.schema_id,
dep.target_table
)
.fetch_optional(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?
.ok_or_else(|| DependencyError::InvalidTableReference {
table_name: dep.target_table.clone(),
script_context: format!("script_id_{}", script_id),
})?;
sqlx::query!(
r#"INSERT INTO script_dependencies
(script_id, source_table_id, target_table_id, dependency_type, context_info)
VALUES ($1, $2, $3, $4, $5)"#,
script_id,
table_id,
target_id,
dep.dependency_type.as_str(),
dep.dependency_type.context_json()
)
.execute(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
}
Ok(())
}
}

View File

@@ -0,0 +1,229 @@
// src/table_script/handlers/dependency_utils.rs
// Utility functions for dependency analysis and debugging
use sqlx::PgPool;
use serde_json::{json, Value};
use std::collections::HashMap;
/// Get a visual representation of the dependency graph for a schema
pub async fn get_dependency_graph_visualization(
db_pool: &PgPool,
schema_id: i64,
) -> Result<Value, sqlx::Error> {
let dependencies = sqlx::query!(
r#"SELECT
st.table_name as source_table,
tt.table_name as target_table,
sd.dependency_type,
sd.context_info,
COUNT(*) as dependency_count
FROM script_dependencies sd
JOIN table_definitions st ON sd.source_table_id = st.id
JOIN table_definitions tt ON sd.target_table_id = tt.id
WHERE st.schema_id = $1
GROUP BY st.table_name, tt.table_name, sd.dependency_type, sd.context_info
ORDER BY st.table_name, tt.table_name"#,
schema_id
)
.fetch_all(db_pool)
.await?;
let mut graph = HashMap::new();
for dep in dependencies {
let source_entry = graph.entry(dep.source_table.clone()).or_insert_with(|| json!({
"table": dep.source_table,
"dependencies": []
}));
if let Some(deps_array) = source_entry.get_mut("dependencies") {
if let Some(deps) = deps_array.as_array_mut() {
deps.push(json!({
"target": dep.target_table,
"type": dep.dependency_type,
"context": dep.context_info,
"count": dep.dependency_count.unwrap_or(0)
}));
}
}
}
Ok(json!({
"schema_id": schema_id,
"dependency_graph": graph.into_values().collect::<Vec<_>>()
}))
}
/// Check if a schema has any circular dependencies
pub async fn check_schema_for_cycles(
db_pool: &PgPool,
schema_id: i64,
) -> Result<Vec<String>, sqlx::Error> {
// This is a simplified cycle detection for monitoring purposes
let dependencies = sqlx::query!(
r#"SELECT sd.source_table_id, sd.target_table_id, st.table_name as source_table, tt.table_name as target_table
FROM script_dependencies sd
JOIN table_definitions st ON sd.source_table_id = st.id
JOIN table_definitions tt ON sd.target_table_id = tt.id
WHERE st.schema_id = $1"#,
schema_id
)
.fetch_all(db_pool)
.await?;
let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
let mut table_names: HashMap<i64, String> = HashMap::new();
for dep in dependencies {
graph.entry(dep.source_table_id).or_default().push(dep.target_table_id);
table_names.insert(dep.source_table_id, dep.source_table);
table_names.insert(dep.target_table_id, dep.target_table);
}
// Simple cycle detection using DFS
let mut visited = std::collections::HashSet::new();
let mut rec_stack = std::collections::HashSet::new();
let mut cycles = Vec::new();
for &node in graph.keys() {
if !visited.contains(&node) {
if let Some(cycle) = detect_cycle_dfs(
node,
&graph,
&mut visited,
&mut rec_stack,
&table_names
) {
cycles.push(cycle);
}
}
}
Ok(cycles)
}
fn detect_cycle_dfs(
node: i64,
graph: &HashMap<i64, Vec<i64>>,
visited: &mut std::collections::HashSet<i64>,
rec_stack: &mut std::collections::HashSet<i64>,
table_names: &HashMap<i64, String>,
) -> Option<String> {
visited.insert(node);
rec_stack.insert(node);
if let Some(neighbors) = graph.get(&node) {
for &neighbor in neighbors {
if !visited.contains(&neighbor) {
if let Some(cycle) = detect_cycle_dfs(neighbor, graph, visited, rec_stack, table_names) {
return Some(cycle);
}
} else if rec_stack.contains(&neighbor) {
// Found a cycle
let node_name = table_names.get(&node).cloned().unwrap_or_else(|| node.to_string());
let neighbor_name = table_names.get(&neighbor).cloned().unwrap_or_else(|| neighbor.to_string());
return Some(format!("{} -> {}", node_name, neighbor_name));
}
}
}
rec_stack.remove(&node);
None
}
/// Get processing order for tables based on dependencies
/// Tables with no dependencies come first, then tables that depend only on already-processed tables
pub async fn get_table_processing_order(
db_pool: &PgPool,
schema_id: i64,
) -> Result<Vec<String>, sqlx::Error> {
let all_tables = sqlx::query!(
"SELECT id, table_name FROM table_definitions WHERE schema_id = $1 ORDER BY table_name",
schema_id
)
.fetch_all(db_pool)
.await?;
let dependencies = sqlx::query!(
r#"SELECT source_table_id, target_table_id
FROM script_dependencies sd
JOIN table_definitions td ON sd.source_table_id = td.id
WHERE td.schema_id = $1"#,
schema_id
)
.fetch_all(db_pool)
.await?;
// Build dependency graph
let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
let mut in_degree: HashMap<i64, usize> = HashMap::new();
let mut table_names: HashMap<i64, String> = HashMap::new();
// Initialize all tables
for table in &all_tables {
in_degree.insert(table.id, 0);
table_names.insert(table.id, table.table_name.clone());
graph.insert(table.id, Vec::new());
}
// Build graph and calculate in-degrees
for dep in dependencies {
graph.entry(dep.target_table_id).or_default().push(dep.source_table_id);
*in_degree.entry(dep.source_table_id).or_insert(0) += 1;
}
// Topological sort using Kahn's algorithm
let mut queue: Vec<i64> = in_degree
.iter()
.filter(|(_, &degree)| degree == 0)
.map(|(&id, _)| id)
.collect();
let mut result = Vec::new();
while let Some(node) = queue.pop() {
result.push(table_names[&node].clone());
if let Some(neighbors) = graph.get(&node) {
for &neighbor in neighbors {
if let Some(degree) = in_degree.get_mut(&neighbor) {
*degree -= 1;
if *degree == 0 {
queue.push(neighbor);
}
}
}
}
}
// If result doesn't contain all tables, there are cycles
if result.len() != all_tables.len() {
return Err(sqlx::Error::Protocol("Circular dependencies detected - cannot determine processing order".into()));
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cycle_detection_simple() {
// Test the cycle detection logic with a simple case
let mut graph = HashMap::new();
graph.insert(1, vec![2]);
graph.insert(2, vec![1]); // 1 -> 2 -> 1 (cycle)
let mut table_names = HashMap::new();
table_names.insert(1, "table_a".to_string());
table_names.insert(2, "table_b".to_string());
let mut visited = std::collections::HashSet::new();
let mut rec_stack = std::collections::HashSet::new();
let cycle = detect_cycle_dfs(1, &graph, &mut visited, &mut rec_stack, &table_names);
assert!(cycle.is_some());
let cycle_str = cycle.unwrap();
assert!(cycle_str.contains("table_a") && cycle_str.contains("table_b"));
}
}

View File

@@ -1,14 +1,357 @@
// src/table_script/handlers/post_table_script.rs
// TODO MAKE THE SCRIPTS PUSH ONLY TO THE EMPTY FILES
use tonic::Status;
use sqlx::{PgPool, Error as SqlxError};
use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse};
use common::proto::komp_ac::table_script::{PostTableScriptRequest, TableScriptResponse};
use serde_json::Value;
use crate::steel::server::syntax_parser::SyntaxParser;
use steel_decimal::SteelDecimal;
use regex::Regex;
use std::collections::HashSet;
use std::collections::HashMap;
use crate::table_script::handlers::dependency_analyzer::DependencyAnalyzer;
const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "created_at"];
// TODO MAKE SCRIPT PUSH ONLY TO THE EMPTY TABLES
/// Validates the target column and ensures it is not a system column.
// Define prohibited data types for Steel scripts (boolean is explicitly allowed)
const PROHIBITED_TYPES: &[&str] = &["BIGINT", "DATE", "TIMESTAMPTZ"];
const MATH_PROHIBITED_TYPES: &[&str] = &["BIGINT", "TEXT", "BOOLEAN", "DATE", "TIMESTAMPTZ"];
// Math operations that Steel Decimal will transform
const MATH_OPERATIONS: &[&str] = &[
"+", "-", "*", "/", "^", "**", "pow", "sqrt",
">", "<", "=", ">=", "<=", "min", "max", "abs",
"round", "ln", "log", "log10", "exp", "sin", "cos", "tan"
];
#[derive(Debug, Clone)]
enum SExpr {
Atom(String),
List(Vec<SExpr>),
}
#[derive(Debug)]
struct Parser {
tokens: Vec<String>,
position: usize,
}
impl Parser {
fn new(script: &str) -> Self {
let tokens = Self::tokenize(script);
Self { tokens, position: 0 }
}
fn tokenize(script: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current_token = String::new();
let mut in_string = false;
let mut escape_next = false;
for ch in script.chars() {
if escape_next {
current_token.push(ch);
escape_next = false;
continue;
}
match ch {
'\\' if in_string => {
escape_next = true;
current_token.push(ch);
}
'"' => {
current_token.push(ch);
if in_string {
// End of string - push the complete string token
tokens.push(current_token.clone());
current_token.clear();
}
in_string = !in_string;
}
'(' | ')' if !in_string => {
if !current_token.is_empty() {
tokens.push(current_token.clone());
current_token.clear();
}
tokens.push(ch.to_string());
}
ch if ch.is_whitespace() && !in_string => {
if !current_token.is_empty() {
tokens.push(current_token.clone());
current_token.clear();
}
}
_ => {
current_token.push(ch);
}
}
}
if !current_token.is_empty() {
tokens.push(current_token);
}
tokens
}
fn parse(&mut self) -> Result<Vec<SExpr>, String> {
let mut expressions = Vec::new();
while self.position < self.tokens.len() {
expressions.push(self.parse_expr()?);
}
Ok(expressions)
}
fn parse_expr(&mut self) -> Result<SExpr, String> {
if self.position >= self.tokens.len() {
return Err("Unexpected end of input".to_string());
}
let token = &self.tokens[self.position];
if token == "(" {
self.position += 1; // consume '('
let mut elements = Vec::new();
while self.position < self.tokens.len() && self.tokens[self.position] != ")" {
elements.push(self.parse_expr()?);
}
if self.position >= self.tokens.len() {
return Err("Missing closing parenthesis".to_string());
}
self.position += 1; // consume ')'
Ok(SExpr::List(elements))
} else {
self.position += 1;
Ok(SExpr::Atom(token.clone()))
}
}
}
#[derive(Debug)]
struct MathValidator {
column_references: Vec<(String, String)>, // (table, column) pairs found in math contexts
}
impl MathValidator {
fn new() -> Self {
Self {
column_references: Vec::new(),
}
}
fn validate_expressions(&mut self, expressions: &[SExpr]) -> Result<(), String> {
for expr in expressions {
self.check_expression(expr, false)?;
}
Ok(())
}
fn check_expression(&mut self, expr: &SExpr, in_math_context: bool) -> Result<(), String> {
match expr {
SExpr::Atom(_) => Ok(()),
SExpr::List(elements) => {
if elements.is_empty() {
return Ok(());
}
// Check if this is a math operation
let is_math = if let SExpr::Atom(op) = &elements[0] {
MATH_OPERATIONS.contains(&op.as_str())
} else {
false
};
// Check if this is a column access function
if let SExpr::Atom(func) = &elements[0] {
if func == "steel_get_column" && in_math_context {
self.extract_column_reference_from_steel_get_column(elements)?;
} else if func == "steel_get_column_with_index" && in_math_context {
self.extract_column_reference_from_steel_get_column_with_index(elements)?;
}
}
// Recursively check all elements, marking math context appropriately
for element in &elements[1..] { // Skip the operator/function name
self.check_expression(element, in_math_context || is_math)?;
}
Ok(())
}
}
}
fn extract_column_reference_from_steel_get_column(&mut self, elements: &[SExpr]) -> Result<(), String> {
// (steel_get_column "table" "column")
if elements.len() >= 3 {
if let (SExpr::Atom(table), SExpr::Atom(column)) = (&elements[1], &elements[2]) {
let table_name = self.unquote_string(table)?;
let column_name = self.unquote_string(column)?;
self.column_references.push((table_name, column_name));
}
}
Ok(())
}
fn extract_column_reference_from_steel_get_column_with_index(&mut self, elements: &[SExpr]) -> Result<(), String> {
// (steel_get_column_with_index "table" index "column")
if elements.len() >= 4 {
if let (SExpr::Atom(table), SExpr::Atom(column)) = (&elements[1], &elements[3]) {
let table_name = self.unquote_string(table)?;
let column_name = self.unquote_string(column)?;
self.column_references.push((table_name, column_name));
}
}
Ok(())
}
fn unquote_string(&self, s: &str) -> Result<String, String> {
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
Ok(s[1..s.len()-1].to_string())
} else {
Err(format!("Expected quoted string, got: {}", s))
}
}
}
/// Valide script is not empty
fn validate_script_basic_syntax(script: &str) -> Result<(), Status> {
let trimmed = script.trim();
// Check for empty script
if trimmed.is_empty() {
return Err(Status::invalid_argument("Script cannot be empty"));
}
// Basic parentheses balance check
let mut paren_count = 0;
for ch in trimmed.chars() {
match ch {
'(' => paren_count += 1,
')' => {
paren_count -= 1;
if paren_count < 0 {
return Err(Status::invalid_argument("Unbalanced parentheses: closing ')' without matching opening '('"));
}
},
_ => {}
}
}
if paren_count != 0 {
return Err(Status::invalid_argument("Unbalanced parentheses: missing closing parentheses"));
}
// Check for basic S-expression structure
if !trimmed.starts_with('(') {
return Err(Status::invalid_argument("Script must start with an opening parenthesis '('"));
}
Ok(())
}
/// Parse Steel script and extract column references used in mathematical contexts
fn extract_math_column_references(script: &str) -> Result<Vec<(String, String)>, String> {
let mut parser = Parser::new(script);
let expressions = parser.parse()
.map_err(|e| format!("Parse error: {}", e))?;
let mut validator = MathValidator::new();
validator.validate_expressions(&expressions)
.map_err(|e| format!("Validation error: {}", e))?;
Ok(validator.column_references)
}
/// Validate that mathematical operations don't use TEXT or BOOLEAN columns
async fn validate_math_operations_column_types(
db_pool: &PgPool,
schema_id: i64,
script: &str,
) -> Result<(), Status> {
// Extract column references from mathematical contexts using proper S-expression parsing
let column_refs = extract_math_column_references(script)
.map_err(|e| Status::invalid_argument(format!("Script parsing failed: {}", e)))?;
if column_refs.is_empty() {
return Ok(()); // No column references in math operations
}
// Get all unique table names referenced in math operations
let table_names: HashSet<String> = column_refs.iter()
.map(|(table, _)| table.clone())
.collect();
// Fetch table definitions for all referenced tables
let table_definitions = sqlx::query!(
r#"SELECT table_name, columns FROM table_definitions
WHERE schema_id = $1 AND table_name = ANY($2)"#,
schema_id,
&table_names.into_iter().collect::<Vec<_>>()
)
.fetch_all(db_pool)
.await
.map_err(|e| Status::internal(format!("Failed to fetch table definitions: {}", e)))?;
// Build a map of table_name -> column_name -> column_type
let mut table_column_types: HashMap<String, HashMap<String, String>> = HashMap::new();
for table_def in table_definitions {
let columns: Vec<String> = serde_json::from_value(table_def.columns)
.map_err(|e| Status::internal(format!("Invalid column data for table '{}': {}", table_def.table_name, e)))?;
let mut column_types = HashMap::new();
for column_def in columns {
let mut parts = column_def.split_whitespace();
if let (Some(name), Some(data_type)) = (parts.next(), parts.next()) {
let column_name = name.trim_matches('"');
column_types.insert(column_name.to_string(), data_type.to_string());
}
}
table_column_types.insert(table_def.table_name, column_types);
}
// Check each column reference in mathematical operations
for (table_name, column_name) in column_refs {
if let Some(table_columns) = table_column_types.get(&table_name) {
if let Some(column_type) = table_columns.get(&column_name) {
let normalized_type = normalize_data_type(column_type);
// Check if this type is prohibited in math operations
if MATH_PROHIBITED_TYPES.iter().any(|&prohibited| normalized_type.starts_with(prohibited)) {
return Err(Status::invalid_argument(format!(
"Cannot use column '{}' of type '{}' from table '{}' in mathematical operations. Mathematical operations cannot use columns of type: {}",
column_name,
column_type,
table_name,
MATH_PROHIBITED_TYPES.join(", ")
)));
}
} else {
return Err(Status::invalid_argument(format!(
"Script references column '{}' in table '{}' but this column does not exist",
column_name,
table_name
)));
}
} else {
return Err(Status::invalid_argument(format!(
"Script references table '{}' in mathematical operations but this table does not exist in this schema",
table_name
)));
}
}
Ok(())
}
/// Validates the target column and ensures it is not a system column or prohibited type.
/// Returns the column type if valid.
fn validate_target_column(
table_name: &str,
@@ -35,32 +378,237 @@ fn validate_target_column(
.collect();
// Find the target column and return its type
column_info
let column_type = column_info
.iter()
.find(|(name, _)| *name == target)
.map(|(_, dt)| dt.to_string())
.ok_or_else(|| format!("Target column '{}' not defined in table '{}'", target, table_name))
.ok_or_else(|| format!("Target column '{}' not defined in table '{}'", target, table_name))?;
// Check if the target column type is prohibited
if is_prohibited_type(&column_type) {
return Err(format!(
"Cannot create script for column '{}' with type '{}'. Steel scripts cannot target columns of type: {}",
target,
column_type,
PROHIBITED_TYPES.join(", ")
));
}
// Add helpful info for boolean columns
let normalized_type = normalize_data_type(&column_type);
if normalized_type == "BOOLEAN" || normalized_type == "BOOL" {
println!("Info: Target column '{}' is boolean type. Values will be converted to Steel format (#true/#false)", target);
}
Ok(column_type)
}
/// Handles the creation of a new table script.
/// Check if a data type is prohibited for Steel scripts
/// Note: BOOLEAN/BOOL is explicitly allowed and handled with special conversion
fn is_prohibited_type(data_type: &str) -> bool {
let normalized_type = normalize_data_type(data_type);
PROHIBITED_TYPES.iter().any(|&prohibited| normalized_type.starts_with(prohibited))
}
/// Normalize data type for comparison (handle NUMERIC variations, etc.)
fn normalize_data_type(data_type: &str) -> String {
data_type.to_uppercase()
.split('(') // Remove precision/scale from NUMERIC(x,y)
.next()
.unwrap_or(data_type)
.trim()
.to_string()
}
/// Parse Steel script to extract all table/column references
fn extract_column_references_from_script(script: &str) -> Vec<(String, String)> {
let mut references = Vec::new();
// Regex patterns to match Steel function calls
let patterns = [
// (steel_get_column "table_name" "column_name")
r#"\(steel_get_column\s+"([^"]+)"\s+"([^"]+)"\)"#,
// (steel_get_column_with_index "table_name" index "column_name")
r#"\(steel_get_column_with_index\s+"([^"]+)"\s+\d+\s+"([^"]+)"\)"#,
];
for pattern in &patterns {
if let Ok(re) = Regex::new(pattern) {
for cap in re.captures_iter(script) {
if let (Some(table), Some(column)) = (cap.get(1), cap.get(2)) {
references.push((table.as_str().to_string(), column.as_str().to_string()));
}
}
}
}
// Also check for steel_get_column_with_index pattern (table, column are in different positions)
if let Ok(re) = Regex::new(r#"\(steel_get_column_with_index\s+"([^"]+)"\s+\d+\s+"([^"]+)"\)"#) {
for cap in re.captures_iter(script) {
if let (Some(table), Some(column)) = (cap.get(1), cap.get(2)) {
references.push((table.as_str().to_string(), column.as_str().to_string()));
}
}
}
references
}
/// Validate that script doesn't reference prohibited column types by checking actual DB schema
async fn validate_script_column_references(
db_pool: &PgPool,
schema_id: i64,
script: &str,
) -> Result<(), Status> {
// Extract all table/column references from the script
let references = extract_column_references_from_script(script);
if references.is_empty() {
return Ok(()); // No column references to validate
}
// Get all unique table names referenced in the script
let table_names: HashSet<String> = references.iter()
.map(|(table, _)| table.clone())
.collect();
// Fetch table definitions for all referenced tables
for table_name in table_names {
// Query the actual table definition from the database
let table_def = sqlx::query!(
r#"SELECT table_name, columns FROM table_definitions
WHERE schema_id = $1 AND table_name = $2"#,
schema_id,
table_name
)
.fetch_optional(db_pool)
.await
.map_err(|e| Status::internal(format!("Failed to fetch table definition for '{}': {}", table_name, e)))?;
if let Some(table_def) = table_def {
// Check each column reference for this table
for (ref_table, ref_column) in &references {
if ref_table == &table_name {
// Validate this specific column reference
if let Err(error_msg) = validate_referenced_column_type(&table_name, ref_column, &table_def.columns) {
return Err(Status::invalid_argument(error_msg));
}
}
}
} else {
return Err(Status::invalid_argument(format!(
"Script references table '{}' which does not exist in this schema",
table_name
)));
}
}
Ok(())
}
/// Validate that a referenced column doesn't have a prohibited type
fn validate_referenced_column_type(table_name: &str, column_name: &str, table_columns: &Value) -> Result<(), String> {
// Parse the columns JSON into a vector of strings
let columns: Vec<String> = serde_json::from_value(table_columns.clone())
.map_err(|e| format!("Invalid column data for table '{}': {}", table_name, e))?;
// Extract column names and types
let column_info: Vec<(&str, &str)> = columns
.iter()
.filter_map(|c| {
let mut parts = c.split_whitespace();
let name = parts.next()?.trim_matches('"');
let data_type = parts.next()?;
Some((name, data_type))
})
.collect();
// Find the referenced column and check its type
if let Some((_, column_type)) = column_info.iter().find(|(name, _)| *name == column_name) {
if is_prohibited_type(column_type) {
return Err(format!(
"Script references column '{}' in table '{}' which has prohibited type '{}'. Steel scripts cannot access columns of type: {}",
column_name,
table_name,
column_type,
PROHIBITED_TYPES.join(", ")
));
}
// Log info for boolean columns
let normalized_type = normalize_data_type(column_type);
if normalized_type == "BOOLEAN" || normalized_type == "BOOL" {
println!("Info: Script references boolean column '{}' in table '{}'. Values will be converted to Steel format (#true/#false)", column_name, table_name);
}
} else {
return Err(format!(
"Script references column '{}' in table '{}' but this column does not exist",
column_name,
table_name
));
}
Ok(())
}
/// Parse Steel SQL queries to check for prohibited type usage (basic heuristic)
fn validate_sql_queries_in_script(script: &str) -> Result<(), String> {
// Look for steel_query_sql calls
if let Ok(re) = Regex::new(r#"\(steel_query_sql\s+"([^"]+)"\)"#) {
for cap in re.captures_iter(script) {
if let Some(query) = cap.get(1) {
let sql = query.as_str().to_uppercase();
// Basic heuristic checks for prohibited type operations
let prohibited_patterns = [
"EXTRACT(",
"DATE_PART(",
"::DATE",
"::TIMESTAMPTZ",
"::BIGINT",
"CAST(", // Could be casting to prohibited types
];
for pattern in &prohibited_patterns {
if sql.contains(pattern) {
return Err(format!(
"Script contains SQL query with potentially prohibited type operations: '{}'. Steel scripts cannot use operations on types: {}",
query.as_str(),
PROHIBITED_TYPES.join(", ")
));
}
}
}
}
}
Ok(())
}
/// Handles the creation of a new table script with dependency validation.
pub async fn post_table_script(
db_pool: &PgPool,
request: PostTableScriptRequest,
) -> Result<TableScriptResponse, Status> {
// Basic script validation first
validate_script_basic_syntax(&request.script)?;
// Start a transaction for ALL operations - critical for atomicity
let mut tx = db_pool.begin().await
.map_err(|e| Status::internal(format!("Failed to start transaction: {}", e)))?;
// Fetch the table definition
let table_def = sqlx::query!(
r#"SELECT id, table_name, columns, schema_id
FROM table_definitions WHERE id = $1"#,
request.table_definition_id
)
.fetch_optional(db_pool)
.fetch_optional(&mut *tx)
.await
.map_err(|e| {
Status::internal(format!("Failed to fetch table definition: {}", e))
})?
.map_err(|e| Status::internal(format!("Failed to fetch table definition: {}", e)))?
.ok_or_else(|| Status::not_found("Table definition not found"))?;
// Validate the target column and get its type
// Validate the target column and get its type (includes prohibited type check)
let column_type = validate_target_column(
&table_def.table_name,
&request.target_column,
@@ -68,16 +616,46 @@ pub async fn post_table_script(
)
.map_err(|e| Status::invalid_argument(e))?;
// Parse and transform the script using the syntax parser
let parser = SyntaxParser::new();
let parsed_script = parser.parse(&request.script, &table_def.table_name);
// REORDER: Math validation FIRST so we get specific error messages for math operations
validate_math_operations_column_types(db_pool, table_def.schema_id, &request.script).await?;
// Insert the script into the database
// THEN general column validation (catches non-math prohibited access)
validate_script_column_references(db_pool, table_def.schema_id, &request.script).await?;
// Validate SQL queries in script for prohibited type operations
validate_sql_queries_in_script(&request.script)
.map_err(|e| Status::invalid_argument(e))?;
// Create dependency analyzer for this schema
let analyzer = DependencyAnalyzer::new(table_def.schema_id);
// Analyze script dependencies
let dependencies = analyzer
.analyze_script_dependencies(&request.script, &table_def.table_name)
.map_err(|e| Status::from(e))?;
// Check for circular dependencies BEFORE making any changes
// Pass the transaction to ensure we see any existing dependencies
analyzer
.check_for_cycles(&mut tx, table_def.id, &dependencies)
.await
.map_err(|e| Status::from(e))?;
// Transform the script using steel_decimal (this happens AFTER validation)
let steel_decimal = SteelDecimal::new();
let parsed_script = steel_decimal.transform(&request.script);
// Insert or update the script
let script_record = sqlx::query!(
r#"INSERT INTO table_scripts
(table_definitions_id, target_table, target_column,
target_column_type, script, description, schema_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (table_definitions_id, target_column)
DO UPDATE SET
script = EXCLUDED.script,
description = EXCLUDED.description,
target_column_type = EXCLUDED.target_column_type
RETURNING id"#,
request.table_definition_id,
table_def.table_name,
@@ -87,18 +665,83 @@ pub async fn post_table_script(
request.description,
table_def.schema_id
)
.fetch_one(db_pool)
.fetch_one(&mut *tx)
.await
.map_err(|e| match e {
SqlxError::Database(db_err) if db_err.constraint() == Some("table_scripts_table_definitions_id_target_column_key") => {
Status::already_exists("Script already exists for this column")
.map_err(|e| {
match e {
SqlxError::Database(db_err)
if db_err.constraint() == Some("table_scripts_table_definitions_id_target_column_key") => {
Status::already_exists("Script already exists for this column")
}
_ => Status::internal(format!("Failed to insert script: {}", e)),
}
_ => Status::internal(format!("Failed to insert script: {}", e)),
})?;
// Return the response with the new script ID
// Save the dependencies within the same transaction
analyzer
.save_dependencies(&mut tx, script_record.id, table_def.id, &dependencies)
.await
.map_err(|e| Status::from(e))?;
// Only now commit the entire transaction - script + dependencies together
tx.commit().await
.map_err(|e| Status::internal(format!("Failed to commit transaction: {}", e)))?;
// Generate warnings for potential issues
let warnings = generate_warnings(&dependencies, &table_def.table_name);
Ok(TableScriptResponse {
id: script_record.id,
warnings: String::new(), // No warnings for now
warnings,
})
}
/// Generate helpful warnings for script dependencies
fn generate_warnings(dependencies: &[crate::table_script::handlers::dependency_analyzer::Dependency], table_name: &str) -> String {
let mut warnings = Vec::new();
// Check for self-references
if dependencies.iter().any(|d| d.target_table == table_name) {
warnings.push("Warning: Script references its own table, which may cause issues during initial population.".to_string());
}
// Check for complex SQL queries
let sql_deps_count = dependencies.iter()
.filter(|d| matches!(d.dependency_type, crate::table_script::handlers::dependency_analyzer::DependencyType::SqlQuery { .. }))
.count();
if sql_deps_count > 0 {
warnings.push(format!(
"Warning: Script contains {} raw SQL quer{}, ensure they are read-only and reference valid tables.",
sql_deps_count,
if sql_deps_count == 1 { "y" } else { "ies" }
));
}
// Check for many dependencies
if dependencies.len() > 5 {
warnings.push(format!(
"Warning: Script depends on {} tables, which may affect processing performance.",
dependencies.len()
));
}
// Count structured access dependencies
let structured_deps_count = dependencies.iter()
.filter(|d| matches!(
d.dependency_type,
crate::table_script::handlers::dependency_analyzer::DependencyType::ColumnAccess { .. } |
crate::table_script::handlers::dependency_analyzer::DependencyType::IndexedAccess { .. }
))
.count();
if structured_deps_count > 0 {
warnings.push(format!(
"Info: Script uses {} linked table{} via steel_get_column functions.",
structured_deps_count,
if structured_deps_count == 1 { "" } else { "s" }
));
}
warnings.join(" ")
}

View File

@@ -1,3 +1,4 @@
// src/tables_data/mod.rs
// src/table_script/mod.rs
pub mod handlers;
pub use handlers::*;

View File

@@ -4,7 +4,7 @@ grpcurl -plaintext \
"table_name": "2025_customer"
}' \
localhost:50051 \
multieko2.table_structure.TableStructureService/GetTableStructure
komp_ac.table_structure.TableStructureService/GetTableStructure
{
"columns": [
{

View File

@@ -1,5 +1,5 @@
// src/table_structure/handlers/table_structure.rs
use common::proto::multieko2::table_structure::{
use common::proto::komp_ac::table_structure::{
GetTableStructureRequest, TableColumn, TableStructureResponse,
};
use sqlx::PgPool;

View File

@@ -2,7 +2,7 @@ grpcurl -plaintext -d '{
"profile_name": "default",
"table_name": "2025_adresar",
"position": 1
}' localhost:50051 multieko2.tables_data.TablesData/GetTableDataByPosition
}' localhost:50051 komp_ac.tables_data.TablesData/GetTableDataByPosition
{
"data": {
"banka": "New Banka",

View File

@@ -1,6 +1,6 @@
Valid get request:
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 2}' localhost:50051 multieko2.tables_data.TablesData/GetTableData
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 2}' localhost:50051 komp_ac.tables_data.TablesData/GetTableData
{
"data": {
@@ -26,7 +26,7 @@ grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar",
Request of a deleted data:
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 1}' localhost:50051 multieko2.tables_data.TablesData/GetTableData
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar", "id": 1}' localhost:50051 komp_ac.tables_data.TablesData/GetTableData
ERROR:
Code: NotFound

View File

@@ -1,4 +1,4 @@
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar"}' localhost:50051 multieko2.tables_data.TablesData/GetTableDataCount
grpcurl -plaintext -d '{"profile_name": "default", "table_name": "2025_adresar"}' localhost:50051 komp_ac.tables_data.TablesData/GetTableDataCount
{
"count": "1"

View File

@@ -1,4 +1,4 @@
grpcurl -plaintext -d '{}' localhost:50051 multieko2.table_definition.TableDefinition/GetProfileTree
grpcurl -plaintext -d '{}' localhost:50051 komp_ac.table_definition.TableDefinition/GetProfileTree
{
"profiles": [
{
@@ -26,7 +26,7 @@
"support_email": "support@acmecorp.com",
"is_active": "true"
}
}' localhost:50051 multieko2.tables_data.TablesData/PostTableData
}' localhost:50051 komp_ac.tables_data.TablesData/PostTableData
{
"success": true,
"message": "Data inserted successfully",

View File

@@ -14,7 +14,7 @@
"support_email": "updated-support@acmecorp.com",
"is_active": "false"
}
}' localhost:50051 multieko2.tables_data.TablesData/PutTableData
}' localhost:50051 komp_ac.tables_data.TablesData/PutTableData
{
"success": true,
"message": "Data updated successfully",
@@ -28,7 +28,7 @@
"firma": "1",
"is_active": "true"
}
}' localhost:50051 multieko2.tables_data.TablesData/PutTableData
}' localhost:50051 komp_ac.tables_data.TablesData/PutTableData
{
"success": true,
"message": "Data updated successfully",

View File

@@ -1,7 +1,7 @@
// src/tables_data/handlers/delete_table_data.rs
use tonic::Status;
use sqlx::PgPool;
use common::proto::multieko2::tables_data::{DeleteTableDataRequest, DeleteTableDataResponse};
use common::proto::komp_ac::tables_data::{DeleteTableDataRequest, DeleteTableDataResponse};
use crate::shared::schema_qualifier::qualify_table_name_for_data; // Import schema qualifier
pub async fn delete_table_data(

View File

@@ -3,7 +3,7 @@
use tonic::Status;
use sqlx::{PgPool, Row};
use std::collections::HashMap;
use common::proto::multieko2::tables_data::{GetTableDataRequest, GetTableDataResponse};
use common::proto::komp_ac::tables_data::{GetTableDataRequest, GetTableDataResponse};
use crate::shared::schema_qualifier::qualify_table_name_for_data;
pub async fn get_table_data(

View File

@@ -1,7 +1,7 @@
// src/tables_data/handlers/get_table_data_by_position.rs
use tonic::Status;
use sqlx::PgPool;
use common::proto::multieko2::tables_data::{
use common::proto::komp_ac::tables_data::{
GetTableDataByPositionRequest, GetTableDataRequest, GetTableDataResponse
};
use super::get_table_data;

View File

@@ -1,8 +1,8 @@
// src/tables_data/handlers/get_table_data_count.rs
use tonic::Status;
use sqlx::PgPool;
use common::proto::multieko2::common::CountResponse;
use common::proto::multieko2::tables_data::GetTableDataCountRequest;
use common::proto::komp_ac::common::CountResponse;
use common::proto::komp_ac::tables_data::GetTableDataCountRequest;
use crate::shared::schema_qualifier::qualify_table_name_for_data; // 1. IMPORT THE FUNCTION
pub async fn get_table_data_count(

View File

@@ -4,7 +4,7 @@ use tonic::Status;
use sqlx::{PgPool, Arguments};
use sqlx::postgres::PgArguments;
use chrono::{DateTime, Utc};
use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse};
use common::proto::komp_ac::tables_data::{PostTableDataRequest, PostTableDataResponse};
use std::collections::HashMap;
use std::sync::Arc;
use prost_types::value::Kind;
@@ -12,12 +12,18 @@ use rust_decimal::Decimal;
use std::str::FromStr;
use crate::steel::server::execution::{self, Value};
use crate::steel::server::functions::SteelContext;
use crate::indexer::{IndexCommand, IndexCommandData};
use tokio::sync::mpsc;
use tracing::error;
/// Inserts new record into a table with validation through associated scripts.
///
/// This function handles creating new records in dynamically defined tables while:
/// - Validating permissions and column existence
/// - Executing validation scripts for computed columns
/// - Performing type-safe database insertions
/// - Maintaining search index consistency
pub async fn post_table_data(
db_pool: &PgPool,
request: PostTableDataRequest,
@@ -26,6 +32,7 @@ pub async fn post_table_data(
let profile_name = request.profile_name;
let table_name = request.table_name;
// Fetch schema and table metadata
let schema = sqlx::query!(
"SELECT id FROM schemas WHERE name = $1",
profile_name
@@ -48,6 +55,7 @@ pub async fn post_table_data(
let table_def = table_def.ok_or_else(|| Status::not_found("Table not found"))?;
// Parse column definitions from JSON format
let columns_json: Vec<String> = serde_json::from_value(table_def.columns.clone())
.map_err(|e| Status::internal(format!("Column parsing error: {}", e)))?;
@@ -62,6 +70,7 @@ pub async fn post_table_data(
columns.push((name, sql_type));
}
// Build list of valid system columns (foreign keys and special columns)
let fk_columns = sqlx::query!(
r#"SELECT ltd.table_name
FROM table_definition_links tdl
@@ -80,6 +89,7 @@ pub async fn post_table_data(
let system_columns_set: std::collections::HashSet<_> = system_columns.iter().map(|s| s.as_str()).collect();
// Validate all requested columns exist and are insertable
let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect();
for key in request.data.keys() {
if !system_columns_set.contains(key.as_str()) &&
@@ -88,6 +98,7 @@ pub async fn post_table_data(
}
}
// Convert proto values to strings for script execution context
let mut string_data_for_scripts = HashMap::new();
for (key, proto_value) in &request.data {
let str_val = match &proto_value.kind {
@@ -108,6 +119,7 @@ pub async fn post_table_data(
string_data_for_scripts.insert(key.clone(), str_val);
}
// Execute validation scripts for computed columns
let scripts = sqlx::query!(
"SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1",
table_def.id
@@ -119,50 +131,66 @@ pub async fn post_table_data(
for script_record in scripts {
let target_column = script_record.target_column;
// All script target columns are required for INSERT operations
let user_value = string_data_for_scripts.get(&target_column)
.ok_or_else(|| Status::invalid_argument(
format!("Script target column '{}' is required", target_column)
))?;
let context = SteelContext {
current_table: table_name.clone(),
schema_id,
schema_name: profile_name.clone(),
row_data: string_data_for_scripts.clone(),
db_pool: Arc::new(db_pool.clone()),
};
// Execute script to calculate expected value
let script_result = execution::execute_script(
script_record.script,
"STRINGS",
Arc::new(db_pool.clone()),
context,
schema_id,
profile_name.clone(),
table_name.clone(),
string_data_for_scripts.clone(),
)
.await
.map_err(|e| Status::invalid_argument(
format!("Script execution failed for '{}': {}", target_column, e)
))?;
let Value::Strings(mut script_output) = script_result else {
return Err(Status::internal("Script must return string values"));
};
let Value::Strings(mut script_output) = script_result;
let expected_value = script_output.pop()
.ok_or_else(|| Status::internal("Script returned no values"))?;
if user_value != &expected_value {
// Perform numeric comparison for decimal values to handle equivalent representations
// (e.g., "76.5" should equal "76.50")
let user_decimal = Decimal::from_str(user_value).map_err(|_| {
Status::invalid_argument(format!(
"Invalid decimal format provided for column '{}': {}",
target_column, user_value
))
})?;
let expected_decimal = Decimal::from_str(&expected_value).map_err(|_| {
Status::internal(format!(
"Script for column '{}' produced an invalid decimal format: {}",
target_column, expected_value
))
})?;
if user_decimal != expected_decimal {
return Err(Status::invalid_argument(format!(
"Validation failed for column '{}': Expected '{}', Got '{}'",
"Validation failed for column '{}': Script calculated '{}', but user provided '{}'",
target_column, expected_value, user_value
)));
}
}
// Build parameterized INSERT query with type-safe parameter binding
let mut params = PgArguments::default();
let mut columns_list = Vec::new();
let mut placeholders = Vec::new();
let mut param_idx = 1;
for (col, proto_value) in request.data {
// Determine SQL type for proper parameter binding
let sql_type = if system_columns_set.contains(col.as_str()) {
match col.as_str() {
"deleted" => "BOOLEAN",
@@ -176,6 +204,7 @@ pub async fn post_table_data(
.ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))?
};
// Handle NULL values first
let kind = match &proto_value.kind {
None | Some(Kind::NullValue(_)) => {
match sql_type {
@@ -196,102 +225,107 @@ pub async fn post_table_data(
Some(k) => k,
};
if sql_type == "TEXT" {
if let Kind::StringValue(value) = kind {
let trimmed_value = value.trim();
// Handle typed values with validation
match sql_type {
"TEXT" => {
if let Kind::StringValue(value) = kind {
let trimmed_value = value.trim();
if trimmed_value.is_empty() {
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
} else {
if col == "telefon" && trimmed_value.len() > 15 {
return Err(Status::internal(format!("Value too long for {}", col)));
}
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
}
} else {
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
}
} else if sql_type == "BOOLEAN" {
if let Kind::BoolValue(val) = kind {
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
}
} else if sql_type == "TIMESTAMPTZ" {
if let Kind::StringValue(value) = kind {
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
}
} else if sql_type == "BIGINT" {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
// Simple universal check: try the conversion and verify it's reversible
// This handles ALL edge cases: infinity, NaN, overflow, underflow, precision loss
let as_i64 = *val as i64;
if (as_i64 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
}
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
} else if sql_type == "INTEGER" {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
// Simple universal check: try the conversion and verify it's reversible
// This handles ALL edge cases: infinity, NaN, overflow, underflow, precision loss
let as_i32 = *val as i32;
if (as_i32 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
}
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
} else if sql_type.starts_with("NUMERIC") {
// MODIFIED: This block is now stricter.
let decimal_val = match kind {
Kind::StringValue(s) => {
let trimmed = s.trim();
if trimmed.is_empty() {
None // Treat empty string as NULL
if trimmed_value.is_empty() {
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
} else {
// This is the only valid path: parse from a string.
Some(Decimal::from_str(trimmed).map_err(|_| {
Status::invalid_argument(format!(
"Invalid decimal string format for column '{}': {}",
col, s
))
})?)
// Apply business rule validation (e.g., phone number length)
if col == "telefon" && trimmed_value.len() > 15 {
return Err(Status::internal(format!("Value too long for {}", col)));
}
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
}
} else {
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
}
// CATCH-ALL: Reject NumberValue, BoolValue, etc. for NUMERIC fields.
_ => {
return Err(Status::invalid_argument(format!(
"Expected a string representation for decimal column '{}', but received a different type.",
col
)));
},
"BOOLEAN" => {
if let Kind::BoolValue(val) = kind {
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
}
};
},
"TIMESTAMPTZ" => {
if let Kind::StringValue(value) = kind {
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
}
},
"BIGINT" => {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
params.add(decimal_val).map_err(|e| {
Status::invalid_argument(format!(
"Failed to add decimal parameter for {}: {}",
col, e
))
})?;
} else {
return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type)));
// Validate conversion is reversible to catch overflow, underflow, and precision loss
let as_i64 = *val as i64;
if (as_i64 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
}
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
},
"INTEGER" => {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
// Validate conversion is reversible to catch overflow, underflow, and precision loss
let as_i32 = *val as i32;
if (as_i32 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
}
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
},
s if s.starts_with("NUMERIC") => {
// Decimal values must be provided as strings for precise parsing
let decimal_val = match kind {
Kind::StringValue(s) => {
let trimmed = s.trim();
if trimmed.is_empty() {
None // Treat empty string as NULL
} else {
Some(Decimal::from_str(trimmed).map_err(|_| {
Status::invalid_argument(format!(
"Invalid decimal string format for column '{}': {}",
col, s
))
})?)
}
}
// Reject other types to ensure precise decimal handling
_ => {
return Err(Status::invalid_argument(format!(
"Expected a string representation for decimal column '{}', but received a different type.",
col
)));
}
};
params.add(decimal_val).map_err(|e| {
Status::invalid_argument(format!(
"Failed to add decimal parameter for {}: {}",
col, e
))
})?;
},
_ => return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type))),
}
columns_list.push(format!("\"{}\"", col));
@@ -303,6 +337,7 @@ pub async fn post_table_data(
return Err(Status::invalid_argument("No valid columns to insert"));
}
// Execute the INSERT query
let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(
db_pool,
&profile_name,
@@ -324,6 +359,7 @@ pub async fn post_table_data(
let inserted_id = match result {
Ok(id) => id,
Err(e) => {
// Handle specific database errors with user-friendly messages
if let Some(db_err) = e.as_database_error() {
if db_err.code() == Some(std::borrow::Cow::Borrowed("22P02")) ||
db_err.code() == Some(std::borrow::Cow::Borrowed("22003")) {
@@ -342,6 +378,7 @@ pub async fn post_table_data(
}
};
// Queue record for search index update
let command = IndexCommand::AddOrUpdate(IndexCommandData {
table_name: table_name.clone(),
row_id: inserted_id,

View File

@@ -1,22 +1,29 @@
// src/tables_data/handlers/put_table_data.rs
use tonic::Status;
use sqlx::{PgPool, Arguments};
use sqlx::{PgPool, Arguments, Row};
use sqlx::postgres::PgArguments;
use chrono::{DateTime, Utc};
use common::proto::multieko2::tables_data::{PutTableDataRequest, PutTableDataResponse};
use std::collections::HashMap;
use common::proto::komp_ac::tables_data::{PutTableDataRequest, PutTableDataResponse};
use std::sync::Arc;
use prost_types::value::Kind;
use rust_decimal::Decimal;
use std::str::FromStr;
use std::collections::HashMap;
use crate::steel::server::execution::{self, Value};
use crate::steel::server::functions::SteelContext;
use crate::indexer::{IndexCommand, IndexCommandData};
use tokio::sync::mpsc;
use tracing::error;
/// Updates table data with validation through associated scripts.
///
/// This function handles updating records in dynamically defined tables while:
/// - Validating permissions and column existence
/// - Executing validation scripts for computed columns
/// - Performing type-safe database updates
/// - Maintaining search index consistency
pub async fn put_table_data(
db_pool: &PgPool,
request: PutTableDataRequest,
@@ -26,7 +33,6 @@ pub async fn put_table_data(
let table_name = request.table_name;
let record_id = request.id;
// An update with no fields is a no-op; we can return success early.
if request.data.is_empty() {
return Ok(PutTableDataResponse {
success: true,
@@ -35,30 +41,22 @@ pub async fn put_table_data(
});
}
// --- Start of logic copied and adapted from post_table_data ---
let schema = sqlx::query!(
"SELECT id FROM schemas WHERE name = $1",
profile_name
)
.fetch_optional(db_pool)
.await
.map_err(|e| Status::internal(format!("Profile lookup error: {}", e)))?;
let schema_id = schema.ok_or_else(|| Status::not_found("Profile not found"))?.id;
// Fetch schema and table metadata
let schema = sqlx::query!("SELECT id FROM schemas WHERE name = $1", profile_name)
.fetch_optional(db_pool).await
.map_err(|e| Status::internal(format!("Profile lookup error: {}", e)))?
.ok_or_else(|| Status::not_found("Profile not found"))?;
let schema_id = schema.id;
let table_def = sqlx::query!(
r#"SELECT id, columns FROM table_definitions
WHERE schema_id = $1 AND table_name = $2"#,
schema_id,
table_name
"SELECT id, columns FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
schema_id, table_name
)
.fetch_optional(db_pool)
.await
.map_err(|e| Status::internal(format!("Table lookup error: {}", e)))?;
let table_def = table_def.ok_or_else(|| Status::not_found("Table not found"))?;
.fetch_optional(db_pool).await
.map_err(|e| Status::internal(format!("Table lookup error: {}", e)))?
.ok_or_else(|| Status::not_found("Table not found"))?;
// Parse column definitions from JSON format
let columns_json: Vec<String> = serde_json::from_value(table_def.columns.clone())
.map_err(|e| Status::internal(format!("Column parsing error: {}", e)))?;
@@ -73,6 +71,7 @@ pub async fn put_table_data(
columns.push((name, sql_type));
}
// Build list of valid system columns (foreign keys and special columns)
let fk_columns = sqlx::query!(
r#"SELECT ltd.table_name
FROM table_definition_links tdl
@@ -85,13 +84,14 @@ pub async fn put_table_data(
.map_err(|e| Status::internal(format!("Foreign key lookup error: {}", e)))?;
let mut system_columns = vec!["deleted".to_string()];
for fk in fk_columns {
for fk in &fk_columns {
system_columns.push(format!("{}_id", fk.table_name));
}
// Validate all requested columns exist and are editable
let system_columns_set: std::collections::HashSet<_> = system_columns.iter().map(|s| s.as_str()).collect();
let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect();
for key in request.data.keys() {
if !system_columns_set.contains(key.as_str()) &&
!user_columns.contains(&&key.to_string()) {
@@ -99,77 +99,191 @@ pub async fn put_table_data(
}
}
let mut string_data_for_scripts = HashMap::new();
for (key, proto_value) in &request.data {
let str_val = match &proto_value.kind {
Some(Kind::StringValue(s)) => {
let trimmed = s.trim();
if trimmed.is_empty() {
continue;
}
trimmed.to_string()
},
Some(Kind::NumberValue(n)) => n.to_string(),
Some(Kind::BoolValue(b)) => b.to_string(),
Some(Kind::NullValue(_)) | None => continue,
Some(Kind::StructValue(_)) | Some(Kind::ListValue(_)) => {
return Err(Status::invalid_argument(format!("Unsupported type for script validation in column '{}'", key)));
}
};
string_data_for_scripts.insert(key.clone(), str_val);
// Fetch validation scripts for this table
let scripts = sqlx::query!("SELECT id, target_column, script FROM table_scripts WHERE table_definitions_id = $1", table_def.id)
.fetch_all(db_pool).await
.map_err(|e| Status::internal(format!("Failed to fetch scripts: {}", e)))?;
// Determine minimal set of columns needed for script execution context
let mut required_columns = std::collections::HashSet::new();
// Always need: id, target columns of scripts, and columns being updated
required_columns.insert("id".to_string());
for script_record in &scripts {
required_columns.insert(script_record.target_column.clone());
}
for key in request.data.keys() {
required_columns.insert(key.clone());
}
let scripts = sqlx::query!(
"SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1",
table_def.id
)
.fetch_all(db_pool)
.await
.map_err(|e| Status::internal(format!("Failed to fetch scripts: {}", e)))?;
// Add columns referenced by scripts based on pre-computed dependencies
if !scripts.is_empty() {
let script_ids: Vec<i64> = scripts.iter().map(|s| s.id).collect();
for script_record in scripts {
let target_column = script_record.target_column;
let dependencies = sqlx::query!(
r#"SELECT sd.target_table_id, sd.dependency_type, sd.context_info, td.table_name as target_table
FROM script_dependencies sd
JOIN table_definitions td ON sd.target_table_id = td.id
WHERE sd.script_id = ANY($1)"#,
&script_ids
)
.fetch_all(db_pool)
.await
.map_err(|e| Status::internal(format!("Failed to fetch script dependencies: {}", e)))?;
if let Some(user_value) = string_data_for_scripts.get(&target_column) {
let context = SteelContext {
current_table: table_name.clone(),
schema_id,
schema_name: profile_name.clone(),
row_data: string_data_for_scripts.clone(),
db_pool: Arc::new(db_pool.clone()),
};
let script_result = execution::execute_script(
script_record.script,
"STRINGS",
Arc::new(db_pool.clone()),
context,
)
.map_err(|e| Status::invalid_argument(
format!("Script execution failed for '{}': {}", target_column, e)
))?;
let Value::Strings(mut script_output) = script_result else {
return Err(Status::internal("Script must return string values"));
};
let expected_value = script_output.pop()
.ok_or_else(|| Status::internal("Script returned no values"))?;
if user_value != &expected_value {
return Err(Status::invalid_argument(format!(
"Validation failed for column '{}': Expected '{}', Got '{}'",
target_column, expected_value, user_value
)));
for dep in dependencies {
if dep.target_table == table_name {
// Script references this table - add specific columns it uses
match dep.dependency_type.as_str() {
"column_access" | "indexed_access" => {
if let Some(context) = dep.context_info {
if let Some(column) = context.get("column").and_then(|v| v.as_str()) {
required_columns.insert(column.to_string());
}
}
}
_ => {} // SQL queries handled differently
}
} else {
// Script references linked table - add foreign key column
let fk_column = format!("{}_id", dep.target_table.split('_').last().unwrap_or(&dep.target_table));
required_columns.insert(fk_column);
}
}
}
// Fetch current record state with only required columns for efficiency
let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(db_pool, &profile_name, &table_name).await?;
let columns_clause = required_columns
.iter()
.map(|name| format!("COALESCE(\"{0}\"::TEXT, '') AS \"{0}\"", name))
.collect::<Vec<_>>()
.join(", ");
let select_sql = format!("SELECT {} FROM {} WHERE id = $1", columns_clause, qualified_table);
let current_row = sqlx::query(&select_sql).bind(record_id).fetch_optional(db_pool).await
.map_err(|e| Status::internal(format!("Failed to fetch current row state: {}", e)))?
.ok_or_else(|| Status::not_found("Record not found"))?;
let mut current_row_data = HashMap::new();
for col_name in &required_columns {
let value: String = current_row.try_get(col_name.as_str()).unwrap_or_default();
current_row_data.insert(col_name.clone(), value);
}
// Extract and normalize update data from request
let mut update_data = HashMap::new();
for (key, proto_value) in &request.data {
let str_val = match &proto_value.kind {
Some(Kind::StringValue(s)) => s.trim().to_string(),
Some(Kind::NumberValue(n)) => n.to_string(),
Some(Kind::BoolValue(b)) => b.to_string(),
Some(Kind::NullValue(_)) | None => String::new(),
_ => return Err(Status::invalid_argument(format!("Unsupported type for column '{}'", key))),
};
update_data.insert(key.clone(), str_val);
}
// Create final context by merging current data with updates
let mut final_context_data = current_row_data.clone();
final_context_data.extend(update_data.clone());
// Execute and validate scripts for computed columns
for script_record in scripts {
let target_column = script_record.target_column;
// Determine SQL type for type-aware validation
let target_sql_type = if let Some((_, stype)) = columns.iter().find(|(name, _)| name == &target_column) {
stype.as_str()
} else {
"TEXT" // Default fallback for system columns
};
// Execute script with current context
let script_result = execution::execute_script(
script_record.script.clone(),
"STRINGS",
Arc::new(db_pool.clone()),
schema_id,
profile_name.clone(),
table_name.clone(),
final_context_data.clone(),
)
.await
.map_err(|e| {
error!("Script execution failed for '{}': {:?}", target_column, e);
Status::invalid_argument(format!("Script execution failed for '{}': {}", target_column, e))
})?;
let Value::Strings(mut script_output_vec) = script_result;
let script_output = script_output_vec.pop().ok_or_else(|| Status::internal("Script returned no values"))?;
if update_data.contains_key(&target_column) {
// Case A: Column is being updated - validate user input against script output
let user_value = update_data.get(&target_column).unwrap();
// Perform type-aware comparison based on SQL column type
let values_match = match target_sql_type {
s if s.starts_with("NUMERIC") => {
let user_decimal = Decimal::from_str(user_value).map_err(|_| Status::invalid_argument(format!("Invalid decimal format for column '{}'", target_column)))?;
let script_decimal = Decimal::from_str(&script_output).map_err(|_| Status::internal(format!("Script for '{}' produced invalid decimal", target_column)))?;
user_decimal == script_decimal
},
"INTEGER" | "BIGINT" => {
let user_int: i64 = user_value.parse().map_err(|_| Status::invalid_argument(format!("Invalid integer format for column '{}'", target_column)))?;
let script_int: i64 = script_output.parse().map_err(|_| Status::internal(format!("Script for '{}' produced invalid integer", target_column)))?;
user_int == script_int
},
"BOOLEAN" => {
let user_bool: bool = user_value.parse().map_err(|_| Status::invalid_argument(format!("Invalid boolean format for column '{}'", target_column)))?;
let script_bool: bool = script_output.parse().map_err(|_| Status::internal(format!("Script for '{}' produced invalid boolean", target_column)))?;
user_bool == script_bool
},
_ => user_value == &script_output
};
if !values_match {
return Err(Status::invalid_argument(format!("Validation failed for column '{}': Script calculated '{}', but user provided '{}'", target_column, script_output, user_value)));
}
} else {
// Case B: Column is NOT being updated - prevent unauthorized script-triggered changes
let current_value = current_row_data.get(&target_column).cloned().unwrap_or_default();
let values_match = match target_sql_type {
s if s.starts_with("NUMERIC") => {
let current_decimal = Decimal::from_str(&current_value).unwrap_or_default();
let script_decimal = Decimal::from_str(&script_output).unwrap_or_default();
current_decimal == script_decimal
},
"INTEGER" | "BIGINT" => {
let current_int: i64 = current_value.parse().unwrap_or_default();
let script_int: i64 = script_output.parse().unwrap_or_default();
current_int == script_int
},
"BOOLEAN" => {
let current_bool: bool = current_value.parse().unwrap_or(false);
let script_bool: bool = script_output.parse().unwrap_or(false);
current_bool == script_bool
},
_ => current_value == script_output
};
if !values_match {
return Err(Status::failed_precondition(format!("Script for column '{}' was triggered and would change its value from '{}' to '{}'. To apply this change, please include '{}' in your update request.", target_column, current_value, script_output, target_column)));
}
}
}
// Build parameterized UPDATE query with type-safe parameter binding
let mut params = PgArguments::default();
let mut set_clauses = Vec::new();
let mut param_idx = 1;
for (col, proto_value) in request.data {
// Determine SQL type for proper parameter binding
let sql_type = if system_columns_set.contains(col.as_str()) {
match col.as_str() {
"deleted" => "BOOLEAN",
@@ -183,6 +297,7 @@ pub async fn put_table_data(
.ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))?
};
// Handle NULL values first
let kind = match &proto_value.kind {
None | Some(Kind::NullValue(_)) => {
match sql_type {
@@ -202,98 +317,103 @@ pub async fn put_table_data(
Some(k) => k,
};
if sql_type == "TEXT" {
if let Kind::StringValue(value) = kind {
let trimmed_value = value.trim();
if trimmed_value.is_empty() {
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
} else {
if col == "telefon" && trimmed_value.len() > 15 {
return Err(Status::internal(format!("Value too long for {}", col)));
}
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
}
} else {
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
}
} else if sql_type == "BOOLEAN" {
if let Kind::BoolValue(val) = kind {
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
}
} else if sql_type == "TIMESTAMPTZ" {
if let Kind::StringValue(value) = kind {
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
}
} else if sql_type == "BIGINT" {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
let as_i64 = *val as i64;
if (as_i64 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
}
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
} else if sql_type == "INTEGER" {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
let as_i32 = *val as i32;
if (as_i32 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
}
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
} else if sql_type.starts_with("NUMERIC") {
let decimal_val = match kind {
Kind::StringValue(s) => {
let trimmed = s.trim();
if trimmed.is_empty() {
None
// Handle typed values with validation
match sql_type {
"TEXT" => {
if let Kind::StringValue(value) = kind {
let trimmed_value = value.trim();
if trimmed_value.is_empty() {
params.add(None::<String>).map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?;
} else {
Some(Decimal::from_str(trimmed).map_err(|_| {
Status::invalid_argument(format!(
"Invalid decimal string format for column '{}': {}",
col, s
))
})?)
// Apply business rule validation (e.g., phone number length)
if col == "telefon" && trimmed_value.len() > 15 {
return Err(Status::internal(format!("Value too long for {}", col)));
}
params.add(trimmed_value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?;
}
} else {
return Err(Status::invalid_argument(format!("Expected string for column '{}'", col)));
}
_ => {
return Err(Status::invalid_argument(format!(
"Expected a string representation for decimal column '{}', but received a different type.",
col
)));
},
"BOOLEAN" => {
if let Kind::BoolValue(val) = kind {
params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col)));
}
};
params.add(decimal_val).map_err(|e| {
Status::invalid_argument(format!(
"Failed to add decimal parameter for {}: {}",
col, e
))
})?;
} else {
return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type)));
},
"TIMESTAMPTZ" => {
if let Kind::StringValue(value) = kind {
let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col)));
}
},
"BIGINT" => {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
let as_i64 = *val as i64;
if (as_i64 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for BIGINT column '{}'", col)));
}
params.add(as_i64).map_err(|e| Status::invalid_argument(format!("Failed to add bigint parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
},
"INTEGER" => {
if let Kind::NumberValue(val) = kind {
if val.fract() != 0.0 {
return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col)));
}
let as_i32 = *val as i32;
if (as_i32 as f64) != *val {
return Err(Status::invalid_argument(format!("Integer value out of range for INTEGER column '{}'", col)));
}
params.add(as_i32).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?;
} else {
return Err(Status::invalid_argument(format!("Expected number for column '{}'", col)));
}
},
s if s.starts_with("NUMERIC") => {
let decimal_val = match kind {
Kind::StringValue(s) => {
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(Decimal::from_str(trimmed).map_err(|_| {
Status::invalid_argument(format!(
"Invalid decimal string format for column '{}': {}",
col, s
))
})?)
}
}
_ => {
return Err(Status::invalid_argument(format!(
"Expected a string representation for decimal column '{}', but received a different type.",
col
)));
}
};
params.add(decimal_val).map_err(|e| {
Status::invalid_argument(format!(
"Failed to add decimal parameter for {}: {}",
col, e
))
})?;
},
_ => return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type))),
}
set_clauses.push(format!("\"{}\" = ${}", col, param_idx));
param_idx += 1;
}
// --- End of copied logic ---
if set_clauses.is_empty() {
return Ok(PutTableDataResponse {
success: true,
@@ -302,13 +422,7 @@ pub async fn put_table_data(
});
}
let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(
db_pool,
&profile_name,
&table_name,
)
.await?;
// Execute the UPDATE query
let set_clause = set_clauses.join(", ");
let sql = format!(
"UPDATE {} SET {} WHERE id = ${} RETURNING id",
@@ -327,6 +441,7 @@ pub async fn put_table_data(
Ok(Some(id)) => id,
Ok(None) => return Err(Status::not_found("Record not found")),
Err(e) => {
// Handle specific database errors with user-friendly messages
if let Some(db_err) = e.as_database_error() {
if db_err.code() == Some(std::borrow::Cow::Borrowed("22P02")) ||
db_err.code() == Some(std::borrow::Cow::Borrowed("22003")) {
@@ -339,6 +454,7 @@ pub async fn put_table_data(
}
};
// Queue record for search index update
let command = IndexCommand::AddOrUpdate(IndexCommandData {
table_name: table_name.clone(),
row_id: updated_id,

View File

@@ -1,4 +1,6 @@
// tests/mod.rs
pub mod tables_data;
pub mod common;
pub mod table_script;
pub mod table_definition;

View File

@@ -1,7 +1,7 @@
// tests/table_definition/post_table_definition_test.rs
// Keep all your normal use statements
use common::proto::multieko2::table_definition::{
use common::proto::komp_ac::table_definition::{
ColumnDefinition, PostTableDefinitionRequest, TableLink,
};
use rstest::{fixture, rstest};

Some files were not shown because too many files have changed in this diff Show More