feat: implements table, columns serialization

This commit is contained in:
Namu
2026-04-01 22:11:20 +02:00
parent a91adfc621
commit ef0767f33a
12 changed files with 634 additions and 0 deletions

3
src/lib.rs Normal file
View File

@@ -0,0 +1,3 @@
mod mappings;
mod schemas;
mod query_builders;

66
src/mappings/mod.rs Normal file
View File

@@ -0,0 +1,66 @@
/*
This module is used to make mapping between Rust and SQLite
*/
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize, Default)]
pub enum SQLiteDatatypes {
Integer,
Real,
#[default]
Text,
Blob,
Null,
Numeric,
Any,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Default)]
pub enum SupportedTypes {
I32,
I64,
F64,
#[default]
String,
VecU8,
Bool,
Option,
}
impl From<SupportedTypes> for SQLiteDatatypes {
fn from(t: SupportedTypes) -> Self {
match t {
SupportedTypes::I32 | SupportedTypes::I64 | SupportedTypes::Bool => SQLiteDatatypes::Integer,
SupportedTypes::F64 => SQLiteDatatypes::Real,
SupportedTypes::String => SQLiteDatatypes::Text,
SupportedTypes::VecU8 => SQLiteDatatypes::Blob,
SupportedTypes::Option => SQLiteDatatypes::Any,
}
}
}
impl From<SQLiteDatatypes> for SupportedTypes {
fn from(t: SQLiteDatatypes) -> Self {
match t {
SQLiteDatatypes::Integer => SupportedTypes::I64,
SQLiteDatatypes::Real | SQLiteDatatypes::Numeric => SupportedTypes::F64,
SQLiteDatatypes::Text => SupportedTypes::String,
SQLiteDatatypes::Blob => SupportedTypes::VecU8,
SQLiteDatatypes::Null | SQLiteDatatypes::Any => SupportedTypes::Option,
}
}
}
impl From<SQLiteDatatypes> for String {
fn from(t: SQLiteDatatypes) -> Self {
match t {
SQLiteDatatypes::Integer => String::from("INTEGER"),
SQLiteDatatypes::Real => String::from("REAL"),
SQLiteDatatypes::Text => String::from("TEXT"),
SQLiteDatatypes::Blob => String::from("BLOB"),
SQLiteDatatypes::Any => String::from("ANY"),
SQLiteDatatypes::Null => String::from("NULL"),
SQLiteDatatypes::Numeric => String::from("NUMERIC")
}
}
}

View File

@@ -0,0 +1,109 @@
use crate::mappings::SQLiteDatatypes;
use crate::schemas::entities::Column;
#[derive(Debug, Clone, Copy)]
pub struct ColumnSqlSerializer {}
impl ColumnSqlSerializer {
pub fn new() -> Self {
ColumnSqlSerializer {}
}
pub fn serialize(self, column: Column) -> Result<String, String> {
let mut query = String::from(column.name);
query.push(' ');
query += &*String::from(column.datatype);
query.push(' ');
if !column.nullable {
query += "NOT NULL " // Notice the space at the end
}
if column.unique {
query += "UNIQUE " // Notice the space at the end
}
if column.auto_increment {
query += "AUTO_INCREMENT " // Notice the space at the end
}
if let Some(default) = column.default {
match column.datatype {
SQLiteDatatypes::Text | SQLiteDatatypes::Blob => {
query += &format!("DEFAULT '{}' ", default);
},
SQLiteDatatypes::Numeric | SQLiteDatatypes::Integer | SQLiteDatatypes::Real => {
query += &format!("DEFAULT {} ", default);
},
SQLiteDatatypes::Any | SQLiteDatatypes::Null => {
return Err("No default value can be set with Any and Null".to_string())
}
}
}
if let Some(check_constraint) = column.check_constraint {
query += &format!("CHECK {} ", check_constraint);
}
if column.primary_key {
query += "PRIMARY KEY ";
}
query.push(',');
Ok(query)
}
}
#[cfg(test)]
mod tests {
use super::super::super::schemas::builders::ColumnBuilder;
use crate::mappings::SQLiteDatatypes;
use crate::query_builders::column_sql_serializer::ColumnSqlSerializer;
#[test]
pub fn test_column_sql_serializer() {
let column = ColumnBuilder::new()
.with_name("id".to_string())
.with_datatype(SQLiteDatatypes::Integer)
.with_auto_increment()
.with_primary_key()
.build()
.unwrap();
let serializer = ColumnSqlSerializer::new();
let result = serializer.serialize(column).unwrap();
assert_eq!(result, "id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY ,".to_string());
}
#[test]
pub fn test_column_sql_serializer_with_text_default() {
let column = ColumnBuilder::new()
.with_name("name".to_string())
.with_datatype(SQLiteDatatypes::Text)
.with_default("None".to_string())
.build()
.unwrap();
let serializer = ColumnSqlSerializer::new();
let result = serializer.serialize(column).unwrap();
assert_eq!(result, "name TEXT NOT NULL DEFAULT 'None' ,".to_string());
}
#[test]
pub fn test_column_sql_serializer_with_integer_default() {
let column = ColumnBuilder::new()
.with_name("age".to_string())
.with_datatype(SQLiteDatatypes::Integer)
.with_default("18".to_string())
.build()
.unwrap();
let serializer = ColumnSqlSerializer::new();
let result = serializer.serialize(column).unwrap();
assert_eq!(result, "age INTEGER NOT NULL DEFAULT 18 ,".to_string());
}
}

