Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8127c7bb1b | ||
|
|
7437908baf | ||
|
|
9eb46cb5d3 | ||
|
|
38a70128b0 | ||
|
|
c58ce52b33 | ||
|
|
c82813185f | ||
|
|
a96681e9d6 | ||
|
|
4df6c40034 | ||
|
|
089d728cc7 | ||
|
|
aca3d718b5 | ||
|
|
8a6a584cf3 | ||
|
|
00ed0cf796 | ||
|
|
7e54b2fe43 | ||
|
|
84871faad4 | ||
|
|
bcb433d7b2 | ||
|
|
7d1b130b68 | ||
|
|
24c2376ea1 | ||
|
|
810ef5fc10 | ||
|
|
fe246b1fe6 | ||
|
|
de42bb48aa | ||
|
|
17495c49ac | ||
|
|
0e3a7a06a3 | ||
|
|
e0ee48eb9c | ||
|
|
d2053b1d5a | ||
|
|
fbe8e53858 | ||
|
|
8fe2581b3f | ||
|
|
60cc0e562e | ||
|
|
26898d474f | ||
|
|
2311fbaa3b | ||
|
|
be99cd9423 | ||
|
|
a3dd6fa95b | ||
|
|
433d87c96d | ||
|
|
aff4383671 | ||
|
|
b7c8f6b1a2 | ||
|
|
3443839ba4 | ||
|
|
6c31d48f3b | ||
|
|
1770292fd8 | ||
|
|
afdd5c5740 | ||
|
|
11487f0833 | ||
|
|
4d5d22d0c2 | ||
|
|
314a957922 | ||
|
|
4c57b562e6 | ||
|
|
a757acf51c | ||
|
|
f4a23be1a2 | ||
|
|
93c67ffa14 | ||
|
|
d1ebe4732f | ||
|
|
7b7f3ca05a | ||
|
|
234613f831 | ||
|
|
f6d84e70cc | ||
|
|
5cd324b6ae | ||
|
|
a7457f5749 | ||
|
|
a5afc75099 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
.env
|
||||
/tantivy_indexes
|
||||
server/tantivy_indexes
|
||||
steel_decimal/tests/property_tests.proptest-regressions
|
||||
|
||||
933
Cargo.lock
generated
933
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -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" }
|
||||
|
||||
@@ -31,3 +31,9 @@ unicode-width = "0.2.0"
|
||||
[features]
|
||||
default = []
|
||||
ui-debug = []
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.25.0"
|
||||
tokio-test = "0.4.4"
|
||||
uuid = { version = "1.17.0", features = ["v4"] }
|
||||
futures = "0.3.31"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// src/components/form/form.rs
|
||||
use crate::components::common::autocomplete; // <--- ADD THIS IMPORT
|
||||
use crate::components::common::autocomplete;
|
||||
use crate::components::handlers::canvas::render_canvas;
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::state::app::highlight::HighlightState;
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crate::state::pages::form::FormState; // <--- CHANGE THIS IMPORT
|
||||
use crate::state::pages::form::FormState;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
|
||||
style::Style,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)),
|
||||
]);
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
// 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
|
||||
GetTableDataResponse,
|
||||
DeleteTableDataRequest, // ADD THIS
|
||||
DeleteTableDataResponse, // ADD THIS
|
||||
GetTableDataCountRequest,
|
||||
PostTableDataRequest, PostTableDataResponse, PutTableDataRequest,
|
||||
PutTableDataResponse,
|
||||
};
|
||||
use common::proto::multieko2::search::{
|
||||
use common::proto::komp_ac::search::{
|
||||
searcher_client::SearcherClient, SearchRequest, SearchResponse,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
@@ -116,7 +119,7 @@ impl GrpcClient {
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
// NEW Methods for TablesData service
|
||||
// Existing TablesData methods
|
||||
pub async fn get_table_data_count(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
@@ -135,7 +138,7 @@ impl GrpcClient {
|
||||
Ok(response.into_inner().count as u64)
|
||||
}
|
||||
|
||||
pub async fn get_table_data_by_position(
|
||||
pub async fn get_table_data_by_position(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
@@ -155,18 +158,58 @@ pub async fn get_table_data_by_position(
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
// ADD THIS: Missing get_table_data method
|
||||
pub async fn get_table_data(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
id: i64,
|
||||
) -> Result<GetTableDataResponse> {
|
||||
let grpc_request = GetTableDataRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
id,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
.tables_data_client
|
||||
.get_table_data(request)
|
||||
.await
|
||||
.context("gRPC GetTableData call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
// ADD THIS: Missing delete_table_data method
|
||||
pub async fn delete_table_data(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
record_id: i64,
|
||||
) -> Result<DeleteTableDataResponse> {
|
||||
let grpc_request = DeleteTableDataRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
record_id,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
.tables_data_client
|
||||
.delete_table_data(request)
|
||||
.await
|
||||
.context("gRPC DeleteTableData call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn post_table_data(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
// CHANGE THIS: Accept the pre-converted data
|
||||
data: HashMap<String, Value>,
|
||||
) -> Result<PostTableDataResponse> {
|
||||
// The conversion logic is now gone from here.
|
||||
let grpc_request = PostTableDataRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
data, // This is now the correct type
|
||||
data,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
@@ -182,15 +225,13 @@ pub async fn get_table_data_by_position(
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
id: i64,
|
||||
// CHANGE THIS: Accept the pre-converted data
|
||||
data: HashMap<String, Value>,
|
||||
) -> Result<PutTableDataResponse> {
|
||||
// The conversion logic is now gone from here.
|
||||
let grpc_request = PutTableDataRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
id,
|
||||
data, // This is now the correct type
|
||||
data,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) ---
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
262
client/tests/form/gui/form_tests.rs
Normal file
262
client/tests/form/gui/form_tests.rs
Normal file
@@ -0,0 +1,262 @@
|
||||
// client/tests/form_tests.rs
|
||||
use rstest::{fixture, rstest};
|
||||
use std::collections::HashMap;
|
||||
use client::state::pages::form::{FormState, FieldDefinition};
|
||||
use client::state::pages::canvas_state::CanvasState;
|
||||
|
||||
#[fixture]
|
||||
fn test_form_state() -> FormState {
|
||||
let fields = vec![
|
||||
FieldDefinition {
|
||||
display_name: "Company".to_string(),
|
||||
data_key: "firma".to_string(),
|
||||
is_link: false,
|
||||
link_target_table: None,
|
||||
},
|
||||
FieldDefinition {
|
||||
display_name: "Phone".to_string(),
|
||||
data_key: "telefon".to_string(),
|
||||
is_link: false,
|
||||
link_target_table: None,
|
||||
},
|
||||
FieldDefinition {
|
||||
display_name: "Email".to_string(),
|
||||
data_key: "email".to_string(),
|
||||
is_link: false,
|
||||
link_target_table: None,
|
||||
},
|
||||
];
|
||||
|
||||
FormState::new("test_profile".to_string(), "test_table".to_string(), fields)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn test_form_data() -> HashMap<String, String> {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), "Test Company".to_string());
|
||||
data.insert("telefon".to_string(), "+421123456789".to_string());
|
||||
data.insert("email".to_string(), "test@example.com".to_string());
|
||||
data
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_state_creation(test_form_state: FormState) {
|
||||
assert_eq!(test_form_state.profile_name, "test_profile");
|
||||
assert_eq!(test_form_state.table_name, "test_table");
|
||||
assert_eq!(test_form_state.fields.len(), 3);
|
||||
assert_eq!(test_form_state.current_field(), 0);
|
||||
assert!(!test_form_state.has_unsaved_changes());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_field_navigation(mut test_form_state: FormState) {
|
||||
// Test initial field
|
||||
assert_eq!(test_form_state.current_field(), 0);
|
||||
|
||||
// Test navigation to next field
|
||||
test_form_state.set_current_field(1);
|
||||
assert_eq!(test_form_state.current_field(), 1);
|
||||
|
||||
// Test navigation to last field
|
||||
test_form_state.set_current_field(2);
|
||||
assert_eq!(test_form_state.current_field(), 2);
|
||||
|
||||
// Test invalid field (should not crash)
|
||||
test_form_state.set_current_field(999);
|
||||
assert_eq!(test_form_state.current_field(), 2); // Should stay at valid field
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_data_entry(mut test_form_state: FormState) {
|
||||
// Test entering data in first field
|
||||
*test_form_state.get_current_input_mut() = "Test Company".to_string();
|
||||
test_form_state.set_has_unsaved_changes(true);
|
||||
|
||||
assert_eq!(test_form_state.get_current_input(), "Test Company");
|
||||
assert!(test_form_state.has_unsaved_changes());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_field_switching_with_data(mut test_form_state: FormState) {
|
||||
// Enter data in first field
|
||||
*test_form_state.get_current_input_mut() = "Company Name".to_string();
|
||||
|
||||
// Switch to second field
|
||||
test_form_state.set_current_field(1);
|
||||
*test_form_state.get_current_input_mut() = "+421123456789".to_string();
|
||||
|
||||
// Switch back to first field
|
||||
test_form_state.set_current_field(0);
|
||||
assert_eq!(test_form_state.get_current_input(), "Company Name");
|
||||
|
||||
// Switch to second field again
|
||||
test_form_state.set_current_field(1);
|
||||
assert_eq!(test_form_state.get_current_input(), "+421123456789");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_reset_functionality(mut test_form_state: FormState) {
|
||||
// Add some data
|
||||
test_form_state.set_current_field(0);
|
||||
*test_form_state.get_current_input_mut() = "Test Company".to_string();
|
||||
test_form_state.set_current_field(1);
|
||||
*test_form_state.get_current_input_mut() = "+421123456789".to_string();
|
||||
test_form_state.set_has_unsaved_changes(true);
|
||||
test_form_state.id = 123;
|
||||
test_form_state.current_position = 5;
|
||||
|
||||
// Reset the form
|
||||
test_form_state.reset_to_empty();
|
||||
|
||||
// Verify reset
|
||||
assert_eq!(test_form_state.id, 0);
|
||||
assert!(!test_form_state.has_unsaved_changes());
|
||||
assert_eq!(test_form_state.current_field(), 0);
|
||||
|
||||
// Check all fields are empty
|
||||
for i in 0..test_form_state.fields.len() {
|
||||
test_form_state.set_current_field(i);
|
||||
assert!(test_form_state.get_current_input().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_update_from_response(mut test_form_state: FormState, test_form_data: HashMap<String, String>) {
|
||||
let position = 3;
|
||||
|
||||
// Update form with response data
|
||||
test_form_state.update_from_response(&test_form_data, position);
|
||||
|
||||
// Verify data was loaded
|
||||
assert_eq!(test_form_state.current_position, position);
|
||||
assert!(!test_form_state.has_unsaved_changes());
|
||||
assert_eq!(test_form_state.current_field(), 0);
|
||||
|
||||
// Check field values
|
||||
test_form_state.set_current_field(0);
|
||||
assert_eq!(test_form_state.get_current_input(), "Test Company");
|
||||
|
||||
test_form_state.set_current_field(1);
|
||||
assert_eq!(test_form_state.get_current_input(), "+421123456789");
|
||||
|
||||
test_form_state.set_current_field(2);
|
||||
assert_eq!(test_form_state.get_current_input(), "test@example.com");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_cursor_position(mut test_form_state: FormState) {
|
||||
// Test initial cursor position
|
||||
assert_eq!(test_form_state.current_cursor_pos(), 0);
|
||||
|
||||
// Add some text
|
||||
*test_form_state.get_current_input_mut() = "Test Company".to_string();
|
||||
|
||||
// Test cursor positioning
|
||||
test_form_state.set_current_cursor_pos(5);
|
||||
assert_eq!(test_form_state.current_cursor_pos(), 5);
|
||||
|
||||
// Test cursor bounds
|
||||
test_form_state.set_current_cursor_pos(999);
|
||||
// Should be clamped to text length
|
||||
assert!(test_form_state.current_cursor_pos() <= "Test Company".len());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_field_display_names(test_form_state: FormState) {
|
||||
let field_names = test_form_state.fields();
|
||||
|
||||
assert_eq!(field_names.len(), 3);
|
||||
assert_eq!(field_names[0], "Company");
|
||||
assert_eq!(field_names[1], "Phone");
|
||||
assert_eq!(field_names[2], "Email");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_inputs_vector(mut test_form_state: FormState) {
|
||||
// Add data to fields
|
||||
test_form_state.set_current_field(0);
|
||||
*test_form_state.get_current_input_mut() = "Company A".to_string();
|
||||
|
||||
test_form_state.set_current_field(1);
|
||||
*test_form_state.get_current_input_mut() = "123456789".to_string();
|
||||
|
||||
test_form_state.set_current_field(2);
|
||||
*test_form_state.get_current_input_mut() = "test@test.com".to_string();
|
||||
|
||||
// Get inputs vector
|
||||
let inputs = test_form_state.inputs();
|
||||
|
||||
assert_eq!(inputs.len(), 3);
|
||||
assert_eq!(inputs[0], "Company A");
|
||||
assert_eq!(inputs[1], "123456789");
|
||||
assert_eq!(inputs[2], "test@test.com");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_position_management(mut test_form_state: FormState) {
|
||||
// Test initial position
|
||||
assert_eq!(test_form_state.current_position, 1);
|
||||
assert_eq!(test_form_state.total_count, 0);
|
||||
|
||||
// Set some values
|
||||
test_form_state.total_count = 10;
|
||||
test_form_state.current_position = 5;
|
||||
|
||||
assert_eq!(test_form_state.current_position, 5);
|
||||
assert_eq!(test_form_state.total_count, 10);
|
||||
|
||||
// Test reset affects position
|
||||
test_form_state.reset_to_empty();
|
||||
assert_eq!(test_form_state.current_position, 11); // total_count + 1
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_autocomplete_state(mut test_form_state: FormState) {
|
||||
// Test initial autocomplete state
|
||||
assert!(!test_form_state.autocomplete_active);
|
||||
assert!(test_form_state.autocomplete_suggestions.is_empty());
|
||||
assert!(test_form_state.selected_suggestion_index.is_none());
|
||||
|
||||
// Test deactivating autocomplete
|
||||
test_form_state.autocomplete_active = true;
|
||||
test_form_state.deactivate_autocomplete();
|
||||
|
||||
assert!(!test_form_state.autocomplete_active);
|
||||
assert!(test_form_state.autocomplete_suggestions.is_empty());
|
||||
assert!(test_form_state.selected_suggestion_index.is_none());
|
||||
assert!(!test_form_state.autocomplete_loading);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_empty_data_handling(mut test_form_state: FormState) {
|
||||
let empty_data = HashMap::new();
|
||||
|
||||
// Update with empty data
|
||||
test_form_state.update_from_response(&empty_data, 1);
|
||||
|
||||
// All fields should be empty
|
||||
for i in 0..test_form_state.fields.len() {
|
||||
test_form_state.set_current_field(i);
|
||||
assert!(test_form_state.get_current_input().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_form_partial_data_handling(mut test_form_state: FormState) {
|
||||
let mut partial_data = HashMap::new();
|
||||
partial_data.insert("firma".to_string(), "Partial Company".to_string());
|
||||
// Intentionally missing telefon and email
|
||||
|
||||
test_form_state.update_from_response(&partial_data, 1);
|
||||
|
||||
// First field should have data
|
||||
test_form_state.set_current_field(0);
|
||||
assert_eq!(test_form_state.get_current_input(), "Partial Company");
|
||||
|
||||
// Other fields should be empty
|
||||
test_form_state.set_current_field(1);
|
||||
assert!(test_form_state.get_current_input().is_empty());
|
||||
|
||||
test_form_state.set_current_field(2);
|
||||
assert!(test_form_state.get_current_input().is_empty());
|
||||
}
|
||||
1
client/tests/form/gui/mod.rs
Normal file
1
client/tests/form/gui/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod form_tests;
|
||||
2
client/tests/form/mod.rs
Normal file
2
client/tests/form/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod gui;
|
||||
pub mod requests;
|
||||
1019
client/tests/form/requests/form_request_tests.rs
Normal file
1019
client/tests/form/requests/form_request_tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
267
client/tests/form/requests/form_request_tests2.rs
Normal file
267
client/tests/form/requests/form_request_tests2.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
// ========================================================================
|
||||
// ROBUST WORKFLOW AND INTEGRATION TESTS
|
||||
// ========================================================================
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_partial_update_preserves_other_fields(
|
||||
#[future] populated_test_context: FormTestContext,
|
||||
) {
|
||||
let mut context = populated_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// 1. Create a record with multiple fields
|
||||
let mut initial_data = context.create_test_form_data();
|
||||
let original_email = "preserve.this@email.com";
|
||||
initial_data.insert(
|
||||
"email".to_string(),
|
||||
create_string_value(original_email),
|
||||
);
|
||||
|
||||
let post_res = context
|
||||
.client
|
||||
.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
initial_data,
|
||||
)
|
||||
.await
|
||||
.expect("Setup: Failed to create record for partial update test");
|
||||
let created_id = post_res.inserted_id;
|
||||
println!("Partial Update Test: Created record ID {}", created_id);
|
||||
|
||||
// 2. Update only ONE field
|
||||
let mut partial_update = HashMap::new();
|
||||
let updated_firma = "Partially Updated Inc.";
|
||||
partial_update.insert(
|
||||
"firma".to_string(),
|
||||
create_string_value(updated_firma),
|
||||
);
|
||||
|
||||
context
|
||||
.client
|
||||
.put_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
created_id,
|
||||
partial_update,
|
||||
)
|
||||
.await
|
||||
.expect("Partial update failed");
|
||||
println!("Partial Update Test: Updated only 'firma' field");
|
||||
|
||||
// 3. Get the record back and verify ALL fields
|
||||
let get_res = context
|
||||
.client
|
||||
.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
created_id,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to get record after partial update");
|
||||
|
||||
let final_data = get_res.data;
|
||||
assert_eq!(
|
||||
final_data.get("firma").unwrap(),
|
||||
updated_firma,
|
||||
"The 'firma' field should be updated"
|
||||
);
|
||||
assert_eq!(
|
||||
final_data.get("email").unwrap(),
|
||||
original_email,
|
||||
"The 'email' field should have been preserved"
|
||||
);
|
||||
println!("Partial Update Test: Verified other fields were preserved. OK.");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_data_edge_cases_and_unicode(
|
||||
#[future] form_test_context: FormTestContext,
|
||||
) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
let edge_case_strings = vec![
|
||||
("Unicode", "José María González, Москва, 北京市"),
|
||||
("Emoji", "🚀 Tech Company 🌟"),
|
||||
("Quotes", "Quote\"Test'Apostrophe"),
|
||||
("Symbols", "Price: $1,000.50 (50% off!)"),
|
||||
("Empty", ""),
|
||||
("Whitespace", " "),
|
||||
];
|
||||
|
||||
for (case_name, test_string) in edge_case_strings {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value(test_string));
|
||||
data.insert(
|
||||
"kz".to_string(),
|
||||
create_string_value(&format!("EDGE-{}", case_name)),
|
||||
);
|
||||
|
||||
let post_res = context
|
||||
.client
|
||||
.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
)
|
||||
.await
|
||||
.expect(&format!("POST should succeed for case: {}", case_name));
|
||||
let created_id = post_res.inserted_id;
|
||||
|
||||
let get_res = context
|
||||
.client
|
||||
.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
created_id,
|
||||
)
|
||||
.await
|
||||
.expect(&format!(
|
||||
"GET should succeed for case: {}",
|
||||
case_name
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
get_res.data.get("firma").unwrap(),
|
||||
test_string,
|
||||
"Data should be identical after round-trip for case: {}",
|
||||
case_name
|
||||
);
|
||||
println!("Edge Case Test: '{}' passed.", case_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_numeric_and_null_edge_cases(
|
||||
#[future] form_test_context: FormTestContext,
|
||||
) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// 1. Test NULL value
|
||||
let mut null_data = HashMap::new();
|
||||
null_data.insert(
|
||||
"firma".to_string(),
|
||||
create_string_value("Company With Null Phone"),
|
||||
);
|
||||
null_data.insert("telefon".to_string(), create_null_value());
|
||||
let post_res_null = context
|
||||
.client
|
||||
.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
null_data,
|
||||
)
|
||||
.await
|
||||
.expect("POST with NULL value should succeed");
|
||||
let get_res_null = context
|
||||
.client
|
||||
.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
post_res_null.inserted_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// Depending on DB, NULL may come back as empty string or be absent.
|
||||
// The important part is that the operation doesn't fail.
|
||||
assert!(
|
||||
get_res_null.data.get("telefon").unwrap_or(&"".to_string()).is_empty(),
|
||||
"NULL value should result in an empty or absent field"
|
||||
);
|
||||
println!("Edge Case Test: NULL value handled correctly. OK.");
|
||||
|
||||
// 2. Test Zero value for a numeric field (assuming 'age' is numeric)
|
||||
let mut zero_data = HashMap::new();
|
||||
zero_data.insert(
|
||||
"firma".to_string(),
|
||||
create_string_value("Newborn Company"),
|
||||
);
|
||||
// Assuming 'age' is a field in your actual table definition
|
||||
// zero_data.insert("age".to_string(), create_number_value(0.0));
|
||||
// let post_res_zero = context.client.post_table_data(...).await.expect("POST with zero should succeed");
|
||||
// ... then get and verify it's "0"
|
||||
println!("Edge Case Test: Zero value test skipped (uncomment if 'age' field exists).");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_updates_on_same_record(
|
||||
#[future] populated_test_context: FormTestContext,
|
||||
) {
|
||||
let mut context = populated_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// 1. Create a single record to be updated by all tasks
|
||||
let initial_data = context.create_minimal_form_data();
|
||||
let post_res = context
|
||||
.client
|
||||
.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
initial_data,
|
||||
)
|
||||
.await
|
||||
.expect("Setup: Failed to create record for concurrency test");
|
||||
let record_id = post_res.inserted_id;
|
||||
println!("Concurrency Test: Target record ID is {}", record_id);
|
||||
|
||||
// 2. Spawn multiple concurrent UPDATE operations
|
||||
let mut handles = Vec::new();
|
||||
let num_concurrent_tasks = 5;
|
||||
let mut final_values = Vec::new();
|
||||
|
||||
for i in 0..num_concurrent_tasks {
|
||||
let mut client_clone = context.client.clone();
|
||||
let profile_name = context.profile_name.clone();
|
||||
let table_name = context.table_name.clone();
|
||||
let final_value = format!("Concurrent Update {}", i);
|
||||
final_values.push(final_value.clone());
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut update_data = HashMap::new();
|
||||
update_data.insert(
|
||||
"firma".to_string(),
|
||||
create_string_value(&final_value),
|
||||
);
|
||||
client_clone
|
||||
.put_table_data(profile_name, table_name, record_id, update_data)
|
||||
.await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// 3. Wait for all tasks to complete and check for panics
|
||||
let results = futures::future::join_all(handles).await;
|
||||
assert!(
|
||||
results.iter().all(|r| r.is_ok()),
|
||||
"No concurrent task should panic"
|
||||
);
|
||||
println!("Concurrency Test: All update tasks completed without panicking.");
|
||||
|
||||
// 4. Get the final state of the record
|
||||
let final_get_res = context
|
||||
.client
|
||||
.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
)
|
||||
.await
|
||||
.expect("Should be able to get the record after concurrent updates");
|
||||
|
||||
let final_firma = final_get_res.data.get("firma").unwrap();
|
||||
assert!(
|
||||
final_values.contains(final_firma),
|
||||
"The final state '{}' must be one of the states set by the tasks",
|
||||
final_firma
|
||||
);
|
||||
println!(
|
||||
"Concurrency Test: Final state is '{}', which is a valid outcome. OK.",
|
||||
final_firma
|
||||
);
|
||||
}
|
||||
727
client/tests/form/requests/form_request_tests3.rs
Normal file
727
client/tests/form/requests/form_request_tests3.rs
Normal file
@@ -0,0 +1,727 @@
|
||||
// form_request_tests3.rs - Comprehensive and Robust Testing
|
||||
|
||||
// ========================================================================
|
||||
// STEEL SCRIPT VALIDATION TESTS (HIGHEST PRIORITY)
|
||||
// ========================================================================
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_steel_script_validation_success(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test with data that should pass script validation
|
||||
// Assuming there's a script that validates 'kz' field to start with "KZ" and be 5 chars
|
||||
let mut valid_data = HashMap::new();
|
||||
valid_data.insert("firma".to_string(), create_string_value("Script Test Company"));
|
||||
valid_data.insert("kz".to_string(), create_string_value("KZ123"));
|
||||
valid_data.insert("telefon".to_string(), create_string_value("+421123456789"));
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
valid_data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
assert!(response.success, "Valid data should pass script validation");
|
||||
println!("Script Validation Test: Valid data passed - ID {}", response.inserted_id);
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(status) = e.downcast_ref::<Status>() {
|
||||
if status.code() == tonic::Code::Unavailable {
|
||||
println!("Script validation test skipped - backend not available");
|
||||
return;
|
||||
}
|
||||
// If there are no scripts configured, this might still work
|
||||
println!("Script validation test: {}", status.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_steel_script_validation_failure(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test with data that should fail script validation
|
||||
let invalid_script_data = vec![
|
||||
("TooShort", "KZ12"), // Too short
|
||||
("TooLong", "KZ12345"), // Too long
|
||||
("WrongPrefix", "AB123"), // Wrong prefix
|
||||
("NoPrefix", "12345"), // No prefix
|
||||
("Empty", ""), // Empty
|
||||
];
|
||||
|
||||
for (test_case, invalid_kz) in invalid_script_data {
|
||||
let mut invalid_data = HashMap::new();
|
||||
invalid_data.insert("firma".to_string(), create_string_value("Script Fail Company"));
|
||||
invalid_data.insert("kz".to_string(), create_string_value(invalid_kz));
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
invalid_data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
println!("Script Validation Test: {} passed (no validation script configured)", test_case);
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(status) = e.downcast_ref::<Status>() {
|
||||
assert_eq!(status.code(), tonic::Code::InvalidArgument,
|
||||
"Script validation failure should return InvalidArgument for case: {}", test_case);
|
||||
println!("Script Validation Test: {} correctly failed - {}", test_case, status.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_steel_script_validation_on_update(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// 1. Create a valid record first
|
||||
let mut initial_data = HashMap::new();
|
||||
initial_data.insert("firma".to_string(), create_string_value("Update Script Test"));
|
||||
initial_data.insert("kz".to_string(), create_string_value("KZ123"));
|
||||
|
||||
let post_result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
initial_data,
|
||||
).await;
|
||||
|
||||
if let Ok(post_response) = post_result {
|
||||
let record_id = post_response.inserted_id;
|
||||
|
||||
// 2. Try to update with invalid data
|
||||
let mut invalid_update = HashMap::new();
|
||||
invalid_update.insert("kz".to_string(), create_string_value("INVALID"));
|
||||
|
||||
let update_result = context.client.put_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
invalid_update,
|
||||
).await;
|
||||
|
||||
match update_result {
|
||||
Ok(_) => {
|
||||
println!("Script Validation on Update: No validation script configured for updates");
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(status) = e.downcast_ref::<Status>() {
|
||||
assert_eq!(status.code(), tonic::Code::InvalidArgument,
|
||||
"Update with invalid data should fail script validation");
|
||||
println!("Script Validation on Update: Correctly rejected invalid update");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// COMPREHENSIVE DATA TYPE TESTS
|
||||
// ========================================================================
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_boolean_data_type(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test valid boolean values
|
||||
let boolean_test_cases = vec![
|
||||
("true", true),
|
||||
("false", false),
|
||||
];
|
||||
|
||||
for (case_name, bool_value) in boolean_test_cases {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value("Boolean Test Company"));
|
||||
// Assuming there's a boolean field called 'active'
|
||||
data.insert("active".to_string(), create_bool_value(bool_value));
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
println!("Boolean Test: {} value succeeded", case_name);
|
||||
|
||||
// Verify the value round-trip
|
||||
if let Ok(get_response) = context.client.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
response.inserted_id,
|
||||
).await {
|
||||
if let Some(retrieved_value) = get_response.data.get("active") {
|
||||
println!("Boolean Test: {} round-trip value: {}", case_name, retrieved_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Boolean Test: {} failed (field may not exist): {}", case_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_numeric_data_types(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test various numeric values
|
||||
let numeric_test_cases = vec![
|
||||
("Zero", 0.0),
|
||||
("Positive", 123.45),
|
||||
("Negative", -67.89),
|
||||
("Large", 999999.99),
|
||||
("SmallDecimal", 0.01),
|
||||
];
|
||||
|
||||
for (case_name, numeric_value) in numeric_test_cases {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value("Numeric Test Company"));
|
||||
// Assuming there's a numeric field called 'price' or 'amount'
|
||||
data.insert("amount".to_string(), create_number_value(numeric_value));
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
println!("Numeric Test: {} ({}) succeeded", case_name, numeric_value);
|
||||
|
||||
// Verify round-trip
|
||||
if let Ok(get_response) = context.client.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
response.inserted_id,
|
||||
).await {
|
||||
if let Some(retrieved_value) = get_response.data.get("amount") {
|
||||
println!("Numeric Test: {} round-trip value: {}", case_name, retrieved_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Numeric Test: {} failed (field may not exist): {}", case_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_timestamp_data_type(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test various timestamp formats
|
||||
let timestamp_test_cases = vec![
|
||||
("ISO8601", "2024-01-15T10:30:00Z"),
|
||||
("WithTimezone", "2024-01-15T10:30:00+01:00"),
|
||||
("WithMilliseconds", "2024-01-15T10:30:00.123Z"),
|
||||
];
|
||||
|
||||
for (case_name, timestamp_str) in timestamp_test_cases {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value("Timestamp Test Company"));
|
||||
// Assuming there's a timestamp field called 'created_at'
|
||||
data.insert("created_at".to_string(), create_string_value(timestamp_str));
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
println!("Timestamp Test: {} succeeded", case_name);
|
||||
|
||||
// Verify round-trip
|
||||
if let Ok(get_response) = context.client.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
response.inserted_id,
|
||||
).await {
|
||||
if let Some(retrieved_value) = get_response.data.get("created_at") {
|
||||
println!("Timestamp Test: {} round-trip value: {}", case_name, retrieved_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Timestamp Test: {} failed (field may not exist): {}", case_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_invalid_data_types(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test invalid data type combinations
|
||||
let invalid_type_cases = vec![
|
||||
("StringForNumber", "amount", create_string_value("not-a-number")),
|
||||
("NumberForBoolean", "active", create_number_value(123.0)),
|
||||
("StringForBoolean", "active", create_string_value("maybe")),
|
||||
("InvalidTimestamp", "created_at", create_string_value("not-a-date")),
|
||||
];
|
||||
|
||||
for (case_name, field_name, invalid_value) in invalid_type_cases {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value("Invalid Type Test"));
|
||||
data.insert(field_name.to_string(), invalid_value);
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
println!("Invalid Type Test: {} passed (no type validation or field doesn't exist)", case_name);
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(status) = e.downcast_ref::<Status>() {
|
||||
assert_eq!(status.code(), tonic::Code::InvalidArgument,
|
||||
"Invalid data type should return InvalidArgument for case: {}", case_name);
|
||||
println!("Invalid Type Test: {} correctly rejected - {}", case_name, status.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// FOREIGN KEY RELATIONSHIP TESTS
|
||||
// ========================================================================
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_foreign_key_valid_relationship(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// 1. Create a parent record first (e.g., company)
|
||||
let mut parent_data = HashMap::new();
|
||||
parent_data.insert("firma".to_string(), create_string_value("Parent Company"));
|
||||
|
||||
let parent_result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
"companies".to_string(), // Assuming companies table exists
|
||||
parent_data,
|
||||
).await;
|
||||
|
||||
if let Ok(parent_response) = parent_result {
|
||||
let parent_id = parent_response.inserted_id;
|
||||
|
||||
// 2. Create a child record that references the parent
|
||||
let mut child_data = HashMap::new();
|
||||
child_data.insert("name".to_string(), create_string_value("Child Record"));
|
||||
child_data.insert("company_id".to_string(), create_number_value(parent_id as f64));
|
||||
|
||||
let child_result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
"contacts".to_string(), // Assuming contacts table exists
|
||||
child_data,
|
||||
).await;
|
||||
|
||||
match child_result {
|
||||
Ok(child_response) => {
|
||||
assert!(child_response.success, "Valid foreign key relationship should succeed");
|
||||
println!("Foreign Key Test: Valid relationship created - Parent ID: {}, Child ID: {}",
|
||||
parent_id, child_response.inserted_id);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Foreign Key Test: Failed (tables may not exist or no FK constraint): {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Foreign Key Test: Could not create parent record");
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_foreign_key_invalid_relationship(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Try to create a child record with non-existent parent ID
|
||||
let mut invalid_child_data = HashMap::new();
|
||||
invalid_child_data.insert("name".to_string(), create_string_value("Orphan Record"));
|
||||
invalid_child_data.insert("company_id".to_string(), create_number_value(99999.0)); // Non-existent ID
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
"contacts".to_string(),
|
||||
invalid_child_data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
println!("Foreign Key Test: Invalid relationship passed (no FK constraint configured)");
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(status) = e.downcast_ref::<Status>() {
|
||||
// Could be InvalidArgument or NotFound depending on implementation
|
||||
assert!(matches!(status.code(), tonic::Code::InvalidArgument | tonic::Code::NotFound),
|
||||
"Invalid foreign key should return InvalidArgument or NotFound");
|
||||
println!("Foreign Key Test: Invalid relationship correctly rejected - {}", status.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// DELETED RECORD INTERACTION TESTS
|
||||
// ========================================================================
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_update_deleted_record_behavior(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// 1. Create a record
|
||||
let initial_data = context.create_test_form_data();
|
||||
let post_result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
initial_data,
|
||||
).await;
|
||||
|
||||
if let Ok(post_response) = post_result {
|
||||
let record_id = post_response.inserted_id;
|
||||
println!("Deleted Record Test: Created record ID {}", record_id);
|
||||
|
||||
// 2. Delete the record (soft delete)
|
||||
let delete_result = context.client.delete_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
).await;
|
||||
|
||||
assert!(delete_result.is_ok(), "Delete should succeed");
|
||||
println!("Deleted Record Test: Soft-deleted record {}", record_id);
|
||||
|
||||
// 3. Try to UPDATE the deleted record
|
||||
let mut update_data = HashMap::new();
|
||||
update_data.insert("firma".to_string(), create_string_value("Updated Deleted Record"));
|
||||
|
||||
let update_result = context.client.put_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
update_data,
|
||||
).await;
|
||||
|
||||
match update_result {
|
||||
Ok(_) => {
|
||||
// This might be a bug - updating deleted records should probably fail
|
||||
println!("Deleted Record Test: UPDATE on deleted record succeeded (potential bug?)");
|
||||
|
||||
// Check if the record is still considered deleted
|
||||
let get_result = context.client.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
).await;
|
||||
|
||||
if get_result.is_err() {
|
||||
println!("Deleted Record Test: Record still appears deleted after update");
|
||||
} else {
|
||||
println!("Deleted Record Test: Record appears to be undeleted after update");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(status) = e.downcast_ref::<Status>() {
|
||||
assert_eq!(status.code(), tonic::Code::NotFound,
|
||||
"UPDATE on deleted record should return NotFound");
|
||||
println!("Deleted Record Test: UPDATE correctly rejected on deleted record");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_delete_already_deleted_record(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// 1. Create and delete a record
|
||||
let initial_data = context.create_test_form_data();
|
||||
let post_result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
initial_data,
|
||||
).await;
|
||||
|
||||
if let Ok(post_response) = post_result {
|
||||
let record_id = post_response.inserted_id;
|
||||
|
||||
// First deletion
|
||||
let delete_result1 = context.client.delete_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
).await;
|
||||
assert!(delete_result1.is_ok(), "First delete should succeed");
|
||||
|
||||
// Second deletion (idempotent)
|
||||
let delete_result2 = context.client.delete_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
).await;
|
||||
|
||||
assert!(delete_result2.is_ok(), "Second delete should succeed (idempotent)");
|
||||
if let Ok(response) = delete_result2 {
|
||||
assert!(response.success, "Delete should report success even for already-deleted record");
|
||||
}
|
||||
println!("Double Delete Test: Both deletions succeeded (idempotent behavior)");
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// VALIDATION AND BOUNDARY TESTS
|
||||
// ========================================================================
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_large_data_handling(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test with very large string values
|
||||
let large_string = "A".repeat(10000); // 10KB string
|
||||
let very_large_string = "B".repeat(100000); // 100KB string
|
||||
|
||||
let test_cases = vec![
|
||||
("Large", large_string),
|
||||
("VeryLarge", very_large_string),
|
||||
];
|
||||
|
||||
for (case_name, large_value) in test_cases {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value(&large_value));
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
println!("Large Data Test: {} string handled successfully", case_name);
|
||||
|
||||
// Verify round-trip
|
||||
if let Ok(get_response) = context.client.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
response.inserted_id,
|
||||
).await {
|
||||
if let Some(retrieved_value) = get_response.data.get("firma") {
|
||||
assert_eq!(retrieved_value.len(), large_value.len(),
|
||||
"Large string should survive round-trip for case: {}", case_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Large Data Test: {} failed (may hit size limits): {}", case_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_sql_injection_attempts(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test potential SQL injection strings
|
||||
let injection_attempts = vec![
|
||||
("SingleQuote", "'; DROP TABLE users; --"),
|
||||
("DoubleQuote", "\"; DROP TABLE users; --"),
|
||||
("Union", "' UNION SELECT * FROM users --"),
|
||||
("Comment", "/* malicious comment */"),
|
||||
("Semicolon", "; DELETE FROM users;"),
|
||||
];
|
||||
|
||||
for (case_name, injection_string) in injection_attempts {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value(injection_string));
|
||||
data.insert("kz".to_string(), create_string_value("KZ123"));
|
||||
|
||||
let result = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
println!("SQL Injection Test: {} handled safely (parameterized queries)", case_name);
|
||||
|
||||
// Verify the malicious string was stored as-is (not executed)
|
||||
if let Ok(get_response) = context.client.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
response.inserted_id,
|
||||
).await {
|
||||
if let Some(retrieved_value) = get_response.data.get("firma") {
|
||||
assert_eq!(retrieved_value, injection_string,
|
||||
"Injection string should be stored literally for case: {}", case_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("SQL Injection Test: {} rejected: {}", case_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_operations_with_same_data(#[future] form_test_context: FormTestContext) {
|
||||
let context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
// Test multiple concurrent operations with identical data
|
||||
let mut handles = Vec::new();
|
||||
let num_tasks = 10;
|
||||
|
||||
for i in 0..num_tasks {
|
||||
let mut context_clone = context.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value("Concurrent Identical"));
|
||||
data.insert("kz".to_string(), create_string_value(&format!("SAME{:02}", i)));
|
||||
|
||||
context_clone.client.post_table_data(
|
||||
context_clone.profile_name,
|
||||
context_clone.table_name,
|
||||
data,
|
||||
).await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// Wait for all to complete
|
||||
let mut success_count = 0;
|
||||
let mut inserted_ids = Vec::new();
|
||||
|
||||
for (i, handle) in handles.into_iter().enumerate() {
|
||||
match handle.await {
|
||||
Ok(Ok(response)) => {
|
||||
success_count += 1;
|
||||
inserted_ids.push(response.inserted_id);
|
||||
println!("Concurrent Identical Data: Task {} succeeded with ID {}", i, response.inserted_id);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
println!("Concurrent Identical Data: Task {} failed: {}", i, e);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Concurrent Identical Data: Task {} panicked: {}", i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(success_count > 0, "At least some concurrent operations should succeed");
|
||||
|
||||
// Verify all IDs are unique
|
||||
let unique_ids: std::collections::HashSet<_> = inserted_ids.iter().collect();
|
||||
assert_eq!(unique_ids.len(), inserted_ids.len(), "All inserted IDs should be unique");
|
||||
|
||||
println!("Concurrent Identical Data: {}/{} operations succeeded with unique IDs",
|
||||
success_count, num_tasks);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// PERFORMANCE AND STRESS TESTS
|
||||
// ========================================================================
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_bulk_operations_performance(#[future] form_test_context: FormTestContext) {
|
||||
let mut context = form_test_context.await;
|
||||
skip_if_backend_unavailable!();
|
||||
|
||||
let operation_count = 50;
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let mut successful_operations = 0;
|
||||
let mut created_ids = Vec::new();
|
||||
|
||||
// Bulk create
|
||||
for i in 0..operation_count {
|
||||
let mut data = HashMap::new();
|
||||
data.insert("firma".to_string(), create_string_value(&format!("Bulk Company {}", i)));
|
||||
data.insert("kz".to_string(), create_string_value(&format!("BLK{:02}", i)));
|
||||
|
||||
if let Ok(response) = context.client.post_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
data,
|
||||
).await {
|
||||
successful_operations += 1;
|
||||
created_ids.push(response.inserted_id);
|
||||
}
|
||||
}
|
||||
|
||||
let create_duration = start_time.elapsed();
|
||||
println!("Bulk Performance: Created {} records in {:?}", successful_operations, create_duration);
|
||||
|
||||
// Bulk read
|
||||
let read_start = std::time::Instant::now();
|
||||
let mut successful_reads = 0;
|
||||
|
||||
for &record_id in &created_ids {
|
||||
if context.client.get_table_data(
|
||||
context.profile_name.clone(),
|
||||
context.table_name.clone(),
|
||||
record_id,
|
||||
).await.is_ok() {
|
||||
successful_reads += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let read_duration = read_start.elapsed();
|
||||
println!("Bulk Performance: Read {} records in {:?}", successful_reads, read_duration);
|
||||
|
||||
// Performance assertions
|
||||
assert!(successful_operations > operation_count * 8 / 10,
|
||||
"At least 80% of operations should succeed");
|
||||
assert!(create_duration.as_secs() < 60,
|
||||
"Bulk operations should complete in reasonable time");
|
||||
|
||||
println!("Bulk Performance Test: {}/{} creates, {}/{} reads successful",
|
||||
successful_operations, operation_count, successful_reads, created_ids.len());
|
||||
}
|
||||
1
client/tests/form/requests/mod.rs
Normal file
1
client/tests/form/requests/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod form_request_tests;
|
||||
3
client/tests/mod.rs
Normal file
3
client/tests/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// tests/mod.rs
|
||||
|
||||
pub mod form;
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/adresar.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.adresar;
|
||||
package komp_ac.adresar;
|
||||
|
||||
import "common.proto";
|
||||
// import "table_structure.proto";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/auth.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.auth;
|
||||
package komp_ac.auth;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/common.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.common;
|
||||
package komp_ac.common;
|
||||
|
||||
message Empty {}
|
||||
message CountResponse { int64 count = 1; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
syntax = "proto3";
|
||||
package multieko2.table_script;
|
||||
package komp_ac.table_script;
|
||||
|
||||
service TableScript {
|
||||
rpc PostTableScript(PostTableScriptRequest) returns (TableScriptResponse);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/table_structure.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.table_structure;
|
||||
package komp_ac.table_structure;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// proto/uctovnictvo.proto
|
||||
syntax = "proto3";
|
||||
package multieko2.uctovnictvo;
|
||||
package komp_ac.uctovnictvo;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
|
||||
@@ -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.
791
common/src/proto/komp_ac.adresar.rs
Normal file
791
common/src/proto/komp_ac.adresar.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
418
common/src/proto/komp_ac.auth.rs
Normal file
418
common/src/proto/komp_ac.auth.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
13
common/src/proto/komp_ac.common.rs
Normal file
13
common/src/proto/komp_ac.common.rs
Normal 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,
|
||||
}
|
||||
317
common/src/proto/komp_ac.search.rs
Normal file
317
common/src/proto/komp_ac.search.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
544
common/src/proto/komp_ac.table_definition.rs
Normal file
544
common/src/proto/komp_ac.table_definition.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
323
common/src/proto/komp_ac.table_script.rs
Normal file
323
common/src/proto/komp_ac.table_script.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
336
common/src/proto/komp_ac.table_structure.rs
Normal file
336
common/src/proto/komp_ac.table_structure.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
794
common/src/proto/komp_ac.tables_data.rs
Normal file
794
common/src/proto/komp_ac.tables_data.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
712
common/src/proto/komp_ac.uctovnictvo.rs
Normal file
712
common/src/proto/komp_ac.uctovnictvo.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = "1.37.2"
|
||||
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"
|
||||
|
||||
@@ -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;
|
||||
@@ -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",
|
||||
|
||||
@@ -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 ······ ✔
|
||||
╰─
|
||||
|
||||
@@ -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
|
||||
╰─
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -33,7 +33,7 @@ pub async fn qualify_table_name(
|
||||
.unwrap_or(false);
|
||||
|
||||
if definition_exists {
|
||||
Ok(format!("{}.\"{}\"", profile_name, table_name))
|
||||
Ok(format!("\"{}\".\"{}\"", profile_name, table_name))
|
||||
} else {
|
||||
// It's not a user-defined table, so it must be a system table in 'public.
|
||||
Ok(format!("\"{}\"", table_name))
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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\")"
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
// 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::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}")]
|
||||
@@ -24,63 +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, ¤t_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 steel_get_column with row context
|
||||
vm.register_fn("steel_get_column", {
|
||||
let ctx = context.clone();
|
||||
move |table: String, column: String| {
|
||||
ctx.steel_get_column(&table, &column)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
});
|
||||
// Register database access functions
|
||||
register_steel_functions(&mut vm, context.clone());
|
||||
|
||||
// Register steel_get_column_with_index
|
||||
vm.register_fn("steel_get_column_with_index", {
|
||||
let ctx = context.clone();
|
||||
move |table: String, index: i64, column: String| {
|
||||
ctx.steel_get_column_with_index(&table, index, &column)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
});
|
||||
// Register decimal math operations
|
||||
register_decimal_math_functions(&mut vm);
|
||||
|
||||
// SQL query registration
|
||||
vm.register_fn("steel_query_sql", {
|
||||
let ctx = context.clone();
|
||||
move |query: String| {
|
||||
ctx.steel_query_sql(&query)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
});
|
||||
// Register row data as variables in the Steel VM for get-var access
|
||||
let mut define_script = String::new();
|
||||
|
||||
// Execute script and process results
|
||||
let results = vm.compile_and_run_raw_program(script)
|
||||
.map_err(|e| ExecutionError::RuntimeError(e.to_string()))?;
|
||||
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));
|
||||
}
|
||||
|
||||
// Convert results to target type
|
||||
// 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>) {
|
||||
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| {
|
||||
error!("steel_get_column failed: {:?}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// 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| {
|
||||
error!("steel_get_column_with_index failed: {:?}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// 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| {
|
||||
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) {
|
||||
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 {
|
||||
if let SteelVal::StringV(s) = result {
|
||||
strings.push(s.to_string());
|
||||
} else {
|
||||
return Err(ExecutionError::TypeConversionError(
|
||||
format!("Expected string, got {:?}", result)
|
||||
));
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// src/steel/server/mod.rs
|
||||
pub mod execution;
|
||||
pub mod syntax_parser;
|
||||
pub mod functions;
|
||||
|
||||
pub use execution::*;
|
||||
pub use syntax_parser::*;
|
||||
pub use functions::*;
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// src/steel/server/syntax_parser.rs
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct SyntaxParser {
|
||||
current_table_column_re: Regex,
|
||||
different_table_column_re: Regex,
|
||||
one_to_many_indexed_re: Regex,
|
||||
sql_integration_re: Regex,
|
||||
}
|
||||
|
||||
impl SyntaxParser {
|
||||
pub fn new() -> Self {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&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: ®ex::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: ®ex::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: ®ex::Captures| {
|
||||
format!("(steel_get_column \"{}\" \"{}\")", current_table, &caps[1])
|
||||
}).to_string();
|
||||
|
||||
// Process SQL integration
|
||||
transformed = self.sql_integration_re.replace_all(&transformed, |caps: ®ex::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)
|
||||
}
|
||||
}
|
||||
@@ -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 ········· ✔
|
||||
╰─
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
566
server/src/table_script/handlers/dependency_analyzer.rs
Normal file
566
server/src/table_script/handlers/dependency_analyzer.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
229
server/src/table_script/handlers/dependency_utils.rs
Normal file
229
server/src/table_script/handlers/dependency_utils.rs
Normal 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(|(_, °ree)| 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"));
|
||||
}
|
||||
}
|
||||
@@ -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(" ")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// src/tables_data/mod.rs
|
||||
|
||||
// src/table_script/mod.rs
|
||||
pub mod handlers;
|
||||
|
||||
pub use handlers::*;
|
||||
|
||||
@@ -4,7 +4,7 @@ grpcurl -plaintext \
|
||||
"table_name": "2025_customer"
|
||||
}' \
|
||||
localhost:50051 \
|
||||
multieko2.table_structure.TableStructureService/GetTableStructure
|
||||
komp_ac.table_structure.TableStructureService/GetTableStructure
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user