Skip to content

Commit d61fc5c

Browse files
authored
Merge pull request #94 from dev-five-git/rename-form
Rename form
2 parents 3d29071 + b754fd9 commit d61fc5c

20 files changed

Lines changed: 3120 additions & 151 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"Cargo.toml":"Patch"},"note":"Implement Multiform","date":"2026-03-06T16:01:53.731789300Z"}

Cargo.lock

Lines changed: 7 additions & 90 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,14 @@ pub struct CreateUserRequest {
168168

169169
#### Typed Multipart (Recommended)
170170

171-
Upload files using `TypedMultipart` from [`axum_typed_multipart`](https://crates.io/crates/axum_typed_multipart):
171+
Upload files using vespera's built-in `TypedMultipart` extractor:
172172

173173
```rust
174-
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
174+
use vespera::multipart::{FieldData, TypedMultipart};
175+
use vespera::{Multipart, Schema};
175176
use tempfile::NamedTempFile;
176177

177-
#[derive(TryFromMultipart, vespera::Schema)]
178+
#[derive(Multipart, Schema)]
178179
pub struct CreateUploadRequest {
179180
pub name: String,
180181
#[form_data(limit = "10MiB")]
@@ -189,8 +190,6 @@ pub async fn create_upload(
189190

190191
Vespera automatically generates `multipart/form-data` content type in OpenAPI, and maps `FieldData<NamedTempFile>` to `{ "type": "string", "format": "binary" }`.
191192

192-
> **Note:** `axum` must be a direct dependency of your project (not just via vespera) because `TryFromMultipart` internally references `axum::extract::multipart::Multipart`.
193-
194193
#### Raw Multipart (Untyped)
195194

196195
For dynamic multipart handling where the fields aren't known at compile time, use axum's built-in `Multipart` extractor:
@@ -444,23 +443,23 @@ vespera::schema_type!(Schema from Model, name = "MemoSchema");
444443

445444
### Multipart Mode
446445

447-
Generate `TryFromMultipart` structs from existing types using the `multipart` keyword:
446+
Generate `Multipart` structs from existing types using the `multipart` keyword:
448447

449448
```rust
450-
#[derive(TryFromMultipart, vespera::Schema)]
449+
#[derive(vespera::Multipart, vespera::Schema)]
451450
pub struct CreateUploadRequest {
452451
pub name: String,
453452
#[form_data(limit = "10MiB")]
454453
pub file: Option<FieldData<NamedTempFile>>,
455454
pub description: Option<String>,
456455
}
457456

458-
// Generates a TryFromMultipart struct (no serde derives), all fields Optional
457+
// Generates a Multipart struct (no serde derives), all fields Optional
459458
schema_type!(PatchUploadRequest from CreateUploadRequest, multipart, partial, omit = ["file"]);
460459
```
461460

462461
When `multipart` is enabled:
463-
- Derives `TryFromMultipart` instead of `Serialize`/`Deserialize`
462+
- Derives `Multipart` instead of `Serialize`/`Deserialize`
464463
- Suppresses `#[serde(...)]` attributes (multipart parsing is not serde-based)
465464
- Preserves `#[form_data(...)]` attributes from source struct
466465
- Skips SeaORM relation fields (nested objects can't be represented in multipart forms)
@@ -479,7 +478,7 @@ When `multipart` is enabled:
479478
| `name` | Custom OpenAPI schema name: `name = "UserSchema"` |
480479
| `rename_all` | Serde rename strategy: `rename_all = "camelCase"` |
481480
| `ignore` | Skip Schema derive (bare keyword, no value) |
482-
| `multipart` | Derive `TryFromMultipart` instead of serde (bare keyword) |
481+
| `multipart` | Derive `Multipart` instead of serde (bare keyword) |
483482
| `omit_default` | Auto-omit fields with DB defaults: `primary_key`, `default_value` (bare keyword) |
484483

485484
---

SKILL.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ Json(model.into()) // Easy conversion!
345345
| `rename` | Rename fields | API naming differs from model |
346346
| `rename_all` | Serde rename strategy | Different casing needed |
347347
| `add` | Add new fields | New fields not in model (breaks `From` impl) |
348-
| `multipart` | Derive `TryFromMultipart` | Multipart form-data endpoints |
348+
| `multipart` | Derive `Multipart` | Multipart form-data endpoints |
349349

350350
**Avoid (Special Cases Only):**
351351

@@ -446,14 +446,15 @@ pub async fn patch_user(
446446

447447
### Multipart Mode (`multipart`)
448448

449-
Generate `TryFromMultipart` structs from existing multipart request types:
449+
Generate `Multipart` structs from existing multipart request types:
450450

451451
```rust
452-
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
452+
use vespera::multipart::{FieldData, TypedMultipart};
453+
use vespera::{Multipart, Schema};
453454
use tempfile::NamedTempFile;
454455

455456
// Base multipart struct (manually defined)
456-
#[derive(TryFromMultipart, vespera::Schema)]
457+
#[derive(Multipart, Schema)]
457458
pub struct CreateUploadRequest {
458459
pub name: String,
459460
#[form_data(limit = "10MiB")]
@@ -464,7 +465,7 @@ pub struct CreateUploadRequest {
464465
}
465466

466467
// Derive a partial update struct via schema_type!
467-
// - Derives TryFromMultipart (not serde)
468+
// - Derives Multipart (not serde)
468469
// - All fields become Option<T> (partial)
469470
// - "document" field excluded
470471
// - #[form_data(limit = "10MiB")] preserved from source
@@ -475,18 +476,17 @@ schema_type!(PatchUploadRequest from CreateUploadRequest, multipart, partial, om
475476

476477
| Aspect | Normal Mode | Multipart Mode |
477478
|--------|------------|----------------|
478-
| Derives | `Serialize`, `Deserialize` | `TryFromMultipart` |
479+
| Derives | `Serialize`, `Deserialize` | `Multipart` |
479480
| Struct attrs | `#[serde(rename_all=...)]` | None |
480481
| Field attrs | `#[serde(...)]` preserved | `#[form_data(...)]` preserved |
481482
| Relation fields | Included (BelongsTo/HasOne) | **Skipped** (can't represent in forms) |
482483
| `From` impl | Auto-generated | **Not generated** |
483484

484-
**OpenAPI rename alignment:** The schema parser reads `#[form_data(field_name = "...")]` and `#[try_from_multipart(rename_all = "...")]` as fallbacks when serde attrs are absent, ensuring OpenAPI field names match runtime multipart parsing.
485+
**OpenAPI rename alignment:** The schema parser reads `#[form_data(field_name = "...")]` and `#[serde(rename_all = "...")]` for multipart structs, ensuring OpenAPI field names match runtime multipart parsing.
485486

486487
**Dependencies required in your Cargo.toml:**
487488
```toml
488-
axum = "0.8" # Required: TryFromMultipart references axum internals
489-
axum_typed_multipart = "0.16" # The multipart crate
489+
vespera = "0.1" # Includes multipart support natively
490490
tempfile = "3" # For NamedTempFile file uploads
491491
```
492492

crates/vespera/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ default = ["axum-extra/typed-header", "axum-extra/form", "axum-extra/query", "ax
1212
[dependencies]
1313
vespera_core = { workspace = true }
1414
vespera_macro = { workspace = true }
15-
axum = "0.8"
15+
axum = { version = "0.8", features = ["multipart"] }
1616
axum-extra = { version = "0.12" }
1717
chrono = { version = "0.4", features = ["serde"] }
18-
axum_typed_multipart = "0.16"
1918
tempfile = "3"
2019
serde_json = "1"
2120
tower-layer = "0.3"

crates/vespera/src/lib.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub mod openapi {
2020
pub use vespera_core::openapi::OpenApi;
2121

2222
// Re-export macros from vespera_macro
23-
pub use vespera_macro::{Schema, export_app, route, schema, schema_type, vespera};
23+
pub use vespera_macro::{Multipart, Schema, export_app, route, schema, schema_type, vespera};
2424

2525
// Re-export serde_json for merge feature (runtime spec merging)
2626
pub use serde_json;
@@ -29,9 +29,8 @@ pub use serde_json;
2929
// This allows generated types to use chrono::DateTime without users adding chrono dependency
3030
pub use chrono;
3131

32-
// Re-export axum_typed_multipart for schema_type! multipart mode
33-
// This allows generated types to use FieldData/TryFromMultipart without users adding the dependency
34-
pub use axum_typed_multipart;
32+
// Native multipart form data extraction (replaces axum_typed_multipart)
33+
pub mod multipart;
3534

3635
// Re-export tempfile for schema_type! multipart mode (NamedTempFile)
3736
pub use tempfile;

0 commit comments

Comments
 (0)