use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
pub const QUERY_BUNDLE_VERSION: u32 = 4;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum QueryParamType {
Bool,
I64,
F64,
F32,
String,
DateTime,
Bytes,
Value,
Object,
Array(Box<QueryParamType>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QueryParameter {
pub name: String,
pub ty: QueryParamType,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct QueryBundle {
pub version: u32,
pub read_routes: BTreeMap<String, crate::ReadBatch>,
pub write_routes: BTreeMap<String, crate::WriteBatch>,
pub read_parameters: BTreeMap<String, Vec<QueryParameter>>,
pub write_parameters: BTreeMap<String, Vec<QueryParameter>>,
}
impl Default for QueryBundle {
fn default() -> Self {
Self {
version: QUERY_BUNDLE_VERSION,
read_routes: BTreeMap::new(),
write_routes: BTreeMap::new(),
read_parameters: BTreeMap::new(),
write_parameters: BTreeMap::new(),
}
}
}
pub struct RegisteredReadQuery {
pub name: &'static str,
pub build: fn() -> crate::ReadBatch,
pub parameters: fn() -> Vec<QueryParameter>,
}
pub struct RegisteredWriteQuery {
pub name: &'static str,
pub build: fn() -> crate::WriteBatch,
pub parameters: fn() -> Vec<QueryParameter>,
}
inventory::collect!(RegisteredReadQuery);
inventory::collect!(RegisteredWriteQuery);
#[derive(Debug)]
pub enum GenerateError {
DuplicateQueryName(String),
Io(std::io::Error),
Json(sonic_rs::Error),
UnsupportedVersion {
found: u32,
expected: u32,
},
}
impl std::fmt::Display for GenerateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DuplicateQueryName(name) => {
write!(f, "duplicate generated query name: {name}")
}
Self::Io(err) => write!(f, "io error: {err}"),
Self::Json(err) => write!(f, "json error: {err}"),
Self::UnsupportedVersion { found, expected } => {
write!(
f,
"unsupported query bundle version {found} (expected {expected})"
)
}
}
}
}
impl std::error::Error for GenerateError {}
impl From<std::io::Error> for GenerateError {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
impl From<sonic_rs::Error> for GenerateError {
fn from(value: sonic_rs::Error) -> Self {
Self::Json(value)
}
}
pub fn build_query_bundle() -> Result<QueryBundle, GenerateError> {
let mut bundle = QueryBundle::default();
for registered in inventory::iter::<RegisteredReadQuery> {
if bundle.read_routes.contains_key(registered.name)
|| bundle.write_routes.contains_key(registered.name)
{
return Err(GenerateError::DuplicateQueryName(
registered.name.to_string(),
));
}
bundle
.read_routes
.insert(registered.name.to_string(), (registered.build)());
bundle
.read_parameters
.insert(registered.name.to_string(), (registered.parameters)());
}
for registered in inventory::iter::<RegisteredWriteQuery> {
if bundle.read_routes.contains_key(registered.name)
|| bundle.write_routes.contains_key(registered.name)
{
return Err(GenerateError::DuplicateQueryName(
registered.name.to_string(),
));
}
bundle
.write_routes
.insert(registered.name.to_string(), (registered.build)());
bundle
.write_parameters
.insert(registered.name.to_string(), (registered.parameters)());
}
Ok(bundle)
}
pub fn serialize_query_bundle(bundle: &QueryBundle) -> Result<Vec<u8>, GenerateError> {
Ok(sonic_rs::to_vec_pretty(bundle)?)
}
pub fn deserialize_query_bundle(bytes: &[u8]) -> Result<QueryBundle, GenerateError> {
let bundle: QueryBundle = sonic_rs::from_slice(bytes)?;
if bundle.version != QUERY_BUNDLE_VERSION {
return Err(GenerateError::UnsupportedVersion {
found: bundle.version,
expected: QUERY_BUNDLE_VERSION,
});
}
Ok(bundle)
}
pub fn write_query_bundle_to_path<P: AsRef<Path>>(
bundle: &QueryBundle,
path: P,
) -> Result<(), GenerateError> {
let bytes = serialize_query_bundle(bundle)?;
std::fs::write(path, bytes)?;
Ok(())
}
pub fn read_query_bundle_from_path<P: AsRef<Path>>(path: P) -> Result<QueryBundle, GenerateError> {
let bytes = std::fs::read(path)?;
deserialize_query_bundle(&bytes)
}
pub fn generate() -> Result<PathBuf, GenerateError> {
generate_to_path("queries.json")
}
pub fn generate_to_path<P: AsRef<Path>>(path: P) -> Result<PathBuf, GenerateError> {
let path = path.as_ref();
let bundle = build_query_bundle()?;
write_query_bundle_to_path(&bundle, path)?;
Ok(path.to_path_buf())
}