View File

@@ -0,0 +1,2 @@
mod column_sql_serializer;
mod table_sql_serializer;

View File

@@ -0,0 +1,52 @@
use crate::query_builders::column_sql_serializer::ColumnSqlSerializer;
use crate::schemas::entities::Table;
#[derive(Debug, Clone, Copy)]
pub struct TableSqlSerializer {}
impl TableSqlSerializer {
pub fn new() -> Self {
TableSqlSerializer{}
}
pub fn serialize(self, table: Table) -> String {
let mut query = String::from("CREATE TABLE ");
query += &format!("{} (\n", table.name);
let column_serializer = ColumnSqlSerializer::new();
for column in table.columns {
query += &column_serializer.serialize(column).unwrap();
}
query += ");";
query
}
}
#[cfg(test)]
mod test {
use crate::mappings::SQLiteDatatypes;
use crate::query_builders::table_sql_serializer::TableSqlSerializer;
use crate::schemas::builders::{ColumnBuilder, TableBuilder};
#[test]
pub fn test_table_sql_serializer() {
let column = ColumnBuilder::new()
.with_name("id".to_string())
.with_datatype(SQLiteDatatypes::Integer)
.with_auto_increment()
.build()
.unwrap();
let table = TableBuilder::new()
.with_name("test".to_string())
.with_column(column)
.build();
let serializer = TableSqlSerializer::new();
let result = serializer.serialize(table);
assert_eq!(result, "CREATE TABLE test (\nid INTEGER NOT NULL AUTO_INCREMENT ,);".to_string());
}
}

182
src/schemas/builders.rs Normal file
View File

@@ -0,0 +1,182 @@
use crate::mappings::SQLiteDatatypes;
use crate::schemas::entities::{Column, Table};
pub struct ColumnBuilder {
name: String,
datatype: SQLiteDatatypes,
nullable: bool,
default: Option<String>,
auto_increment: bool,
unique: bool,
check_constraint: Option<String>,
primary_key: bool,
}
impl ColumnBuilder {
pub fn new() -> ColumnBuilder {
ColumnBuilder {
name: String::new(),
datatype: SQLiteDatatypes::Text,
nullable: false,
default: None,
auto_increment: false,
unique: false,
check_constraint: None,
primary_key: false,
}
}
pub fn with_name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn with_datatype(mut self, datatype: SQLiteDatatypes) -> Self {
self.datatype = datatype;
self
}
pub fn with_nullable(mut self) -> Self {
self.nullable = true;
self
}
pub fn with_default(mut self, default: String) -> Self {
self.default = Some(default);
self
}
pub fn with_auto_increment(mut self) -> Self {
self.auto_increment = true;
self
}
pub fn with_unique(mut self) -> Self {
self.unique = true;
self
}
pub fn with_check_constraint(mut self, constraint: String) -> Self {
self.check_constraint = Some(constraint);
self
}
pub fn with_primary_key(mut self) -> Self {
self.primary_key = true;
self
}
pub fn build(self) -> Result<Column, String> {
if self.auto_increment && self.datatype != SQLiteDatatypes::Integer {
return Err("Cannot set AUTO_INCREMENT on non-INTEGER column".to_string());
}
Ok(Column::new(
self.name,
self.datatype,
self.nullable,
self.default,
self.auto_increment,
self.unique,
self.check_constraint,
self.primary_key,
))
}
}
pub struct TableBuilder {
name: String,
columns: Vec<Column>,
strict: bool
}
impl TableBuilder {
pub fn new() -> TableBuilder {
TableBuilder {
name: String::new(),
columns: Vec::new(),
strict: false,
}
}
pub fn with_name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn with_strict(mut self) -> Self {
self.strict = true;
self
}
pub fn with_column(mut self, column: Column) -> Self {
self.columns.push(column);
self
}
pub fn build(self) -> Table {
Table::new(self.name, self.columns, self.strict)
}
}
#[cfg(test)]
mod test {
use crate::mappings::SQLiteDatatypes;
use crate::schemas::builders::{ColumnBuilder, TableBuilder};
#[test]
fn test_column_builder() {
let result = ColumnBuilder::new()
.with_name("name".to_string())
.with_datatype(SQLiteDatatypes::Text)
.with_nullable()
.build();
match result {
Ok(column) => {
assert_eq!(column.name, String::from("name"));
assert_eq!(column.datatype, SQLiteDatatypes::Text);
assert_eq!(column.nullable, true);
},
Err(_) => {
assert!(false, "should not fail");
}
}
}
#[test]
fn test_column_builder_should_fail() {
let result = ColumnBuilder::new()
.with_name("name".to_string())
.with_datatype(SQLiteDatatypes::Blob)
.with_auto_increment()
.build();
match result {
Ok(_) => {
assert!(false, "Should not make auto increment with another type than integer")
},
Err(_) => {
assert!(true);
}
}
}
#[test]
fn test_table_builder() {
let column = ColumnBuilder::new()
.with_name("id".to_string())
.with_datatype(SQLiteDatatypes::Integer)
.with_auto_increment()
.build()
.expect("Failed to create id column");
let table = TableBuilder::new()
.with_name("my_table".to_string())
.with_column(column.clone())
.build();
assert_eq!(table.name, String::from("my_table"));
assert_eq!(*table.columns.first().unwrap(), column);
}
}

