From 424e589547ba6ce10813a26f63e2968aa17e1f34 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 13 Feb 2026 23:29:12 +0900 Subject: [PATCH] Apply escaped --- .../changepack_log_1gif03GIm-FpqHt2Zs8Or.json | 1 + Cargo.lock | 20 ++++----- crates/vespertide-exporter/src/seaorm/mod.rs | 33 ++++++++++++++- .../vespertide-exporter/src/sqlalchemy/mod.rs | 39 ++++++++++++++++- .../vespertide-exporter/src/sqlmodel/mod.rs | 42 +++++++++++++++++-- 5 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 .changepacks/changepack_log_1gif03GIm-FpqHt2Zs8Or.json diff --git a/.changepacks/changepack_log_1gif03GIm-FpqHt2Zs8Or.json b/.changepacks/changepack_log_1gif03GIm-FpqHt2Zs8Or.json new file mode 100644 index 0000000..2f84d9e --- /dev/null +++ b/.changepacks/changepack_log_1gif03GIm-FpqHt2Zs8Or.json @@ -0,0 +1 @@ +{"changes":{"crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch","crates/vespertide-loader/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch","crates/vespertide-naming/Cargo.toml":"Patch","crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide/Cargo.toml":"Patch","crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide-macro/Cargo.toml":"Patch"},"note":"Apply escaped into default_value","date":"2026-02-13T14:28:40.532707200Z"} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9b41aa8..100ecc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3343,7 +3343,7 @@ dependencies = [ [[package]] name = "vespertide" -version = "0.1.44" +version = "0.1.45" dependencies = [ "vespertide-core", "vespertide-macro", @@ -3351,7 +3351,7 @@ dependencies = [ [[package]] name = "vespertide-cli" -version = "0.1.44" +version = "0.1.45" dependencies = [ "anyhow", "assert_cmd", @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "vespertide-config" -version = "0.1.44" +version = "0.1.45" dependencies = [ "clap", "schemars", @@ -3390,7 +3390,7 @@ dependencies = [ [[package]] name = "vespertide-core" -version = "0.1.44" +version = "0.1.45" dependencies = [ "rstest", "schemars", @@ -3402,7 +3402,7 @@ dependencies = [ [[package]] name = "vespertide-exporter" -version = "0.1.44" +version = "0.1.45" dependencies = [ "insta", "rstest", @@ -3414,7 +3414,7 @@ dependencies = [ [[package]] name = "vespertide-loader" -version = "0.1.44" +version = "0.1.45" dependencies = [ "anyhow", "rstest", @@ -3429,7 +3429,7 @@ dependencies = [ [[package]] name = "vespertide-macro" -version = "0.1.44" +version = "0.1.45" dependencies = [ "proc-macro2", "quote", @@ -3446,11 +3446,11 @@ dependencies = [ [[package]] name = "vespertide-naming" -version = "0.1.44" +version = "0.1.45" [[package]] name = "vespertide-planner" -version = "0.1.44" +version = "0.1.45" dependencies = [ "insta", "rstest", @@ -3461,7 +3461,7 @@ dependencies = [ [[package]] name = "vespertide-query" -version = "0.1.44" +version = "0.1.45" dependencies = [ "insta", "rstest", diff --git a/crates/vespertide-exporter/src/seaorm/mod.rs b/crates/vespertide-exporter/src/seaorm/mod.rs index 166b5ea..f048a47 100644 --- a/crates/vespertide-exporter/src/seaorm/mod.rs +++ b/crates/vespertide-exporter/src/seaorm/mod.rs @@ -301,6 +301,9 @@ fn format_default_value(value: &StringOrBool, column_type: &ColumnType) -> Strin trimmed }; + // Escape double quotes for embedding in Rust attribute strings + let escaped = cleaned.replace('"', "\\\""); + // Format based on column type match column_type { // Numeric types: no quotes @@ -320,7 +323,7 @@ fn format_default_value(value: &StringOrBool, column_type: &ColumnType) -> Strin match values { EnumValues::String(_) => { // String enum: use the string value as-is with quotes - format!("default_value = \"{}\"", cleaned) + format!("default_value = \"{}\"", escaped) } EnumValues::Integer(int_values) => { // Integer enum: can be either a number or a variant name @@ -342,7 +345,7 @@ fn format_default_value(value: &StringOrBool, column_type: &ColumnType) -> Strin } // All other types: use quotes _ => { - format!("default_value = \"{}\"", cleaned) + format!("default_value = \"{}\"", escaped) } } } @@ -3376,4 +3379,30 @@ mod tests { // Should have original table name without prefix assert!(result.contains("#[sea_orm(table_name = \"users\")]")); } + + #[test] + fn test_json_default_value_escapes_double_quotes() { + let table = TableDef { + name: "configs".into(), + description: None, + columns: vec![ColumnDef { + name: "data".into(), + r#type: ColumnType::Simple(SimpleColumnType::Json), + nullable: false, + default: Some(r#"{"hello": "world"}"#.into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }], + constraints: vec![], + }; + let rendered = render_entity(&table); + assert!( + rendered.contains(r#"default_value = "{\"hello\": \"world\"}"#), + "Expected escaped quotes in default_value, got: {}", + rendered + ); + } } diff --git a/crates/vespertide-exporter/src/sqlalchemy/mod.rs b/crates/vespertide-exporter/src/sqlalchemy/mod.rs index 126f3f6..38b8534 100644 --- a/crates/vespertide-exporter/src/sqlalchemy/mod.rs +++ b/crates/vespertide-exporter/src/sqlalchemy/mod.rs @@ -435,13 +435,15 @@ fn render_column( // Default value if let Some(ref default) = col.default { let default_str = default.to_sql(); + // Escape double quotes for embedding in Python strings + let escaped = default_str.replace('"', "\\\""); // Check if it's a function call or literal if default_str.contains('(') { - attrs.push(format!("server_default=text(\"{}\")", default_str)); + attrs.push(format!("server_default=text(\"{}\")", escaped)); } else if default_str.starts_with('\'') || default_str.starts_with('"') { attrs.push(format!("server_default={}", default_str)); } else { - attrs.push(format!("server_default=\"{}\"", default_str)); + attrs.push(format!("server_default=\"{}\"", escaped)); } } @@ -1484,4 +1486,37 @@ mod tests { used2.add_column_type(&ColumnType::Simple(SimpleColumnType::Integer), true); assert!(used2.needs_optional); } + + #[test] + fn test_json_default_value_escapes_double_quotes() { + let table = TableDef { + name: "configs".into(), + description: None, + columns: vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + ColumnDef { + name: "data".into(), + r#type: ColumnType::Simple(SimpleColumnType::Json), + nullable: false, + default: Some(r#"{"hello": "world"}"#.into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + ], + constraints: vec![TableConstraint::PrimaryKey { + auto_increment: false, + columns: vec!["id".into()], + }], + }; + + let result = render_entity(&table).unwrap(); + assert!( + result.contains(r#"server_default="{\"hello\": \"world\"}"#), + "Expected escaped quotes in server_default, got: {}", + result + ); + } } diff --git a/crates/vespertide-exporter/src/sqlmodel/mod.rs b/crates/vespertide-exporter/src/sqlmodel/mod.rs index 006b092..6bac4b8 100644 --- a/crates/vespertide-exporter/src/sqlmodel/mod.rs +++ b/crates/vespertide-exporter/src/sqlmodel/mod.rs @@ -374,11 +374,13 @@ fn render_column( // Default value handling if let Some(ref default) = col.default { let default_str = default.to_sql(); + // Escape double quotes for embedding in Python strings + let escaped = default_str.replace('"', "\\\""); // For server-side defaults, use sa_column_kwargs if default_str.contains('(') { field_args.push(format!( "sa_column_kwargs={{\"server_default\": text(\"{}\")}}", - default_str + escaped )); } else if default_str == "true" { field_args.push("default=True".into()); @@ -387,14 +389,15 @@ fn render_column( } else if default_str.starts_with('\'') || default_str.starts_with('"') { // String literal - strip quotes for Python let stripped = default_str.trim_matches(|c| c == '\'' || c == '"'); - field_args.push(format!("default=\"{}\"", stripped)); + let stripped_escaped = stripped.replace('"', "\\\""); + field_args.push(format!("default=\"{}\"", stripped_escaped)); } else if default_str.parse::().is_ok() { field_args.push(format!("default={}", default_str)); } else { // Assume it's a server default field_args.push(format!( "sa_column_kwargs={{\"server_default\": text(\"{}\")}}", - default_str + escaped )); } } else if col.nullable { @@ -1343,4 +1346,37 @@ mod tests { used.add_column_type(&ColumnType::Simple(SimpleColumnType::Integer), true); assert!(used.needs_optional); } + + #[test] + fn test_json_default_value_escapes_double_quotes() { + let table = TableDef { + name: "configs".into(), + description: None, + columns: vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + ColumnDef { + name: "data".into(), + r#type: ColumnType::Simple(SimpleColumnType::Json), + nullable: false, + default: Some(r#"{"hello": "world"}"#.into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + ], + constraints: vec![TableConstraint::PrimaryKey { + auto_increment: false, + columns: vec!["id".into()], + }], + }; + + let result = render_entity(&table).unwrap(); + assert!( + result.contains(r#"server_default": text("{\"hello\": \"world\"}"#), + "Expected escaped quotes in server_default, got: {}", + result + ); + } }