Skip to content

Commit

Permalink
refactor(code): improve handling of "StructField"s by splitting respo…
Browse files Browse the repository at this point in the history
…nsibility and improving documentation

re #83 (comment)
  • Loading branch information
hasezoey committed Oct 1, 2023
1 parent ee79ee7 commit d3014bd
Showing 1 changed file with 47 additions and 51 deletions.
98 changes: 47 additions & 51 deletions src/code.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use heck::{ToPascalCase, ToSnakeCase};
use indoc::indoc;
use std::borrow::Cow;

use crate::parser::{ParsedTableMacro, FILE_SIGNATURE};
use crate::parser::{ParsedColumnMacro, ParsedTableMacro, FILE_SIGNATURE};
use crate::{GenerationConfig, TableOptions};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -73,12 +74,41 @@ pub struct StructField {
/// Name for the field
// TODO: should this be a Ident instead of a string?
pub name: String,
/// Rust type of the final field
/// Base Rust type, like "String" or "i32" or "u32"
pub base_type: String,

/// Indicate that this field is optional
pub is_optional: bool,
}

impl StructField {
/// Assemble the current options into a rust type, like `base_type: String, is_optional: true` to `Option<String>`
pub fn to_rust_type(&self) -> Cow<str> {
if self.is_optional {
return format!("Option<{}>", self.base_type).into();
}

return self.base_type.as_str().into();
}
}

impl From<&ParsedColumnMacro> for StructField {
fn from(value: &ParsedColumnMacro) -> Self {
let name = value.name.to_string();
// convert integers to proper rust integers
let base_type = if value.is_unsigned {
value.ty.replace('i', "u")
} else {
value.ty.clone()
};

Self {
name,
base_type,
is_optional: value.is_nullable,
}
}
}

impl<'a> Struct<'a> {
/// Create a new instance
pub fn new(
Expand Down Expand Up @@ -151,7 +181,11 @@ impl<'a> Struct<'a> {
)
}

/// Assemble all fields the struct will have
/// Convert [ParsedColumnMacro]'s to [StructField]'s
///
/// Fields filtered out:
/// - in Create-Structs: auto-generated fields
/// - in Update-Structs: the primary key(s)
fn fields(&self) -> Vec<StructField> {
self.table
.columns
Expand All @@ -174,48 +208,7 @@ impl<'a> Struct<'a> {
StructType::Create => !is_autogenerated,
}
})
.map(|c| {
let name = c.name.to_string();
let base_type = if c.is_unsigned {
c.ty.replace('i', "u")
} else {
c.ty.clone()
};
let base_type = if c.is_nullable {
format!("Option<{}>", base_type)
} else {
base_type
};
let mut is_optional = false;

let is_pk = self
.table
.primary_key_columns
.iter()
.any(|pk| pk.to_string().eq(name.as_str()));
let is_autogenerated = self
.opts
.autogenerated_columns
.as_deref()
.unwrap_or_default()
.contains(&c.name.to_string().as_str());
// let is_fk = table.foreign_keys.iter().any(|fk| fk.1.to_string().eq(field_name.as_str()));

match self.ty {
StructType::Read => {}
StructType::Update => {
// all non-key fields should be optional in Form structs (to allow partial updates)
is_optional = !is_pk || is_autogenerated;
}
StructType::Create => {}
}

StructField {
name,
base_type,
is_optional,
}
})
.map(StructField::from)
.collect()
}

Expand Down Expand Up @@ -250,11 +243,14 @@ impl<'a> Struct<'a> {
let mut lines = vec![];
for f in fields.iter() {
let field_name = &f.name;
let field_type = if f.is_optional {
format!("Option<{}>", f.base_type)
} else {
f.base_type.clone()
};
let mut field_type = f.to_rust_type();

// always wrap a field in "Option<>" for a update struct, instead of flat options
// because otherwise you could not differentiate between "Dont touch this field" and "Set field to null"
// also see https://github.com/Wulf/dsync/pull/83#issuecomment-1741905691
if self.ty == StructType::Update {
field_type = format!("Option<{}>", field_type).into();
}

lines.push(format!(r#" pub {field_name}: {field_type},"#));
}
Expand Down

0 comments on commit d3014bd

Please sign in to comment.