- Replace bank payment flow with a checkout mutation using explicit source context - Return backend errors to players instead of silently falling back to local state - Queue hot state persistence for actors, garages, lockers, orgs, and owned assets
953 lines
33 KiB
Rust
953 lines
33 KiB
Rust
//! Shared transport helpers for oversized extension requests and responses.
|
|
//!
|
|
//! This module provides a routed invoke path that accepts JSON-encoded string
|
|
//! arguments, supports request staging for large payloads, and stores oversized
|
|
//! responses in memory for chunked retrieval by SQF.
|
|
|
|
use arma_rs::{CallContext, Group};
|
|
use serde::Serialize;
|
|
use std::collections::HashMap;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::sync::{LazyLock, Mutex as StdMutex};
|
|
|
|
use crate::{actor, bank, cad, garage, locker, org, v_garage, v_locker};
|
|
|
|
const CHUNK_PREFIX: &str = "FORGE_TRANSPORT_CHUNK:";
|
|
const RESPONSE_CHUNK_SIZE: usize = 12_000;
|
|
const UNSUPPORTED_ROUTE_PREFIX: &str = "Unsupported transport route";
|
|
|
|
static REQUEST_STORE: LazyLock<StdMutex<HashMap<String, String>>> =
|
|
LazyLock::new(|| StdMutex::new(HashMap::new()));
|
|
static RESPONSE_STORE: LazyLock<StdMutex<HashMap<String, Vec<String>>>> =
|
|
LazyLock::new(|| StdMutex::new(HashMap::new()));
|
|
static TRANSFER_SEQUENCE: AtomicU64 = AtomicU64::new(1);
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct ChunkEnvelope {
|
|
transfer_id: String,
|
|
chunk_count: usize,
|
|
total_size: usize,
|
|
}
|
|
|
|
pub fn group() -> Group {
|
|
Group::new()
|
|
.command("invoke", invoke)
|
|
.command("invoke_stored", invoke_stored)
|
|
.group(
|
|
"request",
|
|
Group::new()
|
|
.command("append", append_request_chunk)
|
|
.command("clear", clear_request_chunks),
|
|
)
|
|
.group(
|
|
"response",
|
|
Group::new()
|
|
.command("get", get_response_chunk)
|
|
.command("clear", clear_response_chunks),
|
|
)
|
|
}
|
|
|
|
fn append_request_chunk(transfer_id: String, chunk: String) -> String {
|
|
let mut store = REQUEST_STORE.lock().unwrap();
|
|
store.entry(transfer_id).or_default().push_str(&chunk);
|
|
"OK".to_string()
|
|
}
|
|
|
|
fn clear_request_chunks(transfer_id: String) -> String {
|
|
REQUEST_STORE.lock().unwrap().remove(&transfer_id);
|
|
"OK".to_string()
|
|
}
|
|
|
|
fn get_response_chunk(transfer_id: String, index: String) -> String {
|
|
let chunk_index = match index.parse::<usize>() {
|
|
Ok(value) => value,
|
|
Err(error) => return format!("Error: Invalid response chunk index: {error}"),
|
|
};
|
|
|
|
let store = RESPONSE_STORE.lock().unwrap();
|
|
let Some(chunks) = store.get(&transfer_id) else {
|
|
return format!("Error: Response transfer '{transfer_id}' was not found");
|
|
};
|
|
|
|
chunks.get(chunk_index).cloned().unwrap_or_else(|| {
|
|
format!(
|
|
"Error: Response chunk {} was not found for '{}'",
|
|
chunk_index, transfer_id
|
|
)
|
|
})
|
|
}
|
|
|
|
fn clear_response_chunks(transfer_id: String) -> String {
|
|
RESPONSE_STORE.lock().unwrap().remove(&transfer_id);
|
|
"OK".to_string()
|
|
}
|
|
|
|
fn invoke(call_context: CallContext, function_name: String, arguments_json: String) -> String {
|
|
invoke_internal(call_context, function_name, arguments_json)
|
|
}
|
|
|
|
fn invoke_stored(call_context: CallContext, function_name: String, transfer_id: String) -> String {
|
|
let Some(arguments_json) = REQUEST_STORE.lock().unwrap().remove(&transfer_id) else {
|
|
return format!("Error: Request transfer '{transfer_id}' was not found");
|
|
};
|
|
|
|
invoke_internal(call_context, function_name, arguments_json)
|
|
}
|
|
|
|
fn invoke_internal(
|
|
call_context: CallContext,
|
|
function_name: String,
|
|
arguments_json: String,
|
|
) -> String {
|
|
let arguments: Vec<String> = match parse_transport_arguments(&arguments_json) {
|
|
Ok(value) => value,
|
|
Err(error) => return format!("Error: Invalid transport arguments JSON: {error}"),
|
|
};
|
|
|
|
let result = match route_command(call_context, &function_name, arguments) {
|
|
Ok(value) => value,
|
|
Err(error) => format!("Error: {error}"),
|
|
};
|
|
|
|
chunk_response_if_needed(result)
|
|
}
|
|
|
|
fn parse_transport_arguments(arguments_json: &str) -> Result<Vec<String>, String> {
|
|
let value: serde_json::Value =
|
|
serde_json::from_str(arguments_json).map_err(|error| error.to_string())?;
|
|
parse_transport_argument_value(value)
|
|
}
|
|
|
|
fn parse_transport_argument_value(value: serde_json::Value) -> Result<Vec<String>, String> {
|
|
match value {
|
|
serde_json::Value::Array(values) => Ok(values
|
|
.into_iter()
|
|
.map(|entry| match entry {
|
|
serde_json::Value::String(string_value) => string_value,
|
|
other => other.to_string(),
|
|
})
|
|
.collect()),
|
|
serde_json::Value::String(value) => {
|
|
let trimmed = value.trim();
|
|
if trimmed.starts_with('[') || trimmed.starts_with('{') || trimmed.eq("null") {
|
|
if let Ok(nested_value) = serde_json::from_str::<serde_json::Value>(trimmed) {
|
|
return parse_transport_argument_value(nested_value);
|
|
}
|
|
}
|
|
|
|
Ok(vec![value])
|
|
}
|
|
serde_json::Value::Null => Ok(Vec::new()),
|
|
other => Err(format!("expected string or array but received {}", other)),
|
|
}
|
|
}
|
|
|
|
fn route_command(
|
|
call_context: CallContext,
|
|
function_name: &str,
|
|
arguments: Vec<String>,
|
|
) -> Result<String, String> {
|
|
match function_name {
|
|
"actor:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(actor::get_actor(call_context, arguments[0].clone()))
|
|
}
|
|
"actor:create" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(actor::create_actor(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"actor:update" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(actor::update_actor(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"actor:exists" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(actor::actor_exists(call_context, arguments[0].clone()))
|
|
}
|
|
"actor:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(actor::delete_actor(call_context, arguments[0].clone()))
|
|
}
|
|
"actor:hot:init" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(actor::init_hot_actor(call_context, arguments[0].clone()))
|
|
}
|
|
"actor:hot:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(actor::get_hot_actor(call_context, arguments[0].clone()))
|
|
}
|
|
"actor:hot:override" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(actor::override_hot_actor(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"actor:hot:save" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(actor::save_hot_actor(call_context, arguments[0].clone()))
|
|
}
|
|
"actor:hot:remove" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(actor::remove_hot_actor(call_context, arguments[0].clone()))
|
|
}
|
|
"bank:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(bank::get_bank(call_context, arguments[0].clone()))
|
|
}
|
|
"bank:create" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(bank::create_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"bank:update" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(bank::update_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"bank:exists" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(bank::bank_exists(call_context, arguments[0].clone()))
|
|
}
|
|
"bank:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(bank::delete_bank(call_context, arguments[0].clone()))
|
|
}
|
|
"bank:hot:init" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(bank::init_hot_bank(call_context, arguments[0].clone()))
|
|
}
|
|
"bank:hot:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(bank::get_hot_bank(call_context, arguments[0].clone()))
|
|
}
|
|
"bank:hot:override" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(bank::override_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"bank:hot:patch" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(bank::patch_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"bank:hot:charge_checkout" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(bank::charge_checkout_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"bank:hot:deposit" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(bank::deposit_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"bank:hot:withdraw" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(bank::withdraw_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"bank:hot:deposit_earnings" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(bank::deposit_earnings_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"bank:hot:transfer" => {
|
|
expect_arg_count(function_name, &arguments, 4)?;
|
|
Ok(bank::transfer_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
arguments[3].clone(),
|
|
))
|
|
}
|
|
"bank:hot:validate_pin" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(bank::validate_pin_hot_bank(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"bank:hot:save" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(bank::save_hot_bank(call_context, arguments[0].clone()))
|
|
}
|
|
"bank:hot:remove" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(bank::remove_hot_bank(call_context, arguments[0].clone()))
|
|
}
|
|
"org:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::get_org(arguments[0].clone()))
|
|
}
|
|
"org:create" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(org::create_org(arguments[0].clone(), arguments[1].clone()))
|
|
}
|
|
"org:update" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(org::update_org(arguments[0].clone(), arguments[1].clone()))
|
|
}
|
|
"org:exists" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::org_exists(arguments[0].clone()))
|
|
}
|
|
"org:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::delete_org(arguments[0].clone()))
|
|
}
|
|
"org:hot:init" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::init_hot_org(arguments[0].clone()))
|
|
}
|
|
"org:hot:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::get_hot_org(arguments[0].clone()))
|
|
}
|
|
"org:hot:override" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(org::override_hot_org(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"org:hot:save" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::save_hot_org(arguments[0].clone()))
|
|
}
|
|
"org:hot:remove" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::remove_hot_org(arguments[0].clone()))
|
|
}
|
|
"org:assets:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::get_assets(arguments[0].clone()))
|
|
}
|
|
"org:assets:update" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(org::update_assets(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"org:fleet:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::get_fleet(arguments[0].clone()))
|
|
}
|
|
"org:fleet:update" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(org::update_fleet(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"org:members:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(org::get_members(arguments[0].clone()))
|
|
}
|
|
"org:members:add" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(org::add_member(arguments[0].clone(), arguments[1].clone()))
|
|
}
|
|
"org:members:remove" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(org::remove_member(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"garage:create" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::create_garage(call_context, arguments[0].clone()))
|
|
}
|
|
"garage:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::get_garage(call_context, arguments[0].clone()))
|
|
}
|
|
"garage:add" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(garage::add_vehicle(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"garage:update" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(garage::update_garage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"garage:patch" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(garage::patch_vehicle(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"garage:remove" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(garage::remove_vehicle(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"garage:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::delete_garage(call_context, arguments[0].clone()))
|
|
}
|
|
"garage:exists" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::garage_exists(call_context, arguments[0].clone()))
|
|
}
|
|
"garage:hot:init" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::init_hot_garage(call_context, arguments[0].clone()))
|
|
}
|
|
"garage:hot:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::get_hot_garage(call_context, arguments[0].clone()))
|
|
}
|
|
"garage:hot:override" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(garage::override_hot_garage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"garage:hot:save" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::save_hot_garage(call_context, arguments[0].clone()))
|
|
}
|
|
"garage:hot:remove" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(garage::remove_hot_garage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"garage:hot:add" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(garage::add_hot_vehicle(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"garage:hot:remove_vehicle" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(garage::remove_hot_vehicle(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"locker:create" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::create_locker(call_context, arguments[0].clone()))
|
|
}
|
|
"locker:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::get_locker(call_context, arguments[0].clone()))
|
|
}
|
|
"locker:add" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(locker::add_item(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"locker:update" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(locker::update_locker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"locker:patch" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(locker::patch_item(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"locker:remove" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(locker::remove_item(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"locker:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::delete_locker(call_context, arguments[0].clone()))
|
|
}
|
|
"locker:exists" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::locker_exists(call_context, arguments[0].clone()))
|
|
}
|
|
"locker:hot:init" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::init_hot_locker(call_context, arguments[0].clone()))
|
|
}
|
|
"locker:hot:get" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::get_hot_locker(call_context, arguments[0].clone()))
|
|
}
|
|
"locker:hot:override" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(locker::override_hot_locker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"locker:hot:save" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::save_hot_locker(call_context, arguments[0].clone()))
|
|
}
|
|
"locker:hot:remove" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(locker::remove_hot_locker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:garage:create" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::create_vgarage(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:garage:fetch" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::fetch_vgarage(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:garage:get" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(v_garage::get_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"owned:garage:add" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(v_garage::add_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"owned:garage:remove" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(v_garage::remove_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"owned:garage:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::delete_vgarage(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:garage:exists" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::vgarage_exists(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:garage:hot:init" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::init_hot_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:garage:hot:fetch" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::fetch_hot_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:garage:hot:get" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(v_garage::get_hot_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"owned:garage:hot:override" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(v_garage::override_hot_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"owned:garage:hot:save" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::save_hot_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:garage:hot:remove" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_garage::remove_hot_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:garage:hot:add" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(v_garage::add_hot_vgarage(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"owned:garage:hot:remove_item" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(v_garage::remove_hot_vgarage_item(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"owned:locker:create" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::create_vlocker(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:locker:fetch" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::fetch_vlocker(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:locker:get" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(v_locker::get_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"owned:locker:add" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(v_locker::add_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"owned:locker:remove" => {
|
|
expect_arg_count(function_name, &arguments, 3)?;
|
|
Ok(v_locker::remove_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
arguments[2].clone(),
|
|
))
|
|
}
|
|
"owned:locker:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::delete_vlocker(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:locker:exists" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::vlocker_exists(call_context, arguments[0].clone()))
|
|
}
|
|
"owned:locker:hot:init" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::init_hot_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:locker:hot:fetch" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::fetch_hot_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:locker:hot:get" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(v_locker::get_hot_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"owned:locker:hot:override" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(v_locker::override_hot_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"owned:locker:hot:save" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::save_hot_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"owned:locker:hot:remove" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(v_locker::remove_hot_vlocker(
|
|
call_context,
|
|
arguments[0].clone(),
|
|
))
|
|
}
|
|
"cad:activity:append" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::append_activity(arguments[0].clone()))
|
|
}
|
|
"cad:activity:recent" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::recent_activity(arguments[0].clone()))
|
|
}
|
|
"cad:assignments:list" => {
|
|
expect_arg_count(function_name, &arguments, 0)?;
|
|
Ok(cad::list_assignments())
|
|
}
|
|
"cad:assignments:assign" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(cad::assign_assignment(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"cad:assignments:acknowledge" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(cad::acknowledge_assignment(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"cad:assignments:decline" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(cad::decline_assignment(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"cad:assignments:upsert" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(cad::upsert_assignment(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"cad:assignments:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::delete_assignment(arguments[0].clone()))
|
|
}
|
|
"cad:orders:list" => {
|
|
expect_arg_count(function_name, &arguments, 0)?;
|
|
Ok(cad::list_orders())
|
|
}
|
|
"cad:orders:create" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::create_order(arguments[0].clone()))
|
|
}
|
|
"cad:orders:create_from_context" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::create_order_from_context(arguments[0].clone()))
|
|
}
|
|
"cad:orders:close" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::close_order(arguments[0].clone()))
|
|
}
|
|
"cad:orders:upsert" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(cad::upsert_order(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"cad:orders:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::delete_order(arguments[0].clone()))
|
|
}
|
|
"cad:requests:list" => {
|
|
expect_arg_count(function_name, &arguments, 0)?;
|
|
Ok(cad::list_requests())
|
|
}
|
|
"cad:requests:submit" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::submit_request(arguments[0].clone()))
|
|
}
|
|
"cad:requests:submit_from_context" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::submit_request_from_context(arguments[0].clone()))
|
|
}
|
|
"cad:requests:close" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::close_request(arguments[0].clone()))
|
|
}
|
|
"cad:requests:upsert" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(cad::upsert_request(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"cad:requests:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::delete_request(arguments[0].clone()))
|
|
}
|
|
"cad:profiles:list" => {
|
|
expect_arg_count(function_name, &arguments, 0)?;
|
|
Ok(cad::list_profiles())
|
|
}
|
|
"cad:profiles:update_from_context" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::update_profile_from_context(arguments[0].clone()))
|
|
}
|
|
"cad:profiles:upsert" => {
|
|
expect_arg_count(function_name, &arguments, 2)?;
|
|
Ok(cad::upsert_profile(
|
|
arguments[0].clone(),
|
|
arguments[1].clone(),
|
|
))
|
|
}
|
|
"cad:profiles:delete" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::delete_profile(arguments[0].clone()))
|
|
}
|
|
"cad:groups:build" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::build_groups(arguments[0].clone()))
|
|
}
|
|
"cad:view:hydrate" => {
|
|
expect_arg_count(function_name, &arguments, 1)?;
|
|
Ok(cad::hydrate_view(arguments[0].clone()))
|
|
}
|
|
_ => Err(format!(
|
|
"{UNSUPPORTED_ROUTE_PREFIX} for function '{function_name}'"
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn expect_arg_count(
|
|
function_name: &str,
|
|
arguments: &[String],
|
|
expected_count: usize,
|
|
) -> Result<(), String> {
|
|
if arguments.len() == expected_count {
|
|
return Ok(());
|
|
}
|
|
|
|
Err(format!(
|
|
"Transport route '{}' expected {} arguments but received {}",
|
|
function_name,
|
|
expected_count,
|
|
arguments.len()
|
|
))
|
|
}
|
|
|
|
fn chunk_response_if_needed(result: String) -> String {
|
|
if result.len() <= RESPONSE_CHUNK_SIZE {
|
|
return result;
|
|
}
|
|
|
|
let transfer_id = next_transfer_id("rsp");
|
|
let chunks = split_string_chunks(&result, RESPONSE_CHUNK_SIZE);
|
|
let envelope = ChunkEnvelope {
|
|
transfer_id: transfer_id.clone(),
|
|
chunk_count: chunks.len(),
|
|
total_size: result.len(),
|
|
};
|
|
|
|
RESPONSE_STORE.lock().unwrap().insert(transfer_id, chunks);
|
|
|
|
format!(
|
|
"{CHUNK_PREFIX}{}",
|
|
serde_json::to_string(&envelope)
|
|
.unwrap_or_else(|error| format!("{{\"error\":\"{error}\"}}"))
|
|
)
|
|
}
|
|
|
|
fn next_transfer_id(prefix: &str) -> String {
|
|
let sequence = TRANSFER_SEQUENCE.fetch_add(1, Ordering::Relaxed);
|
|
format!("{prefix}_{sequence}")
|
|
}
|
|
|
|
fn split_string_chunks(input: &str, max_bytes: usize) -> Vec<String> {
|
|
if input.is_empty() {
|
|
return vec![String::new()];
|
|
}
|
|
|
|
let mut chunks = Vec::new();
|
|
let mut chunk_start = 0usize;
|
|
let mut chunk_len = 0usize;
|
|
|
|
for (index, character) in input.char_indices() {
|
|
let char_len = character.len_utf8();
|
|
if chunk_len > 0 && chunk_len + char_len > max_bytes {
|
|
chunks.push(input[chunk_start..index].to_string());
|
|
chunk_start = index;
|
|
chunk_len = 0;
|
|
}
|
|
|
|
chunk_len += char_len;
|
|
}
|
|
|
|
chunks.push(input[chunk_start..].to_string());
|
|
chunks
|
|
}
|