46
src/schemas/entities.rs Normal file
View File

@@ -0,0 +1,46 @@
use serde::{Deserialize, Serialize};
use crate::mappings::SQLiteDatatypes;
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct Table {
pub name: String,
pub columns: Vec<Column>,
pub strict: bool
}
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)]
pub struct Column {
pub name: String,
pub datatype: SQLiteDatatypes,
pub nullable: bool,
pub default: Option<String>,
pub auto_increment: bool,
pub unique: bool,
pub check_constraint: Option<String>, // raw check constraint
pub primary_key: bool
}
impl Table {
pub fn new(name: String, columns: Vec<Column>, strict: bool) -> Table {
Table {
name,
columns,
strict
}
}
}
impl Column {
pub fn new(name: String, datatype: SQLiteDatatypes, nullable: bool, default: Option<String>, auto_increment: bool, unique: bool, check_constraint: Option<String>, primary_key: bool) -> Column {
Column {
name,
datatype,
nullable,
default,
auto_increment,
unique,
check_constraint,
primary_key
}
}
}

4
src/schemas/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub(crate) mod reader;
pub(crate) mod entities;
pub(crate) mod builders;
mod writer;

23
src/schemas/reader.rs Normal file
View File

@@ -0,0 +1,23 @@
use std::fs;
use crate::schemas::entities::Table;
pub struct SchemaParsingFacade {}
impl SchemaParsingFacade {
pub fn new() -> SchemaParsingFacade {
SchemaParsingFacade{}
}
pub fn parse(&self, path: String) -> Vec<Table> {
let json_schema = self.read_schema_file(path);
self.parse_schema_file(json_schema)
}
fn read_schema_file(&self, path: String) -> String {
fs::read_to_string(path).expect("Error reading schema files")
}
fn parse_schema_file(&self, json_schema: String) -> Vec<Table> {
serde_json::from_str(&json_schema).expect("Error parsing JSON schema")
}
}

32
src/schemas/writer.rs Normal file
View File

@@ -0,0 +1,32 @@
use std::fs::File;
use std::io::Write;
use crate::schemas::entities::Table;
pub struct SchemaWriter {
tables: Vec<Table>
}
impl SchemaWriter {
pub fn new() -> Self {
SchemaWriter{
tables: Vec::new()
}
}
pub fn add_table(mut self, table: Table) -> Self {
self.tables.push(table);
self
}
// create_schemas create the schema as a JSON file. the name doesn't need to have the ".json"
pub fn create_schemas(self, schema_name: String) -> Result<(), String> {
let schema_file_path = format!("{}.json", schema_name);
let content_result = serde_json::to_string(self.tables.as_slice()).expect("Error serializing tables");
let mut schema_file = File::create(schema_file_path).expect("Failed to create schema file");
schema_file.write_all(content_result.as_bytes()).expect("Error writing in schema file");
Ok(())
}
}