diff --git a/common/proto/table_script.proto b/common/proto/table_script.proto index b7eeb8f..8d4199f 100644 --- a/common/proto/table_script.proto +++ b/common/proto/table_script.proto @@ -1,6 +1,10 @@ syntax = "proto3"; package multieko2.table_script; +service TableScript { + rpc PostTableScript(PostTableScriptRequest) returns (TableScriptResponse); +} + message PostTableScriptRequest { int64 table_definition_id = 1; string target_column = 2; diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index f697648..dbb9ff5 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/multieko2.table_script.rs b/common/src/proto/multieko2.table_script.rs index 76d52bb..e0b6cf1 100644 --- a/common/src/proto/multieko2.table_script.rs +++ b/common/src/proto/multieko2.table_script.rs @@ -17,3 +17,305 @@ pub struct TableScriptResponse { #[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 { + inner: tonic::client::Grpc, + } + impl TableScriptClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl TableScriptClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + 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( + inner: T, + interceptor: F, + ) -> TableScriptClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + 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, + ) -> std::result::Result< + tonic::Response, + 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( + "/multieko2.table_script.TableScript/PostTableScript", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "multieko2.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, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct TableScriptServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl TableScriptServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> 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( + inner: T, + interceptor: F, + ) -> InterceptedService + 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 tonic::codegen::Service> for TableScriptServer + where + T: TableScript, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/multieko2.table_script.TableScript/PostTableScript" => { + #[allow(non_camel_case_types)] + struct PostTableScriptSvc(pub Arc); + impl< + T: TableScript, + > tonic::server::UnaryService + for PostTableScriptSvc { + type Response = super::TableScriptResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::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(empty_body()); + 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 Clone for TableScriptServer { + 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 = "multieko2.table_script.TableScript"; + impl tonic::server::NamedService for TableScriptServer { + const NAME: &'static str = SERVICE_NAME; + } +} diff --git a/server/src/server/run.rs b/server/src/server/run.rs index f9ff935..273fb32 100644 --- a/server/src/server/run.rs +++ b/server/src/server/run.rs @@ -8,13 +8,15 @@ use crate::server::services::{ UctovnictvoService, TableStructureHandler, TableDefinitionService, - TablesDataService, // Add this + TablesDataService, + TableScriptService, }; use common::proto::multieko2::adresar::adresar_server::AdresarServer; use common::proto::multieko2::uctovnictvo::uctovnictvo_server::UctovnictvoServer; use common::proto::multieko2::table_structure::table_structure_service_server::TableStructureServiceServer; use common::proto::multieko2::table_definition::table_definition_server::TableDefinitionServer; -use common::proto::multieko2::tables_data::tables_data_server::TablesDataServer; // Add this +use common::proto::multieko2::tables_data::tables_data_server::TablesDataServer; +use common::proto::multieko2::table_script::table_script_server::TableScriptServer; pub async fn run_server(db_pool: sqlx::PgPool) -> Result<(), Box> { let addr = "[::1]:50051".parse()?; @@ -26,13 +28,15 @@ pub async fn run_server(db_pool: sqlx::PgPool) -> Result<(), Box, + ) -> Result, Status> { + let request = request.into_inner(); + let response = post_table_script(&self.db_pool, request).await?; + Ok(Response::new(response)) + } +} diff --git a/server/src/steel/validation/script.rs b/server/src/steel/validation/script.rs index 53f040e..bcc2257 100644 --- a/server/src/steel/validation/script.rs +++ b/server/src/steel/validation/script.rs @@ -1,12 +1,10 @@ // src/steel/validation/script.rs -use steel_core::steel_vm::engine::Engine; use std::fmt; #[derive(Debug)] pub enum ScriptValidationError { EmptyScript, InvalidSyntax(String), - MissingTransformFunction, } impl fmt::Display for ScriptValidationError { @@ -14,7 +12,6 @@ impl fmt::Display for ScriptValidationError { match self { Self::EmptyScript => write!(f, "Script cannot be empty"), Self::InvalidSyntax(msg) => write!(f, "Syntax error: {}", msg), - Self::MissingTransformFunction => write!(f, "Script must define a 'transform' function"), } } } @@ -24,14 +21,7 @@ pub fn validate_script(script: &str) -> Result<(), ScriptValidationError> { if script.trim().is_empty() { return Err(ScriptValidationError::EmptyScript); } - - // Create a new Steel engine - let mut engine = Engine::new(); - - // Check for the presence of a 'transform' function - if engine.extract_value("transform").is_err() { - return Err(ScriptValidationError::MissingTransformFunction); - } - + + // If we get here, the script passed basic validation Ok(()) }