use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Schema {
#[serde(skip_serializing_if = "Option::is_none")]
pub database: Option<String>,
#[serde(default)]
pub tables: Vec<Table>,
#[serde(default)]
pub functions: Vec<Function>,
}
impl Schema {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_database(database: impl Into<String>) -> Self {
Self {
database: Some(database.into()),
..Self::default()
}
}
pub fn add_table(&mut self, table: Table) -> &mut Self {
self.tables.push(table);
self
}
pub fn add_function(&mut self, function: Function) -> &mut Self {
self.functions.push(function);
self
}
#[must_use]
pub fn table(mut self, table: Table) -> Self {
self.tables.push(table);
self
}
#[must_use]
pub fn function(mut self, function: Function) -> Self {
self.functions.push(function);
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tables.is_empty() && self.functions.is_empty()
}
#[must_use]
pub fn get_table(&self, name: &str) -> Option<&Table> {
self.tables.iter().find(|t| t.name.eq_ignore_ascii_case(name))
}
#[must_use]
pub fn get_function(&self, name: &str) -> Option<&Function> {
self.functions
.iter()
.find(|f| f.name.eq_ignore_ascii_case(name))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Table {
pub name: String,
#[serde(default)]
pub columns: Vec<Column>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Table {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
columns: Vec::new(),
description: None,
}
}
pub fn add_column(&mut self, column: Column) -> &mut Self {
self.columns.push(column);
self
}
#[must_use]
pub fn column(mut self, column: Column) -> Self {
self.columns.push(column);
self
}
#[must_use]
pub fn with_column(mut self, name: impl Into<String>, data_type: impl Into<String>) -> Self {
self.columns.push(Column::new(name, data_type));
self
}
#[must_use]
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
#[must_use]
pub fn get_column(&self, name: &str) -> Option<&Column> {
self.columns
.iter()
.find(|c| c.name.eq_ignore_ascii_case(name))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Column {
pub name: String,
pub data_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Column {
#[must_use]
pub fn new(name: impl Into<String>, data_type: impl Into<String>) -> Self {
Self {
name: name.into(),
data_type: data_type.into(),
description: None,
}
}
#[must_use]
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
#[must_use]
pub fn string(name: impl Into<String>) -> Self {
Self::new(name, "string")
}
#[must_use]
pub fn long(name: impl Into<String>) -> Self {
Self::new(name, "long")
}
#[must_use]
pub fn real(name: impl Into<String>) -> Self {
Self::new(name, "real")
}
#[must_use]
pub fn bool(name: impl Into<String>) -> Self {
Self::new(name, "bool")
}
#[must_use]
pub fn datetime(name: impl Into<String>) -> Self {
Self::new(name, "datetime")
}
#[must_use]
pub fn timespan(name: impl Into<String>) -> Self {
Self::new(name, "timespan")
}
#[must_use]
pub fn guid(name: impl Into<String>) -> Self {
Self::new(name, "guid")
}
#[must_use]
pub fn dynamic(name: impl Into<String>) -> Self {
Self::new(name, "dynamic")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Function {
pub name: String,
#[serde(default)]
pub parameters: Vec<Parameter>,
pub return_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Function {
#[must_use]
pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
Self {
name: name.into(),
parameters: Vec::new(),
return_type: return_type.into(),
body: None,
description: None,
}
}
pub fn add_parameter(&mut self, param: Parameter) -> &mut Self {
self.parameters.push(param);
self
}
#[must_use]
pub fn param(mut self, name: impl Into<String>, data_type: impl Into<String>) -> Self {
self.parameters.push(Parameter::new(name, data_type));
self
}
#[must_use]
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = Some(body.into());
self
}
#[must_use]
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Parameter {
pub name: String,
pub data_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_value: Option<String>,
}
impl Parameter {
#[must_use]
pub fn new(name: impl Into<String>, data_type: impl Into<String>) -> Self {
Self {
name: name.into(),
data_type: data_type.into(),
default_value: None,
}
}
#[must_use]
pub fn default(mut self, value: impl Into<String>) -> Self {
self.default_value = Some(value.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schema_builder() {
let schema = Schema::with_database("SecurityDB")
.table(
Table::new("SecurityEvent")
.with_column("TimeGenerated", "datetime")
.with_column("Account", "string")
.with_column("EventID", "long")
.with_column("Computer", "string"),
)
.table(
Table::new("SigninLogs")
.with_column("TimeGenerated", "datetime")
.with_column("UserPrincipalName", "string")
.with_column("IPAddress", "string")
.with_column("ResultType", "string"),
);
assert_eq!(schema.database, Some("SecurityDB".to_string()));
assert_eq!(schema.tables.len(), 2);
assert_eq!(schema.tables[0].columns.len(), 4);
}
#[test]
fn test_schema_serialization() {
let schema = Schema::new().table(
Table::new("Test")
.with_column("Id", "long")
.with_column("Name", "string"),
);
let json = serde_json::to_string(&schema).unwrap();
let parsed: Schema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.tables.len(), 1);
assert_eq!(parsed.tables[0].name, "Test");
assert_eq!(parsed.tables[0].columns.len(), 2);
}
}