Skip to content

Commit

Permalink
Support overriding the type name of a column
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Nov 20, 2023
1 parent e21e935 commit a3fb24c
Show file tree
Hide file tree
Showing 16 changed files with 135 additions and 19 deletions.
2 changes: 1 addition & 1 deletion zino-core/src/model/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl QueryContext {
pub fn new() -> Self {
Self {
start_time: Instant::now(),
query_id: Uuid::new_v4(),
query_id: Uuid::now_v7(),
query: String::new(),
arguments: Vec::new(),
last_insert_id: None,
Expand Down
4 changes: 2 additions & 2 deletions zino-core/src/openapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub(crate) fn default_components() -> Components {
let mut components = OPENAPI_COMPONENTS.get_or_init(Components::new).clone();

// Request ID
let request_id_example = Uuid::new_v4();
let request_id_example = Uuid::now_v7();
let request_id_schema = ObjectBuilder::new()
.schema_type(SchemaType::String)
.format(Some(SchemaFormat::KnownFormat(KnownFormat::Uuid)))
Expand Down Expand Up @@ -142,7 +142,7 @@ pub(crate) fn default_components() -> Components {
.insert("default".to_owned(), default_response.into());

// Error response
let model_id_example = Uuid::new_v4();
let model_id_example = Uuid::now_v7();
let detail_example = format!("404 Not Found: cannot find the model `{model_id_example}`");
let instance_example = format!("/model/{model_id_example}/view");
let status_schema = ObjectBuilder::new()
Expand Down
4 changes: 2 additions & 2 deletions zino-core/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ pub trait RequestContext {
let request_id = self
.get_header("x-request-id")
.and_then(|s| s.parse().ok())
.unwrap_or_else(Uuid::new_v4);
.unwrap_or_else(Uuid::now_v7);
let trace_id = self
.get_trace_context()
.map_or_else(Uuid::new_v4, |t| Uuid::from_u128(t.trace_id()));
.map_or_else(Uuid::now_v7, |t| Uuid::from_u128(t.trace_id()));
let session_id = self
.get_header("x-session-id")
.or_else(|| self.get_header("session_id"))
Expand Down
4 changes: 2 additions & 2 deletions zino-core/src/schedule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl Job {
let schedule = Schedule::from_str(cron_expr)
.unwrap_or_else(|err| panic!("invalid cron expression `{cron_expr}`: {err}"));
Job {
id: Uuid::new_v4(),
id: Uuid::now_v7(),
data: Map::new(),
schedule,
run: ExecutableJob::Fn(exec),
Expand All @@ -48,7 +48,7 @@ impl Job {
let schedule = Schedule::from_str(cron_expr)
.unwrap_or_else(|err| panic!("invalid cron expression `{cron_expr}`: {err}"));
Job {
id: Uuid::new_v4(),
id: Uuid::now_v7(),
data: Map::new(),
schedule,
run: ExecutableJob::AsyncFn(exec),
Expand Down
2 changes: 1 addition & 1 deletion zino-core/src/trace/trace_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl TraceContext {
Self {
span_id,
version: 0,
trace_id: Uuid::new_v4().as_u128(),
trace_id: Uuid::now_v7().as_u128(),
parent_id: None,
trace_flags: FLAG_SAMPLED | FLAG_RANDOM_TRACE_ID,
trace_state: TraceState::new(),
Expand Down
20 changes: 18 additions & 2 deletions zino-core/src/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use crate::{error::Error, extension::JsonObjectExt, Map, SharedString};
mod validator;

pub use validator::{
DateTimeValidator, DateValidator, EmailValidator, Ipv4AddrValidator, Ipv6AddrValidator,
TimeValidator, UriValidator, UuidValidator, Validator,
DateTimeValidator, DateValidator, EmailValidator, HostValidator, HostnameValidator,
IpAddrValidator, Ipv4AddrValidator, Ipv6AddrValidator, TimeValidator, UriValidator,
UuidValidator, Validator,
};

/// A record of validation results.
Expand Down Expand Up @@ -60,6 +61,21 @@ impl Validation {
self.record_fail(key, err);
}
}
"host" => {
if let Err(err) = HostValidator.validate(value) {
self.record_fail(key, err);
}
}
"hostname" => {
if let Err(err) = HostnameValidator.validate(value) {
self.record_fail(key, err);
}
}
"ip" => {
if let Err(err) = IpAddrValidator.validate(value) {
self.record_fail(key, err);
}
}
"ipv4" => {
if let Err(err) = Ipv4AddrValidator.validate(value) {
self.record_fail(key, err);
Expand Down
4 changes: 2 additions & 2 deletions zino-core/src/validation/validator/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use super::Validator;
use regex::Regex;
use std::{fmt, net::IpAddr, str::FromStr, sync::LazyLock};

/// A validator for email address.
/// A validator for the email address.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmailValidator;

/// An error for email address validation.
/// An error for the email address validation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvalidEmail {
/// The value is empty.
Expand Down
49 changes: 49 additions & 0 deletions zino-core/src/validation/validator/host.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use self::InvalidHost::*;
use super::Validator;
use std::{fmt, num::ParseIntError};
use url::{Host, ParseError};

/// A validator for the host of a URL.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HostValidator;

/// An error for the host validation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InvalidHost {
/// Invalid port.
InvalidPort(ParseIntError),
/// Invalid hostname.
InvalidHostname(ParseError),
}

impl fmt::Display for InvalidHost {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InvalidPort(err) => write!(f, "invalid port: {err}"),
InvalidHostname(err) => write!(f, "invalid hostname: {err}"),
}
}
}

impl std::error::Error for InvalidHost {}

impl Validator<str> for HostValidator {
type Error = InvalidHost;

#[inline]
fn validate(&self, data: &str) -> Result<(), Self::Error> {
if let Some((hostname, port)) = data.rsplit_once(':') {
if let Err(err) = port.parse::<u16>() {
Err(InvalidPort(err))
} else if let Err(err) = Host::parse(hostname) {
Err(InvalidHostname(err))
} else {
Ok(())
}
} else if let Err(err) = Host::parse(data) {
Err(InvalidHostname(err))
} else {
Ok(())
}
}
}
16 changes: 16 additions & 0 deletions zino-core/src/validation/validator/hostname.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use super::Validator;
use url::{Host, ParseError};

/// A validator for the hostname of a URL.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HostnameValidator;

impl Validator<str> for HostnameValidator {
type Error = ParseError;

#[inline]
fn validate(&self, data: &str) -> Result<(), Self::Error> {
Host::parse(data)?;
Ok(())
}
}
19 changes: 19 additions & 0 deletions zino-core/src/validation/validator/ip_addr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use super::Validator;
use std::{
net::{AddrParseError, IpAddr},
str::FromStr,
};

/// A validator for [`IpAddr`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IpAddrValidator;

impl Validator<str> for IpAddrValidator {
type Error = AddrParseError;

#[inline]
fn validate(&self, data: &str) -> Result<(), Self::Error> {
IpAddr::from_str(data)?;
Ok(())
}
}
6 changes: 6 additions & 0 deletions zino-core/src/validation/validator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
mod date;
mod date_time;
mod email;
mod host;
mod hostname;
mod ip_addr;
mod ipv4_addr;
mod ipv6_addr;
mod time;
Expand All @@ -12,6 +15,9 @@ mod uuid;
pub use date::DateValidator;
pub use date_time::DateTimeValidator;
pub use email::EmailValidator;
pub use host::HostValidator;
pub use hostname::HostnameValidator;
pub use ip_addr::IpAddrValidator;
pub use ipv4_addr::Ipv4AddrValidator;
pub use ipv6_addr::Ipv6AddrValidator;
pub use time::TimeValidator;
Expand Down
3 changes: 2 additions & 1 deletion zino-derive/docs/model_accessor.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ Derives the [`ModelAccessor`](zino_core::orm::ModelAccessor) trait.

- **`#[schema(format = "format")]`**: The `format` attribute specifies
the format for a `String` value. Supported values: **`date`** | **`date-time`**
| **`email`** | **`ipv4`** | **`ipv6`** | **`time`** | **`uri`** | **`uuid`**.
| **`email`** | **`host`** | **`hostname`** | **`ip`** | **`ipv4`** | **`ipv6`**
| **`time`** | **`uri`** | **`uuid`**.


- **`#[schema(length = N)]`**: The `length` attribute specifies
Expand Down
5 changes: 4 additions & 1 deletion zino-derive/docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ Derives the [`Schema`](zino_core::orm::Schema) trait.
- **`#[schema(ignore)]`**: The `ignore` annotation is used to skip a particular field
such that it maps to no database column.

- **`#[schema(type_name = "name")]`**: The `type_name` attribute is used to
override the Rust data type of the column.

- **`#[schema(column_name = "name")]`**: All column names are assumed to be in **snake-case**.
You can override the it by specifying the `column_name` attribute.

- **`#[schema(column_type = "type")]`**: The column type is derived automatically
from the mappings of Rust primitive data types for different database drivers.
from the mappings of Rust data types for different database drivers.
You can override the it by specifying the `column_type` attribute.

- **`#[schema(length = N)]`**: The `length` attribute specifies
Expand Down
10 changes: 8 additions & 2 deletions zino-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ pub fn derive_schema(item: TokenStream) -> TokenStream {
];

// Special attributes
const SPECIAL_ATTRIBUTES: [&str; 7] = [
const SPECIAL_ATTRIBUTES: [&str; 8] = [
"ignore",
"type_name",
"not_null",
"default_value",
"index_type",
Expand Down Expand Up @@ -83,7 +84,7 @@ pub fn derive_schema(item: TokenStream) -> TokenStream {
&& let Fields::Named(fields) = data.fields
{
for field in fields.named.into_iter() {
let type_name = parser::get_type_name(&field.ty);
let mut type_name = parser::get_type_name(&field.ty);
if let Some(ident) = field.ident
&& !type_name.is_empty()
{
Expand Down Expand Up @@ -124,6 +125,11 @@ pub fn derive_schema(item: TokenStream) -> TokenStream {
ignore = true;
break 'inner;
}
"type_name" => {
if let Some(value) = value {
type_name = value;
}
}
"column_type" => {
column_type = value;
}
Expand Down
4 changes: 2 additions & 2 deletions zino-model/src/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ pub struct User {
#[schema(default_value = "User::model_namespace", index_type = "hash")]
namespace: String,
#[cfg(feature = "visibility")]
#[schema(column_type = "String", default_value = "UserVisibility::default")]
#[schema(type_name = "String", default_value = "UserVisibility::default")]
visibility: UserVisibility,
#[schema(
column_type = "String",
type_name = "String",
default_value = "UserStatus::default",
index_type = "hash"
)]
Expand Down
2 changes: 1 addition & 1 deletion zino/src/channel/axum_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl MessageChannel {
/// Creates a new `MessageChannel`.
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel(CHANNEL_CAPACITY.load(Relaxed));
let sender_id = Uuid::new_v4();
let sender_id = Uuid::now_v7();
let subscriber = Subscriber::new(sender, None);
let mut senders = CHANNEL_SUBSCRIBERS.write();
senders.retain(|_, subscriber| !subscriber.emitter().is_closed());
Expand Down

0 comments on commit a3fb24c

Please sign in to comment.