Skip to content

Commit

Permalink
feat: add options "create-str" and "update-str"
Browse files Browse the repository at this point in the history
  • Loading branch information
hasezoey committed Sep 30, 2023
1 parent 8a68c01 commit 3f1c51d
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- replace `structopt` with `clap`
- add subcommand to generate shell completions
- function `generate_files` now takes in `&Path`s instead of `PathBuf`s
- add option `create-str` to set `Create*` structs string type
- add option `update-str` to set `Update*` structs string type

## 0.0.16

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ cargo install dsync
* `--schema-path`: (optional) set a custom schema import path, default `crate::schema::`
* `--no-serde`: (optional) if set, does not output any serde related code
* `--no-crud`: (optional) Do not generate the CRUD functions for generated models
* `--create-str`: (optional) Set which string type to use for `Create*` structs (possible are `string`, `str`, `cow`)
* `--update-str`: (optional) Set which string type to use for `Update*` structs (possible are `string`, `str`, `cow`)
* note: the CLI has fail-safes to prevent accidental file overwriting
```sh
Expand Down
37 changes: 34 additions & 3 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clap::{CommandFactory, Parser, Subcommand};
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{generate, Shell};
use dsync::FileChangeStatus;
use dsync::{error::IOErrorToError, GenerationConfig, TableOptions};
use dsync::{FileChangeStatus, StringType};
use std::collections::HashMap;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
Expand Down Expand Up @@ -82,6 +82,35 @@ pub struct MainOptions {
/// Do not generate the CRUD (impl) functions for generated models
#[arg(long = "no-crud")]
pub no_crud: bool,

/// Set which string type to use for Create* structs
#[arg(long = "create-str", default_value = "string")]
pub create_str: StringTypeCli,

/// Set which string type to use for Update* structs
#[arg(long = "update-str", default_value = "string")]
pub update_str: StringTypeCli,
}

#[derive(Debug, ValueEnum, Clone, PartialEq, Default)]
pub enum StringTypeCli {
/// Use "String"
#[default]
String,
/// Use "&str"
Str,
/// Use "Cow<str>"
Cow,
}

impl From<StringTypeCli> for StringType {
fn from(value: StringTypeCli) -> Self {
match value {
StringTypeCli::String => StringType::String,
StringTypeCli::Str => StringType::Str,
StringTypeCli::Cow => StringType::Cow,
}
}
}

fn main() {
Expand Down Expand Up @@ -125,7 +154,9 @@ fn actual_main() -> dsync::Result<()> {

let cols = args.autogenerated_columns.unwrap_or_default();
let mut default_table_options = TableOptions::default()
.autogenerated_columns(cols.iter().map(|t| t.as_str()).collect::<Vec<&str>>());
.autogenerated_columns(cols.iter().map(|t| t.as_str()).collect::<Vec<&str>>())
.create_str_type(args.create_str.into())
.update_str_type(args.update_str.into());

#[cfg(feature = "tsync")]
if args.tsync {
Expand Down
32 changes: 28 additions & 4 deletions src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,18 +234,33 @@ impl<'a> Struct<'a> {
.collect::<Vec<String>>()
.join(" ");

let lifetimes = {
let lifetimes = match self.ty {
StructType::Read => "",
StructType::Update => self.opts.get_update_str_type().get_lifetime(),
StructType::Create => self.opts.get_create_str_type().get_lifetime(),
};

if lifetimes.is_empty() {
String::new()
} else {
format!("<{}>", lifetimes)
}
};

let struct_code = format!(
indoc! {r#"
{tsync_attr}{derive_attr}
#[diesel(table_name={table_name}{primary_key}{belongs_to})]
pub struct {struct_name} {{
pub struct {struct_name}{lifetimes} {{
$COLUMNS$
}}
"#},
tsync_attr = self.attr_tsync(),
derive_attr = self.attr_derive(),
table_name = table.name,
struct_name = ty.format(&table.struct_name),
lifetimes = lifetimes,
primary_key = if ty != StructType::Read {
"".to_string()
} else {
Expand All @@ -255,17 +270,26 @@ impl<'a> Struct<'a> {
"".to_string()
} else {
belongs_to
}
},
);

let fields = self.fields();
let mut lines = vec![];
for f in fields.iter() {
let field_name = &f.name;
let base_type = if f.base_type == "String" {
match self.ty {
StructType::Read => &f.base_type,
StructType::Update => self.opts.get_update_str_type().as_str(),
StructType::Create => self.opts.get_create_str_type().as_str(),
}
} else {
&f.base_type
};
let field_type = if f.is_optional {
format!("Option<{}>", f.base_type)
format!("Option<{}>", base_type)
} else {
f.base_type.clone()
base_type.into()
};

lines.push(format!(r#" pub {field_name}: {field_type},"#));
Expand Down
63 changes: 63 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@ use std::collections::HashMap;
use std::fmt::Display;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum StringType {
/// Use "String"
#[default]
String,
/// Use "&str"
Str,
/// Use "Cow<str>"
Cow,
}

impl StringType {
/// Get the current [StringType] as a rust type string
pub fn as_str(&self) -> &'static str {
match self {
StringType::String => "String",
StringType::Str => "&'a str",
StringType::Cow => "Cow<'a, str>",
}
}

/// Get the lifetime used for the current [StringType]
pub fn get_lifetime(&self) -> &'static str {
match self {
StringType::String => "",
StringType::Str => "'a",
StringType::Cow => "'a",
}
}
}

/// Options for a individual table
#[derive(Debug, Clone)]
pub struct TableOptions<'a> {
Expand All @@ -33,6 +64,12 @@ pub struct TableOptions<'a> {

/// Generates the CRUD functions for generated models
fns: bool,

/// Determines which string type to use for Create* structs
create_str_type: StringType,

/// Determines which string type to use for Update* structs
update_str_type: StringType,
}

impl<'a> TableOptions<'a> {
Expand All @@ -58,6 +95,14 @@ impl<'a> TableOptions<'a> {
self.fns
}

pub fn get_create_str_type(&self) -> StringType {
self.create_str_type
}

pub fn get_update_str_type(&self) -> StringType {
self.update_str_type
}

pub fn get_autogenerated_columns(&self) -> &[&'_ str] {
self.autogenerated_columns.as_deref().unwrap_or_default()
}
Expand Down Expand Up @@ -103,6 +148,20 @@ impl<'a> TableOptions<'a> {
}
}

pub fn create_str_type(self, type_: StringType) -> Self {
Self {
create_str_type: type_,
..self
}
}

pub fn update_str_type(self, type_: StringType) -> Self {
Self {
update_str_type: type_,
..self
}
}

/// Fills any `None` properties with values from another TableConfig
pub fn apply_defaults(&self, other: &TableOptions<'a>) -> Self {
Self {
Expand All @@ -118,6 +177,8 @@ impl<'a> TableOptions<'a> {

use_serde: self.use_serde || other.use_serde,
fns: self.fns || other.fns,
create_str_type: other.create_str_type,
update_str_type: other.update_str_type,
}
}
}
Expand All @@ -133,6 +194,8 @@ impl<'a> Default for TableOptions<'a> {
use_async: Default::default(),
use_serde: true,
fns: true,
create_str_type: Default::default(),
update_str_type: Default::default(),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions test/create_update_str_cow/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod todos;
92 changes: 92 additions & 0 deletions test/create_update_str_cow/models/todos/generated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* @generated and managed by dsync */

use crate::diesel::*;
use crate::schema::*;
use diesel::QueryResult;
use serde::{Deserialize, Serialize};


type ConnectionType = diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;

#[derive(Debug, Serialize, Deserialize, Clone, Queryable, Insertable, AsChangeset, Selectable)]
#[diesel(table_name=todos, primary_key(text))]
pub struct Todo {
pub text: String,
pub text_nullable: Option<String>,
pub varchar: String,
pub varchar_nullable: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Queryable, Insertable, AsChangeset)]
#[diesel(table_name=todos)]
pub struct CreateTodo<'a> {
pub text: Cow<'a, str>,
pub text_nullable: Option<Cow<'a, str>>,
pub varchar: Cow<'a, str>,
pub varchar_nullable: Option<Cow<'a, str>>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Queryable, Insertable, AsChangeset, Default)]
#[diesel(table_name=todos)]
pub struct UpdateTodo<'a> {
pub text_nullable: Option<Cow<'a, str>>,
pub varchar: Option<Cow<'a, str>>,
pub varchar_nullable: Option<Cow<'a, str>>,
}


#[derive(Debug, Serialize)]
pub struct PaginationResult<T> {
pub items: Vec<T>,
pub total_items: i64,
/// 0-based index
pub page: i64,
pub page_size: i64,
pub num_pages: i64,
}

impl Todo {

pub fn create(db: &mut ConnectionType, item: &CreateTodo) -> QueryResult<Self> {
use crate::schema::todos::dsl::*;

insert_into(todos).values(item).get_result::<Self>(db)
}

pub fn read(db: &mut ConnectionType, param_text: String) -> QueryResult<Self> {
use crate::schema::todos::dsl::*;

todos.filter(text.eq(param_text)).first::<Self>(db)
}

/// Paginates through the table where page is a 0-based index (i.e. page 0 is the first page)
pub fn paginate(db: &mut ConnectionType, page: i64, page_size: i64) -> QueryResult<PaginationResult<Self>> {
use crate::schema::todos::dsl::*;

let page_size = if page_size < 1 { 1 } else { page_size };
let total_items = todos.count().get_result(db)?;
let items = todos.limit(page_size).offset(page * page_size).load::<Self>(db)?;

Ok(PaginationResult {
items,
total_items,
page,
page_size,
/* ceiling division of integers */
num_pages: total_items / page_size + i64::from(total_items % page_size != 0)
})
}

pub fn update(db: &mut ConnectionType, param_text: String, item: &UpdateTodo) -> QueryResult<Self> {
use crate::schema::todos::dsl::*;

diesel::update(todos.filter(text.eq(param_text))).set(item).get_result(db)
}

pub fn delete(db: &mut ConnectionType, param_text: String) -> QueryResult<usize> {
use crate::schema::todos::dsl::*;

diesel::delete(todos.filter(text.eq(param_text))).execute(db)
}

}
2 changes: 2 additions & 0 deletions test/create_update_str_cow/models/todos/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub use generated::*;
pub mod generated;
9 changes: 9 additions & 0 deletions test/create_update_str_cow/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
diesel::table! {
todos (text) {
text -> Text,
text_nullable -> Nullable<Text>,
#[max_length = 255]
varchar -> Varchar,
varchar_nullable -> Nullable<Varchar>,
}
}
7 changes: 7 additions & 0 deletions test/create_update_str_cow/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

cd $SCRIPT_DIR

cargo run -- -i schema.rs -o models -g id -g created_at -g updated_at -c "diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::PgConnection>>" --create-str=cow --update-str=cow
1 change: 1 addition & 0 deletions test/create_update_str_str/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod todos;
Loading

0 comments on commit 3f1c51d

Please sign in to comment.