Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaffold v2 #1209

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
049f500
Add support for additional model field types and arrays
kaplanelad Jan 10, 2025
df2579f
clippy
kaplanelad Jan 10, 2025
5a6e946
Merge branch 'master' into add-support-additional-model-field-types-a…
kaplanelad Jan 10, 2025
d2a9b08
revert rust field
kaplanelad Jan 10, 2025
3c62174
Merge branch 'master' into add-support-additional-model-field-types-a…
kaplanelad Jan 12, 2025
331a005
adding rust types for array
kaplanelad Jan 13, 2025
6fc8133
scaffold ui
kaplanelad Jan 13, 2025
a21450e
clippy fixes
kaplanelad Jan 13, 2025
0044172
test
kaplanelad Jan 13, 2025
58bec24
test
kaplanelad Jan 13, 2025
201b17b
test
kaplanelad Jan 13, 2025
fe89071
test
kaplanelad Jan 14, 2025
14b717d
Merge branch 'master' into add-support-additional-model-field-types-a…
kaplanelad Jan 14, 2025
33157cd
Merge branch 'master' into add-support-additional-model-field-types-a…
kaplanelad Jan 19, 2025
9dad097
remove char and text
kaplanelad Jan 19, 2025
8954733
Merge branch 'master' into add-support-additional-model-field-types-a…
kaplanelad Jan 21, 2025
a017dd8
scaffold improvements
kaplanelad Jan 23, 2025
a9c260e
Merge branch 'master' into scaffold-v2
kaplanelad Jan 23, 2025
41a834a
scaffold improvements
kaplanelad Jan 23, 2025
3779387
more snap
kaplanelad Jan 23, 2025
77118f7
more snap
kaplanelad Jan 23, 2025
efe12b8
more changes
kaplanelad Jan 23, 2025
1532706
change file name
kaplanelad Jan 26, 2025
29523db
merge #1212
kaplanelad Jan 26, 2025
80a468c
mapping
kaplanelad Jan 26, 2025
9d7fc4c
docs
kaplanelad Jan 26, 2025
269404f
snap
kaplanelad Jan 26, 2025
e87cc1d
test html and htmx only when asset is provided
kaplanelad Jan 26, 2025
a074ed1
snap
kaplanelad Jan 26, 2025
25baedf
pg tests
kaplanelad Jan 26, 2025
861dd3f
unnecessary html classes
kaplanelad Jan 26, 2025
75513b6
Merge branch 'master' into scaffold-v2
kaplanelad Jan 26, 2025
b66ebc4
Merge branch 'master' into scaffold-v2
kaplanelad Jan 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/loco-gen-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ jobs:
run: |
cargo run -- generate model post \
uuid_uniq:uuid \
uuid_null:uuid_col \
uuid:uuid_col! \
uuid_null:uuid \
uuid:uuid! \
string_null:string \
string:string! \
string_uniq:string^ \
Expand Down Expand Up @@ -220,8 +220,8 @@ jobs:
run: |
cargo run -- generate scaffold --api room \
uuid_uniq:uuid \
uuid_null:uuid_col \
uuid:uuid_col! \
uuid_null:uuid \
uuid:uuid! \
string_null:string \
string:string! \
string_uniq:string^ \
Expand Down
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ axum-extra = { version = "0.10", features = ["cookie"] }
regex = { workspace = true }
fs-err = "2.11.0"
# mailer
tera = "1.19.1"
tera = { workspace = true }
thousands = "0.2.0"
heck = "0.4.0"
heck = { workspace = true }
cruet = "0.13.0"
lettre = { version = "0.11.4", default-features = false, features = [
"builder",
Expand Down Expand Up @@ -160,6 +160,7 @@ bb8 = { version = "0.8.1", optional = true }
scraper = { version = "0.21.0", features = ["deterministic"], optional = true }

[workspace.dependencies]
tera = { version = "1.19.1" }
colored = { version = "2" }
chrono = { version = "0.4", features = ["serde"] }
tracing = "0.1.40"
Expand All @@ -180,6 +181,7 @@ tower-http = { version = "0.6.1", features = [
"set-header",
"compression-full",
] }
heck = "0.4.0"

[dependencies.sea-orm-migration]
optional = true
Expand Down
6 changes: 3 additions & 3 deletions docs-site/content/docs/the-app/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ These fields are ignored if you provide them in your migration command.
For schema data types, you can use the following mapping to understand the schema:

```rust
("uuid", "uuid_uniq"),
("uuid_col", "uuid_null"),
("uuid_col!", "uuid"),
("uuid^", "uuid_uniq"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a special doc about the new array types?

("uuid", "uuid_null"),
("uuid!", "uuid"),
("string", "string_null"),
("string!", "string"),
("string^", "string_uniq"),
Expand Down
5 changes: 4 additions & 1 deletion loco-gen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ path = "src/lib.rs"
[dependencies]

cruet = "0.14.0"
rrgen = "0.5.5"
# rrgen = "0.5.5"
rrgen = { git = "https://github.com/jondot/rrgen" }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
regex = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
colored = { workspace = true }
heck = { workspace = true }
tera = { workspace = true }

clap = { version = "4.4.7", features = ["derive"] }
duct = "0.13"
Expand Down
222 changes: 201 additions & 21 deletions loco-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use serde_json::{json, Value};
mod controller;
use colored::Colorize;
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
str::FromStr,
Expand All @@ -23,6 +24,7 @@ mod model;
#[cfg(feature = "with-db")]
mod scaffold;
pub mod template;
pub mod template_engine;
#[cfg(test)]
mod testutil;

Expand Down Expand Up @@ -64,55 +66,106 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Serialize, Deserialize, Debug)]
struct FieldType {
name: String,
rust: Option<String>,
schema: Option<String>,
col_type: Option<String>,
rust: RustType,
schema: String,
col_type: String,
#[serde(default)]
arity: usize,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
enum RustType {
String(String),
Map(HashMap<String, String>),
}

#[derive(Serialize, Deserialize, Debug)]
struct Mappings {
field_types: Vec<FieldType>,
}
impl Mappings {
pub fn rust_field(&self, field: &str) -> Option<&String> {
fn error_unrecognized_default_field(&self, field: &str) -> Error {
Self::error_unrecognized(field, &self.schema_fields())
}
fn error_unrecognized(field: &str, allow_fields: &[&String]) -> Error {
Error::Message(format!(
"type: `{}` not found. try any of: `{}`",
field,
allow_fields
.iter()
.map(|&s| s.to_string())
.collect::<Vec<String>>()
.join(",")
))
}
pub fn rust_field_with_params(&self, field: &str, params: &Vec<String>) -> Result<&str> {
match field {
"array" | "array^" | "array!" => {
if let RustType::Map(ref map) = self.rust_field_kind(field)? {
if let [single] = params.as_slice() {
let keys: Vec<&String> = map.keys().collect();
Ok(map
.get(single)
.ok_or_else(|| Self::error_unrecognized(field, &keys))?)
} else {
Err(self.error_unrecognized_default_field(field))
}
} else {
panic!("array field should configured as array")
}
}

_ => self.rust_field(field),
}
}

pub fn rust_field_kind(&self, field: &str) -> Result<&RustType> {
self.field_types
.iter()
.find(|f| f.name == field)
.and_then(|f| f.rust.as_ref())
.map(|f| &f.rust)
.ok_or_else(|| self.error_unrecognized_default_field(field))
}
pub fn schema_field(&self, field: &str) -> Option<&String> {

pub fn rust_field(&self, field: &str) -> Result<&str> {
self.field_types
.iter()
.find(|f| f.name == field)
.and_then(|f| f.schema.as_ref())
.map(|f| &f.rust)
.ok_or_else(|| self.error_unrecognized_default_field(field))
.and_then(|rust_type| match rust_type {
RustType::String(s) => Ok(s),
RustType::Map(_) => Err(Error::Message(format!(
"type `{field}` need params to get the rust field type"
))),
})
.map(std::string::String::as_str)
}
pub fn col_type_field(&self, field: &str) -> Option<&String> {

pub fn schema_field(&self, field: &str) -> Result<&str> {
self.field_types
.iter()
.find(|f| f.name == field)
.and_then(|f| f.col_type.as_ref())
.map(|f| f.schema.as_str())
.ok_or_else(|| self.error_unrecognized_default_field(field))
}
pub fn col_type_arity(&self, field: &str) -> Option<usize> {
pub fn col_type_field(&self, field: &str) -> Result<&str> {
self.field_types
.iter()
.find(|f| f.name == field)
.map(|f| f.arity)
.map(|f| f.col_type.as_str())
.ok_or_else(|| self.error_unrecognized_default_field(field))
}
pub fn schema_fields(&self) -> Vec<&String> {
pub fn col_type_arity(&self, field: &str) -> Result<usize> {
self.field_types
.iter()
.filter(|f| f.schema.is_some())
.map(|f| &f.name)
.collect::<Vec<_>>()
.find(|f| f.name == field)
.map(|f| f.arity)
.ok_or_else(|| self.error_unrecognized_default_field(field))
}
pub fn rust_fields(&self) -> Vec<&String> {
self.field_types
.iter()
.filter(|f| f.rust.is_some())
.map(|f| &f.name)
.collect::<Vec<_>>()
pub fn schema_fields(&self) -> Vec<&String> {
self.field_types.iter().map(|f| &f.name).collect::<Vec<_>>()
}
}

Expand Down Expand Up @@ -218,6 +271,11 @@ pub struct AppInfo {
pub app_name: String,
}

#[must_use]
pub fn new_generator() -> RRgen {
RRgen::default().add_template_engine(template_engine::new())
}

/// Generate a component
///
/// # Errors
Expand Down Expand Up @@ -482,4 +540,126 @@ mod tests {
);
}
}

fn test_mapping() -> Mappings {
Mappings {
field_types: vec![
FieldType {
name: "array".to_string(),
rust: RustType::Map(HashMap::from([
("string".to_string(), "Vec<String>".to_string()),
("chat".to_string(), "Vec<String>".to_string()),
("int".to_string(), "Vec<i32>".to_string()),
])),
schema: "array".to_string(),
col_type: "array_null".to_string(),
arity: 1,
},
FieldType {
name: "string^".to_string(),
rust: RustType::String("String".to_string()),
schema: "string_uniq".to_string(),
col_type: "StringUniq".to_string(),
arity: 0,
},
],
}
}

#[test]
fn can_get_schema_fields_from_mapping() {
let mapping = test_mapping();
assert_eq!(
mapping.schema_fields(),
Vec::from([&"array".to_string(), &"string^".to_string()])
);
}

#[test]
fn can_get_col_type_arity_from_mapping() {
let mapping = test_mapping();

assert_eq!(mapping.col_type_arity("array").expect("Get array arity"), 1);
assert_eq!(
mapping
.col_type_arity("string^")
.expect("Get string^ arity"),
0
);

assert!(mapping.col_type_arity("unknown").is_err());
}

#[test]
fn can_get_col_type_field_from_mapping() {
let mapping = test_mapping();

assert_eq!(
mapping.col_type_field("array").expect("Get array field"),
"array_null"
);

assert!(mapping.col_type_field("unknown").is_err());
}

#[test]
fn can_get_schema_field_from_mapping() {
let mapping = test_mapping();

assert_eq!(
mapping.schema_field("string^").expect("Get string^ schema"),
"string_uniq"
);

assert!(mapping.schema_field("unknown").is_err());
}

#[test]
fn can_get_rust_field_from_mapping() {
let mapping = test_mapping();

assert_eq!(
mapping
.rust_field("string^")
.expect("Get string^ rust field"),
"String"
);

assert!(mapping.rust_field("array").is_err());

assert!(mapping.rust_field("unknown").is_err(),);
}

#[test]
fn can_get_rust_field_kind_from_mapping() {
let mapping = test_mapping();

assert!(mapping.rust_field_kind("string^").is_ok());

assert!(mapping.rust_field_kind("unknown").is_err(),);
}

#[test]
fn can_get_rust_field_with_params_from_mapping() {
let mapping = test_mapping();

assert_eq!(
mapping
.rust_field_with_params("string^", &vec!["string".to_string()])
.expect("Get string^ rust field"),
"String"
);

assert_eq!(
mapping
.rust_field_with_params("array", &vec!["string".to_string()])
.expect("Get string^ rust field"),
"Vec<String>"
);
assert!(mapping
.rust_field_with_params("array", &vec!["unknown".to_string()])
.is_err());

assert!(mapping.rust_field_with_params("unknown", &vec![]).is_err());
}
}
Loading
Loading