diff --git a/.changepacks/changepack_log_FHWJJPPxJTLxVPGXE7Tf3.json b/.changepacks/changepack_log_FHWJJPPxJTLxVPGXE7Tf3.json new file mode 100644 index 0000000..84a3c50 --- /dev/null +++ b/.changepacks/changepack_log_FHWJJPPxJTLxVPGXE7Tf3.json @@ -0,0 +1 @@ +{"changes":{"Cargo.toml":"Patch"},"note":"Fix schema_type has_one","date":"2026-04-01T14:13:09.811093500Z"} \ No newline at end of file diff --git a/crates/vespera_macro/src/schema_macro/type_utils.rs b/crates/vespera_macro/src/schema_macro/type_utils.rs index 6658fa7..08937ab 100644 --- a/crates/vespera_macro/src/schema_macro/type_utils.rs +++ b/crates/vespera_macro/src/schema_macro/type_utils.rs @@ -16,15 +16,19 @@ pub const PRIMITIVE_TYPE_NAMES: &[&str] = &[ "f64", "bool", "String", "Decimal", ]; -/// Normalize a `TokenStream` or `Type` to a compact string by removing spaces. +/// Normalize a `TokenStream` or `Type` to a compact string by removing all whitespace. /// /// This replaces the common `.to_string().replace(' ', "")` pattern used throughout /// the codebase to produce deterministic path strings for comparison and cache keys. +/// +/// Removes spaces, newlines, and carriage returns — `proc_macro2`'s `Display` impl +/// may insert newlines when token sequences exceed an internal line-length threshold, +/// which would break substring checks like `contains("HasOne<")`. #[inline] pub fn normalize_token_str(displayable: &impl std::fmt::Display) -> String { let s = displayable.to_string(); - if s.contains(' ') { - s.replace(' ', "") + if s.contains(|c: char| c.is_ascii_whitespace()) { + s.replace(|c: char| c.is_ascii_whitespace(), "") } else { s } diff --git a/examples/axum-example/models/single.json b/examples/axum-example/models/single.json new file mode 100644 index 0000000..8e65f4c --- /dev/null +++ b/examples/axum-example/models/single.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/model.schema.json", + "name": "single", + "columns": [ + { + "name": "username", + "type": { "kind": "varchar", "length": 32 }, + "nullable": false, + "primary_key": true + } + ] +} diff --git a/examples/axum-example/models/single_rel.json b/examples/axum-example/models/single_rel.json new file mode 100644 index 0000000..7eb6773 --- /dev/null +++ b/examples/axum-example/models/single_rel.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/model.schema.json", + "name": "single_rel", + "columns": [ + { + "name": "username", + "type": { "kind": "varchar", "length": 32 }, + "nullable": false, + "primary_key": true, + "foreign_key": { + "ref_table": "single", + "ref_columns": ["username"], + "on_delete": "cascade" + } + } + ] +} diff --git a/examples/axum-example/openapi.json b/examples/axum-example/openapi.json index bf41bd6..df9656c 100644 --- a/examples/axum-example/openapi.json +++ b/examples/axum-example/openapi.json @@ -3467,6 +3467,60 @@ "createdAt" ] }, + "SingleRelSchema": { + "type": "object", + "properties": { + "single": { + "$ref": "#/components/schemas/SingleRelSchema_Single" + }, + "username": { + "type": "string", + "default": "" + } + }, + "required": [ + "username", + "single" + ] + }, + "SingleRelSchema_Single": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + }, + "required": [ + "username" + ] + }, + "SingleSchema": { + "type": "object", + "properties": { + "singleRel": { + "$ref": "#/components/schemas/SingleSchema_SingleRel", + "nullable": true + }, + "username": { + "type": "string", + "default": "" + } + }, + "required": [ + "username" + ] + }, + "SingleSchema_SingleRel": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + }, + "required": [ + "username" + ] + }, "SkipResponse": { "type": "object", "properties": { diff --git a/examples/axum-example/src/models/mod.rs b/examples/axum-example/src/models/mod.rs index 02ee003..0c26e48 100644 --- a/examples/axum-example/src/models/mod.rs +++ b/examples/axum-example/src/models/mod.rs @@ -1,5 +1,7 @@ pub mod config; pub mod memo; pub mod memo_comment; +pub mod single; +pub mod single_rel; pub mod user; pub mod uuid_item; diff --git a/examples/axum-example/src/models/single.rs b/examples/axum-example/src/models/single.rs new file mode 100644 index 0000000..9e3a48a --- /dev/null +++ b/examples/axum-example/src/models/single.rs @@ -0,0 +1,15 @@ +#![allow(dead_code)] +use sea_orm::entity::prelude::*; + +#[sea_orm::model] +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "single")] +pub struct Model { + #[sea_orm(primary_key)] + pub username: String, + #[sea_orm(has_one)] + pub single_rel: HasOne, +} + +vespera::schema_type!(Schema from Model, name = "SingleSchema"); +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/axum-example/src/models/single_rel.rs b/examples/axum-example/src/models/single_rel.rs new file mode 100644 index 0000000..14323d6 --- /dev/null +++ b/examples/axum-example/src/models/single_rel.rs @@ -0,0 +1,15 @@ +#![allow(dead_code)] +use sea_orm::entity::prelude::*; + +#[sea_orm::model] +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "single_rel")] +pub struct Model { + #[sea_orm(primary_key)] + pub username: String, + #[sea_orm(belongs_to, from = "username", to = "username")] + pub single: HasOne, +} + +vespera::schema_type!(Schema from Model, name = "SingleRelSchema"); +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/axum-example/tests/snapshots/integration_test__openapi.snap b/examples/axum-example/tests/snapshots/integration_test__openapi.snap index be6d493..b7d5c7b 100644 --- a/examples/axum-example/tests/snapshots/integration_test__openapi.snap +++ b/examples/axum-example/tests/snapshots/integration_test__openapi.snap @@ -3471,6 +3471,60 @@ expression: "std::fs::read_to_string(\"openapi.json\").unwrap()" "createdAt" ] }, + "SingleRelSchema": { + "type": "object", + "properties": { + "single": { + "$ref": "#/components/schemas/SingleRelSchema_Single" + }, + "username": { + "type": "string", + "default": "" + } + }, + "required": [ + "username", + "single" + ] + }, + "SingleRelSchema_Single": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + }, + "required": [ + "username" + ] + }, + "SingleSchema": { + "type": "object", + "properties": { + "singleRel": { + "$ref": "#/components/schemas/SingleSchema_SingleRel", + "nullable": true + }, + "username": { + "type": "string", + "default": "" + } + }, + "required": [ + "username" + ] + }, + "SingleSchema_SingleRel": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + }, + "required": [ + "username" + ] + }, "SkipResponse": { "type": "object", "properties": { diff --git a/openapi.json b/openapi.json index bf41bd6..df9656c 100644 --- a/openapi.json +++ b/openapi.json @@ -3467,6 +3467,60 @@ "createdAt" ] }, + "SingleRelSchema": { + "type": "object", + "properties": { + "single": { + "$ref": "#/components/schemas/SingleRelSchema_Single" + }, + "username": { + "type": "string", + "default": "" + } + }, + "required": [ + "username", + "single" + ] + }, + "SingleRelSchema_Single": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + }, + "required": [ + "username" + ] + }, + "SingleSchema": { + "type": "object", + "properties": { + "singleRel": { + "$ref": "#/components/schemas/SingleSchema_SingleRel", + "nullable": true + }, + "username": { + "type": "string", + "default": "" + } + }, + "required": [ + "username" + ] + }, + "SingleSchema_SingleRel": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + }, + "required": [ + "username" + ] + }, "SkipResponse": { "type": "object", "properties": {