Skip to main content

vld_aide/
lib.rs

1//! # vld-aide — Bridge between `vld` and `aide` / `schemars`
2//!
3//! This crate lets you use `vld` validation schemas as the **single source of truth**
4//! for both runtime validation and OpenAPI documentation generated by
5//! [aide](https://docs.rs/aide) (which uses [schemars](https://docs.rs/schemars) internally).
6//!
7//! Instead of duplicating schema definitions with `#[derive(JsonSchema)]` **and**
8//! `vld::schema!`, you define validation rules once and get `schemars` compatibility
9//! for free.
10//!
11//! # Quick Start
12//!
13//! ```rust
14//! use vld::prelude::*;
15//! use vld_aide::impl_json_schema;
16//!
17//! // 1. Define your validated struct as usual
18//! vld::schema! {
19//!     #[derive(Debug)]
20//!     pub struct User {
21//!         pub name: String => vld::string().min(2).max(50),
22//!         pub email: String => vld::string().email(),
23//!     }
24//! }
25//!
26//! // 2. Bridge to schemars — one line
27//! impl_json_schema!(User);
28//!
29//! // Now `User` implements `schemars::JsonSchema` and can be used in
30//! // aide routers: `aide::axum::Json<User>` etc.
31//! ```
32//!
33//! # Converting arbitrary JSON Schema
34//!
35//! ```rust
36//! use vld_aide::vld_to_schemars;
37//!
38//! let vld_schema = serde_json::json!({
39//!     "type": "object",
40//!     "required": ["name"],
41//!     "properties": {
42//!         "name": { "type": "string", "minLength": 1 }
43//!     }
44//! });
45//!
46//! let schemars_schema = vld_to_schemars(&vld_schema);
47//! // Returns `schemars::Schema`
48//! ```
49
50/// Convert a `serde_json::Value` (JSON Schema) produced by `vld` into a
51/// `schemars::Schema`.
52///
53/// `schemars::Schema` wraps a JSON value that must be either an object or a bool.
54/// Since vld's `json_schema()` always returns an object, the conversion is direct.
55///
56/// # Example
57///
58/// ```rust
59/// use vld_aide::vld_to_schemars;
60///
61/// let schema = vld_to_schemars(&serde_json::json!({"type": "string", "format": "email"}));
62/// assert_eq!(schema.get("type").unwrap(), "string");
63/// ```
64pub fn vld_to_schemars(value: &serde_json::Value) -> schemars::Schema {
65    value
66        .clone()
67        .try_into()
68        .unwrap_or_else(|_| schemars::Schema::default())
69}
70
71/// Implement `schemars::JsonSchema` for a type that has a `json_schema()` associated
72/// function (generated by `vld::schema!` with the `openapi` feature enabled, or by
73/// `#[derive(Validate)]`).
74///
75/// This makes the type usable with `aide` for OpenAPI documentation generation.
76///
77/// # Usage
78///
79/// ```rust
80/// use vld::prelude::*;
81/// use vld_aide::impl_json_schema;
82///
83/// vld::schema! {
84///     #[derive(Debug)]
85///     pub struct CreateUser {
86///         pub name: String => vld::string().min(2).max(100),
87///         pub email: String => vld::string().email(),
88///     }
89/// }
90///
91/// impl_json_schema!(CreateUser);
92///
93/// // Now CreateUser implements schemars::JsonSchema and works with aide.
94/// ```
95///
96/// You can also pass a custom schema name:
97///
98/// ```rust
99/// # use vld::prelude::*;
100/// # use vld_aide::impl_json_schema;
101/// # vld::schema! {
102/// #     #[derive(Debug)]
103/// #     pub struct Req { pub x: String => vld::string() }
104/// # }
105/// impl_json_schema!(Req, "CreateUserRequest");
106/// ```
107#[macro_export]
108macro_rules! impl_json_schema {
109    ($ty:ty) => {
110        impl $crate::schemars::JsonSchema for $ty {
111            fn schema_name() -> ::std::borrow::Cow<'static, str> {
112                ::std::borrow::Cow::Borrowed(stringify!($ty))
113            }
114
115            fn schema_id() -> ::std::borrow::Cow<'static, str> {
116                ::std::borrow::Cow::Owned(concat!(module_path!(), "::", stringify!($ty)).to_owned())
117            }
118
119            fn json_schema(
120                gen: &mut $crate::schemars::SchemaGenerator,
121            ) -> $crate::schemars::Schema {
122                #[allow(unused_imports)]
123                use $crate::__VldNestedSchemasFallback as _;
124                for (name, schema_fn) in <$ty>::__vld_nested_schemas() {
125                    gen.definitions_mut()
126                        .entry(name.to_string())
127                        .or_insert_with(|| schema_fn());
128                }
129                $crate::vld_to_schemars(&<$ty>::json_schema())
130            }
131        }
132    };
133    ($ty:ty, $name:expr) => {
134        impl $crate::schemars::JsonSchema for $ty {
135            fn schema_name() -> ::std::borrow::Cow<'static, str> {
136                ::std::borrow::Cow::Borrowed($name)
137            }
138
139            fn schema_id() -> ::std::borrow::Cow<'static, str> {
140                ::std::borrow::Cow::Owned(format!("{}::{}", module_path!(), $name))
141            }
142
143            fn json_schema(
144                gen: &mut $crate::schemars::SchemaGenerator,
145            ) -> $crate::schemars::Schema {
146                #[allow(unused_imports)]
147                use $crate::__VldNestedSchemasFallback as _;
148                for (name, schema_fn) in <$ty>::__vld_nested_schemas() {
149                    gen.definitions_mut()
150                        .entry(name.to_string())
151                        .or_insert_with(|| schema_fn());
152                }
153                $crate::vld_to_schemars(&<$ty>::json_schema())
154            }
155        }
156    };
157}
158
159/// Re-export schemars for use in macros.
160pub use schemars;
161
162#[doc(hidden)]
163pub trait __VldNestedSchemasFallback {
164    #[allow(clippy::type_complexity)]
165    fn __vld_nested_schemas() -> Vec<(&'static str, fn() -> serde_json::Value)> {
166        Vec::new()
167    }
168}
169
170impl<T: ?Sized> __VldNestedSchemasFallback for T {}
171
172/// Prelude module — import everything you need.
173pub mod prelude {
174    pub use crate::impl_json_schema;
175    pub use crate::vld_to_schemars;
176    pub use vld::prelude::*;
177}