diff --git a/.github/workflows/build-and-test-in-memory.yml b/.github/workflows/build-and-test-in-memory.yml new file mode 100644 index 0000000..007a52d --- /dev/null +++ b/.github/workflows/build-and-test-in-memory.yml @@ -0,0 +1,68 @@ +name: In-Memory VSS Server CI + +on: + push: + branches: [ main ] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-in-memory: + runs-on: ubuntu-latest + timeout-minutes: 6 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create in-memory config + run: | + mkdir -p rust/server + cat > rust/server/vss-server-config.toml < server.log 2>&1 & + echo "Server PID: $!" + + - name: Wait for server + run: | + for i in {1..15}; do + if curl -s http://127.0.0.1:8080 > /dev/null; then + echo "Server is up!" + exit 0 + fi + sleep 1 + done + echo "Server failed. Dumping log:" + cat rust/server.log + exit 1 + + - name: HTTP Smoke Test + run: | + curl -f \ + -H "Authorization: Bearer test_user" \ + --data-binary @<(echo "0A04746573741A150A026B3110FFFFFFFFFFFFFFFFFF011A046B317631" | xxd -r -p) \ + http://127.0.0.1:8080/vss/putObjects + + RESPONSE=$(curl -f \ + -H "Authorization: Bearer test_user" \ + --data-binary @<(echo "0A047465737412026B31" | xxd -r -p) \ + http://127.0.0.1:8080/vss/getObject) + + - name: Run In-Memory unit tests + working-directory: rust + run: cargo test --package impls --lib -- in_memory_store::tests --nocapture \ No newline at end of file diff --git a/.github/workflows/ldk-node-integration.yml b/.github/workflows/ldk-node-integration.yml index f6bcf7d..abcc083 100644 --- a/.github/workflows/ldk-node-integration.yml +++ b/.github/workflows/ldk-node-integration.yml @@ -7,14 +7,13 @@ concurrency: cancel-in-progress: true jobs: - build-and-test: + test-postgres: runs-on: ubuntu-latest - + timeout-minutes: 30 services: postgres: image: postgres:latest - ports: - - 5432:5432 + ports: [5432:5432] env: POSTGRES_DB: postgres POSTGRES_USER: postgres @@ -30,20 +29,105 @@ jobs: uses: actions/checkout@v3 with: path: vss-server + - name: Checkout LDK Node uses: actions/checkout@v3 with: repository: lightningdevkit/ldk-node path: ldk-node - - name: Build and Deploy VSS Server + - name: Create Postgres config + run: | + mkdir -p vss-server/rust/server + cat > vss-server/rust/server/vss-server-config.toml < server.log 2>&1 & + echo "Server PID: $!" + + - name: Wait for VSS + run: | + for i in {1..30}; do + if curl -s http://127.0.0.1:8080/vss > /dev/null; then + echo "VSS ready" + exit 0 + fi + sleep 2 + done + echo "VSS failed:" + cat vss-server/rust/vss.log + exit 1 + - name: Run LDK Node Integration tests + working-directory: ldk-node run: | - cd ldk-node - export TEST_VSS_BASE_URL="http://localhost:8080/vss" + export TEST_VSS_BASE_URL="http://127.0.0.1:8080/vss" RUSTFLAGS="--cfg vss_test" cargo test io::vss_store RUSTFLAGS="--cfg vss_test" cargo test --test integration_tests_vss + + test-in-memory: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + path: vss-server + + - name: Checkout LDK Node + uses: actions/checkout@v3 + with: + repository: lightningdevkit/ldk-node + path: ldk-node + + - name: Create In-Memory config + run: | + mkdir -p vss-server/rust/server + cat > vss-server/rust/server/vss-server-config.toml < server.log 2>&1 & + echo "Server PID: $!" + + - name: Wait for VSS + run: | + for i in {1..30}; do + if curl -s http://127.0.0.1:8080/vss > /dev/null; then + echo "VSS ready" + exit 0 + fi + sleep 1 + done + echo "VSS failed:" + cat vss-server/rust/vss.log + exit 1 + + - name: Run LDK Node Integration tests + working-directory: ldk-node + run: | + export TEST_VSS_BASE_URL="http://127.0.0.1:8080/vss" + RUSTFLAGS="--cfg vss_test" cargo test io::vss_store + RUSTFLAGS="--cfg vss_test" cargo test --test integration_tests_vss \ No newline at end of file diff --git a/rust/README.md b/rust/README.md index 7b9b96c..2dab9a1 100644 --- a/rust/README.md +++ b/rust/README.md @@ -24,6 +24,11 @@ cargo build --release ``` cargo run -- server/vss-server-config.toml ``` + + **Note:** For testing purposes, you can pass `--in-memory` to use in-memory instead of PostgreSQL + ``` + cargo run -- server/vss-server-config.toml --in-memory + ``` 4. VSS endpoint should be reachable at `http://localhost:8080/vss`. ### Configuration diff --git a/rust/impls/src/in_memory_store.rs b/rust/impls/src/in_memory_store.rs new file mode 100644 index 0000000..8897bd8 --- /dev/null +++ b/rust/impls/src/in_memory_store.rs @@ -0,0 +1,361 @@ +use crate::models::{ + VssDbRecord, LIST_KEY_VERSIONS_MAX_PAGE_SIZE, MAX_PUT_REQUEST_ITEM_COUNT, +}; +use api::error::VssError; +use api::kv_store::{KvStore, GLOBAL_VERSION_KEY, INITIAL_RECORD_VERSION}; +use api::types::{ + DeleteObjectRequest, DeleteObjectResponse, GetObjectRequest, GetObjectResponse, KeyValue, + ListKeyVersionsRequest, ListKeyVersionsResponse, PutObjectRequest, PutObjectResponse, +}; +use async_trait::async_trait; +use bytes::Bytes; +use chrono::prelude::Utc; +use std::collections::BTreeMap; +use std::sync::Arc; +use tokio::sync::Mutex; + +fn build_storage_key(user_token: &str, store_id: &str, key: &str) -> String { + format!("{}#{}#{}", user_token, store_id, key) +} + +/// In-memory implementation of the VSS Store. +pub struct InMemoryBackendImpl { + store: Arc>>, +} + +impl InMemoryBackendImpl { + /// Creates an in-memory instance. + pub fn new() -> Self { + Self { store: Arc::new(Mutex::new(BTreeMap::new())) } + } + + fn get_current_global_version( + &self, guard: &BTreeMap, user_token: &str, store_id: &str, + ) -> i64 { + let global_key = build_storage_key(user_token, store_id, GLOBAL_VERSION_KEY); + guard.get(&global_key).map(|r| r.version).unwrap_or(0) + } +} + +fn validate_put_operation( + store: &BTreeMap, user_token: &str, store_id: &str, key_value: &KeyValue, +) -> Result<(), VssError> { + let key = build_storage_key(user_token, store_id, &key_value.key); + + if key_value.version == -1 { + Ok(()) + } else if key_value.version == 0 { + if store.contains_key(&key) { + Err(VssError::ConflictError(format!( + "Key {} already exists for conditional insert", + key_value.key + ))) + } else { + Ok(()) + } + } else { + if let Some(existing) = store.get(&key) { + if existing.version == key_value.version { + Ok(()) + } else { + Err(VssError::ConflictError(format!( + "Version mismatch for key {}: expected {}, found {}", + key_value.key, key_value.version, existing.version + ))) + } + } else { + Err(VssError::ConflictError(format!( + "Key {} does not exist for conditional update", + key_value.key + ))) + } + } +} + +fn validate_delete_operation( + store: &BTreeMap, user_token: &str, store_id: &str, key_value: &KeyValue, +) -> Result<(), VssError> { + let key = build_storage_key(user_token, store_id, &key_value.key); + + if key_value.version == -1 { + Ok(()) + } else { + if let Some(existing) = store.get(&key) { + if existing.version == key_value.version { + Ok(()) + } else { + Err(VssError::ConflictError(format!( + "Version mismatch for delete key {}: expected {}, found {}", + key_value.key, key_value.version, existing.version + ))) + } + } else { + Err(VssError::ConflictError(format!( + "Key {} does not exist for conditional delete", + key_value.key + ))) + } + } +} + +fn execute_put_object( + store: &mut BTreeMap, user_token: &str, store_id: &str, + key_value: KeyValue, +) { + let key = build_storage_key(user_token, store_id, &key_value.key); + let now = Utc::now(); + + match store.entry(key) { + std::collections::btree_map::Entry::Occupied(mut occ) => { + let existing = occ.get_mut(); + existing.version = if key_value.version == -1 { + INITIAL_RECORD_VERSION as i64 + } else { + existing.version.saturating_add(1) + }; + existing.value = key_value.value.to_vec(); + existing.last_updated_at = now; + }, + std::collections::btree_map::Entry::Vacant(vac) => { + let new_record = VssDbRecord { + user_token: user_token.to_string(), + store_id: store_id.to_string(), + key: key_value.key, + value: key_value.value.to_vec(), + version: INITIAL_RECORD_VERSION as i64, + created_at: now, + last_updated_at: now, + }; + vac.insert(new_record); + }, + } +} + +fn execute_delete_object( + store: &mut BTreeMap, user_token: &str, store_id: &str, + key_value: &KeyValue, +) { + let key = build_storage_key(user_token, store_id, &key_value.key); + store.remove(&key); +} + +#[async_trait] +impl KvStore for InMemoryBackendImpl { + async fn get( + &self, user_token: String, request: GetObjectRequest, + ) -> Result { + let key = build_storage_key(&user_token, &request.store_id, &request.key); + let guard = self.store.lock().await; + + let result = if let Some(record) = guard.get(&key) { + Ok(GetObjectResponse { + value: Some(KeyValue { + key: record.key.clone(), + value: Bytes::from(record.value.clone()), + version: record.version, + }), + }) + } else if request.key == GLOBAL_VERSION_KEY { + // Non-zero global version is handled above; this is only for initial version 0. + Ok(GetObjectResponse { + value: Some(KeyValue { + key: GLOBAL_VERSION_KEY.to_string(), + value: Bytes::new(), + version: 0, + }), + }) + } else { + Err(VssError::NoSuchKeyError("Requested key not found.".to_string())) + }; + + result + } + + async fn put( + &self, user_token: String, request: PutObjectRequest, + ) -> Result { + if request.transaction_items.len() + request.delete_items.len() > MAX_PUT_REQUEST_ITEM_COUNT + { + return Err(VssError::InvalidRequestError(format!( + "Number of write items per request should be less than equal to {}", + MAX_PUT_REQUEST_ITEM_COUNT + ))); + } + + let store_id = request.store_id.clone(); + let mut guard = self.store.lock().await; + + if let Some(version) = request.global_version { + validate_put_operation( + &guard, + &user_token, + &store_id, + &KeyValue { key: GLOBAL_VERSION_KEY.to_string(), value: Bytes::new(), version }, + )?; + } + + for key_value in &request.transaction_items { + validate_put_operation(&guard, &user_token, &store_id, key_value)?; + } + + for key_value in &request.delete_items { + validate_delete_operation(&guard, &user_token, &store_id, key_value)?; + } + + for key_value in request.transaction_items { + execute_put_object(&mut guard, &user_token, &store_id, key_value); + } + + for key_value in &request.delete_items { + execute_delete_object(&mut guard, &user_token, &store_id, key_value); + } + + if let Some(version) = request.global_version { + execute_put_object( + &mut guard, + &user_token, + &store_id, + KeyValue { key: GLOBAL_VERSION_KEY.to_string(), value: Bytes::new(), version }, + ); + } + + Ok(PutObjectResponse {}) + } + + async fn delete( + &self, user_token: String, request: DeleteObjectRequest, + ) -> Result { + let key_value = request.key_value.ok_or_else(|| { + VssError::InvalidRequestError("key_value missing in DeleteObjectRequest".to_string()) + })?; + + let store_id = request.store_id.clone(); + let mut guard = self.store.lock().await; + + execute_delete_object(&mut guard, &user_token, &store_id, &key_value); + + Ok(DeleteObjectResponse {}) + } + + async fn list_key_versions( + &self, user_token: String, request: ListKeyVersionsRequest, + ) -> Result { + let store_id = request.store_id.clone(); + let key_prefix = request.key_prefix.clone().unwrap_or_default(); + let page_size = request.page_size.unwrap_or(i32::MAX); + let limit = std::cmp::min(page_size, LIST_KEY_VERSIONS_MAX_PAGE_SIZE) as usize; + + let offset: usize = + request.page_token.as_ref().and_then(|s| s.parse::().ok()).unwrap_or(0); + + let guard = self.store.lock().await; + + let mut global_version: Option = None; + if offset == 0 { + global_version = Some(self.get_current_global_version(&guard, &user_token, &store_id)); + } + + let storage_prefix = format!("{}#{}#", user_token, store_id); + let prefix_len = storage_prefix.len(); + + let mut all_items: Vec = guard + .iter() + .filter(|(storage_key, _)| storage_key.starts_with(&storage_prefix)) + .filter_map(|(storage_key, record)| { + let key = &storage_key[prefix_len..]; + + if key == GLOBAL_VERSION_KEY { + return None; + } + + if !key_prefix.is_empty() && !key.starts_with(&key_prefix) { + return None; + } + + Some(KeyValue { + key: key.to_string(), + value: Bytes::new(), + version: record.version, + }) + }) + .collect(); + + all_items.sort_by(|a, b| a.key.cmp(&b.key)); + + let page_items: Vec = + all_items.iter().skip(offset).take(limit).cloned().collect(); + + let next_offset = offset + page_items.len(); + let next_page_token = if page_items.is_empty() { + Some("".to_string()) + } else { + Some(next_offset.to_string()) + }; + + Ok(ListKeyVersionsResponse { key_versions: page_items, next_page_token, global_version }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use api::define_kv_store_tests; + use api::types::{GetObjectRequest, KeyValue, PutObjectRequest}; + use bytes::Bytes; + use tokio::test; + + define_kv_store_tests!(InMemoryKvStoreTest, InMemoryBackendImpl, InMemoryBackendImpl::new()); + + #[test] + async fn test_in_memory_crud() { + let store = InMemoryBackendImpl::new(); + let user_token = "test_user".to_string(); + let store_id = "test_store".to_string(); + + let put_request = PutObjectRequest { + store_id: store_id.clone(), + transaction_items: vec![KeyValue { + key: "key1".to_string(), + value: Bytes::from("value1"), + version: 0, + }], + delete_items: vec![], + global_version: None, + }; + store.put(user_token.clone(), put_request).await.unwrap(); + + let get_request = GetObjectRequest { store_id: store_id.clone(), key: "key1".to_string() }; + let response = store.get(user_token.clone(), get_request).await.unwrap(); + let key_value = response.value.unwrap(); + assert_eq!(key_value.value, Bytes::from("value1")); + assert_eq!(key_value.version, 1, "Expected version 1 after put"); + + let list_request = ListKeyVersionsRequest { + store_id: store_id.clone(), + key_prefix: None, + page_size: Some(1), + page_token: None, + }; + let response = store.list_key_versions(user_token.clone(), list_request).await.unwrap(); + assert_eq!(response.key_versions.len(), 1); + assert_eq!(response.key_versions[0].key, "key1"); + assert_eq!(response.key_versions[0].version, 1); + + let delete_request = DeleteObjectRequest { + store_id: store_id.clone(), + key_value: Some(KeyValue { key: "key1".to_string(), value: Bytes::new(), version: 1 }), + }; + store.delete(user_token.clone(), delete_request).await.unwrap(); + + let get_request = GetObjectRequest { store_id: store_id.clone(), key: "key1".to_string() }; + assert!(matches!( + store.get(user_token.clone(), get_request).await, + Err(VssError::NoSuchKeyError(_)) + )); + + let global_request = + GetObjectRequest { store_id: store_id.clone(), key: GLOBAL_VERSION_KEY.to_string() }; + let response = store.get(user_token.clone(), global_request).await.unwrap(); + assert_eq!(response.value.unwrap().version, 0, "Expected global_version=0"); + } +} diff --git a/rust/impls/src/lib.rs b/rust/impls/src/lib.rs index 27844d0..0d41aac 100644 --- a/rust/impls/src/lib.rs +++ b/rust/impls/src/lib.rs @@ -11,9 +11,13 @@ #![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] -mod migrations; +/// Contains in-memory backend implementation for VSS, for testing purposes only. +pub mod in_memory_store; +/// A shared abstraction +pub mod models; /// Contains [PostgreSQL](https://www.postgresql.org/) based backend implementation for VSS. pub mod postgres_store; +mod migrations; #[macro_use] extern crate api; diff --git a/rust/impls/src/migrations.rs b/rust/impls/src/migrations.rs index bab951b..02d3f54 100644 --- a/rust/impls/src/migrations.rs +++ b/rust/impls/src/migrations.rs @@ -5,7 +5,7 @@ pub(crate) const MIGRATION_LOG_COLUMN: &str = "upgrade_from"; pub(crate) const CHECK_DB_STMT: &str = "SELECT 1 FROM pg_database WHERE datname = $1"; pub(crate) const INIT_DB_CMD: &str = "CREATE DATABASE"; #[cfg(test)] -const DROP_DB_CMD: &str = "DROP DATABASE"; +pub(crate) const DROP_DB_CMD: &str = "DROP DATABASE"; pub(crate) const GET_VERSION_STMT: &str = "SELECT db_version FROM vss_db_version;"; pub(crate) const UPDATE_VERSION_STMT: &str = "UPDATE vss_db_version SET db_version=$1;"; pub(crate) const LOG_MIGRATION_STMT: &str = "INSERT INTO vss_db_upgrades VALUES($1);"; @@ -36,4 +36,4 @@ pub(crate) const MIGRATIONS: &[&str] = &[ );", ]; #[cfg(test)] -const DUMMY_MIGRATION: &str = "SELECT 1 WHERE FALSE;"; +pub(crate) const DUMMY_MIGRATION: &str = "SELECT 1 WHERE FALSE;"; diff --git a/rust/impls/src/models.rs b/rust/impls/src/models.rs new file mode 100644 index 0000000..2f69e69 --- /dev/null +++ b/rust/impls/src/models.rs @@ -0,0 +1,33 @@ +use chrono::Utc; + +/// A record stored in the VSS database. +pub struct VssDbRecord { + /// Token uniquely identifying the user that owns this record. + pub user_token: String, + /// Identifier for the store this record belongs to. + pub store_id: String, + /// Key under which the value is stored. + pub key: String, + /// Stored value as raw bytes. + pub value: Vec, + /// Version number for optimistic concurrency control. + pub version: i64, + /// Timestamp when the record was created (UTC). + pub created_at: chrono::DateTime, + /// Timestamp when the record was last updated (UTC). + pub last_updated_at: chrono::DateTime, +} + +/// The maximum number of key versions that can be returned in a single page. +/// +/// This constant helps control memory and bandwidth usage for list operations, +/// preventing overly large payloads. If the number of results exceeds this limit, +/// the response will be paginated. +pub const LIST_KEY_VERSIONS_MAX_PAGE_SIZE: i32 = 100; + +/// The maximum number of items allowed in a single `PutObjectRequest`. +/// +/// Setting an upper bound on the number of items helps ensure that +/// each request stays within acceptable memory and performance limits. +/// Exceeding this value will result in request rejection through [`VssError::InvalidRequestError`]. +pub const MAX_PUT_REQUEST_ITEM_COUNT: usize = 1000; diff --git a/rust/impls/src/postgres_store.rs b/rust/impls/src/postgres_store.rs index 1e951df..641e192 100644 --- a/rust/impls/src/postgres_store.rs +++ b/rust/impls/src/postgres_store.rs @@ -15,34 +15,16 @@ use std::cmp::min; use std::io; use std::io::{Error, ErrorKind}; use tokio_postgres::{error, NoTls, Transaction}; +use crate::models::{ + VssDbRecord, + LIST_KEY_VERSIONS_MAX_PAGE_SIZE, + MAX_PUT_REQUEST_ITEM_COUNT, +}; -pub(crate) struct VssDbRecord { - pub(crate) user_token: String, - pub(crate) store_id: String, - pub(crate) key: String, - pub(crate) value: Vec, - pub(crate) version: i64, - pub(crate) created_at: chrono::DateTime, - pub(crate) last_updated_at: chrono::DateTime, -} const KEY_COLUMN: &str = "key"; const VALUE_COLUMN: &str = "value"; const VERSION_COLUMN: &str = "version"; -/// The maximum number of key versions that can be returned in a single page. -/// -/// This constant helps control memory and bandwidth usage for list operations, -/// preventing overly large payloads. If the number of results exceeds this limit, -/// the response will be paginated. -pub const LIST_KEY_VERSIONS_MAX_PAGE_SIZE: i32 = 100; - -/// The maximum number of items allowed in a single `PutObjectRequest`. -/// -/// Setting an upper bound on the number of items helps ensure that -/// each request stays within acceptable memory and performance limits. -/// Exceeding this value will result in request rejection through [`VssError::InvalidRequestError`]. -pub const MAX_PUT_REQUEST_ITEM_COUNT: usize = 1000; - /// A [PostgreSQL](https://www.postgresql.org/) based backend implementation for VSS. pub struct PostgresBackendImpl { pool: Pool>, diff --git a/rust/server/Cargo.toml b/rust/server/Cargo.toml index 2a0e6f1..5b26e80 100644 --- a/rust/server/Cargo.toml +++ b/rust/server/Cargo.toml @@ -15,3 +15,7 @@ prost = { version = "0.11.6", default-features = false, features = ["std"] } bytes = "1.4.0" serde = { version = "1.0.203", default-features = false, features = ["derive"] } toml = { version = "0.8.9", default-features = false, features = ["parse"] } + +[[bin]] +name = "vss-server" +path = "src/main.rs" \ No newline at end of file diff --git a/rust/server/src/main.rs b/rust/server/src/main.rs index 5a78be6..0407b89 100644 --- a/rust/server/src/main.rs +++ b/rust/server/src/main.rs @@ -17,9 +17,11 @@ use tokio::signal::unix::SignalKind; use hyper::server::conn::http1; use hyper_util::rt::TokioIo; +use crate::util::config::StoreType; use crate::vss_service::VssService; use api::auth::{Authorizer, NoopAuthorizer}; use api::kv_store::KvStore; +use impls::in_memory_store::InMemoryBackendImpl; use impls::postgres_store::PostgresBackendImpl; use std::sync::Arc; @@ -28,12 +30,15 @@ pub(crate) mod vss_service; fn main() { let args: Vec = std::env::args().collect(); - if args.len() != 2 { - eprintln!("Usage: {} ", args[0]); + if args.len() < 2 { + eprintln!("Usage: {} [--in-memory]", args[0]); std::process::exit(1); } - let config = match util::config::load_config(&args[1]) { + let config_path = &args[1]; + let use_in_memory = args.contains(&"--in-memory".to_string()); + + let mut config = match util::config::load_config(config_path) { Ok(cfg) => cfg, Err(e) => { eprintln!("Failed to load configuration: {}", e); @@ -41,6 +46,10 @@ fn main() { }, }; + if use_in_memory { + config.server_config.store_type = StoreType::InMemory; + } + let addr: SocketAddr = match format!("{}:{}", config.server_config.host, config.server_config.port).parse() { Ok(addr) => addr, @@ -67,15 +76,28 @@ fn main() { }, }; let authorizer = Arc::new(NoopAuthorizer {}); - let postgresql_config = config.postgresql_config.expect("PostgreSQLConfig must be defined in config file."); - let endpoint = postgresql_config.to_postgresql_endpoint(); - let db_name = postgresql_config.database; - let store = Arc::new( - PostgresBackendImpl::new(&endpoint, &db_name) - .await - .unwrap(), - ); - println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, db_name); + let store: Arc = match config.server_config.store_type { + StoreType::Postgres => { + let pg_config = config.postgresql_config + .expect("PostgreSQL configuration required for postgres backend"); + let endpoint = pg_config.to_postgresql_endpoint(); + let db_name = pg_config.database; + match PostgresBackendImpl::new(&endpoint, &db_name).await { + Ok(backend) => { + println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, db_name); + Arc::new(backend) + }, + Err(e) => { + eprintln!("Failed to connect to PostgreSQL backend: {}", e); + std::process::exit(1); + }, + } + }, + StoreType::InMemory => { + println!("Using in-memory backend for testing"); + Arc::new(InMemoryBackendImpl::new()) + } + }; let rest_svc_listener = TcpListener::bind(&addr).await.expect("Failed to bind listening port"); println!("Listening for incoming connections on {}", addr); diff --git a/rust/server/src/util/config.rs b/rust/server/src/util/config.rs index cf70daf..e1e1af4 100644 --- a/rust/server/src/util/config.rs +++ b/rust/server/src/util/config.rs @@ -6,10 +6,19 @@ pub(crate) struct Config { pub(crate) postgresql_config: Option, } +#[derive(Deserialize)] +pub(crate) enum StoreType { + #[serde(rename = "postgres")] + Postgres, + #[serde(rename = "in-memory")] + InMemory, +} + #[derive(Deserialize)] pub(crate) struct ServerConfig { pub(crate) host: String, pub(crate) port: u16, + pub(crate) store_type: StoreType, } #[derive(Deserialize)] diff --git a/rust/server/vss-server-config.toml b/rust/server/vss-server-config.toml index 8a013b5..6223d5e 100644 --- a/rust/server/vss-server-config.toml +++ b/rust/server/vss-server-config.toml @@ -1,6 +1,7 @@ [server_config] host = "127.0.0.1" port = 8080 +store_type = "postgres" # "postgres" for using postgresql and "in_memory" for testing purposes [postgresql_config] username = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_USERNAME`