Changelog
All notable changes to the crudcrate project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.15.0 - 2026-09-24
Added
#[schema(...)]on an entity field is accepted byEntityToModelsand forwarded to the generated API and List models, so a column whose Rust type says less than the value it holds (a jsonbserde_json::Valueover an array of strings) declares its shape where the schema is derived:#[schema(value_type = Vec<String>)]. Previously the attribute did not compile on the entity, and a consumer had to redeclare the wire shape by hand.register_new, an insert-only registration on the sameupsert_keylookup asupsert. A key already stored is returned exactly as it stands and reportedRegistrationStatus::Kept; nothing is written over. For a row whose stored content is the arithmetic something else was already computed with, where re-registering the key must not move it.RegistrationStatusis its own enum rather than a thirdUpsertStatusvariant, so no existing response or schema widens.
Fixed
- A list’s tie-break is every key column, in the requested sort direction, in
the derived
get_allbodies as well as the default one. The derived bodies appended only the first key column, so a composite key did not order a page, and every body tie-broke ascending, so a descending walk was not the ascending one reversed. A descending page whose rows tie on the sort column now lists them in descending key order.
0.14.0 - 2026-09-11
Added
-
Composite primary keys. An entity may mark more than one field
#[crudcrate(primary_key)]; the key’s value is the tuple SeaORM spells it as, in declaration order.ResourceIdcarries what a tuple cannot implement itself, rendering a key into a not-found message and decomposing it into the column values a query binds, andCRUDResource::id_columns()names the columns. Pagination tie-breaks on every key column, and a batch operation filters on whole keys: a single-column key stays anINlist, a composite key becomes an OR of per-key equality.No route that addresses one row by its id is mounted for a composite key. Those take the key through
Path<PrimaryKeyType<Self>>against a one-segment/{id}, which a tuple cannot deserialize from, so the route would answer 500 to every call. Reads, the generated models and everyCRUDResourcemethod are unaffected. -
A registration key may declare the predicate it is unique under. Where the unique index backing
upsert_keyis partial,upsert_where = <expr>names itsWHEREclause, the same SeaORM expressionOnConflict::target_cond_wheretakes, and the find is narrowed by it. A row the index does not cover is not the key’s row, so registering a slot whose previous row has left the index opens a new row instead of updating the old one. The predicate is exposed asCRUDResource::upsert_predicate(), defaulting to every row. Naming it without anupsert_keyis a compile error. Seecargo run --example partial_upsert.
Fixed
- Two callers registering one key concurrently no longer fail on the unique
index.
upsertread the key and inserted where it read as absent, with nothing holding the key between the two, so under READ COMMITTED both transactions read absent and the second violated the constraint. The insert now runs inside a savepoint and the loser re-reads the row that won, reportingUpdatedorUnchangedagainst it.
0.13.0 - 2026-09-09
Changed
upserttakes the resource’s active model. Convert a create model with.into()and set any server-owned key columns before calling it. Registration keys can remain excluded from create and update requests. Seedocs/MIGRATION_0.13.md.- Scoped writes confine rows instead of refusing every request. Updates and deletes
return 404 for excluded rows; creates and updates roll back with 403 when their
resulting row leaves the condition. Batch checks share the write transaction,
including custom hooks; partial batches confine each item independently. Write
responses use scoped field exclusions. Mount
read_only_router()or authorize methods separately when a caller must not write. Seedocs/MIGRATION_0.13.md.
Fixed
- A nullable field on a List, Response, scoped or API model is
requiredin the OpenAPI document. utoipa reads everyOption<T>as not-required and nullable, so a value the API always sends as null and one a client may omit were described identically, and a generated client had to handle an absence that never happens. A field carryingskip_serializing_if, or declaring its own#[schema(required)], keeps what it declares; create and update models are requests and are untouched.
0.12.0 - 2026-09-08
Added
- The CRUD stack takes the connection as a type parameter, so every operation and
every hook accepts a
&DatabaseTransactionas readily as a&DatabaseConnection. A caller composing its own transaction runs a crudcrate operation inside it, and a hook writing another resource writes on the same transaction as the row it guards. Trait methods and hook functions are declared<C: ConnectionTrait + TransactionTrait>takingdb: &C. Seedocs/MIGRATION_0.12.md. CRUDOperations::after_beginruns immediately after everyBEGINa write issues, before any other hook on that transaction: once for a single-row lifecycle, and in a batch once for the enclosing transaction and again for each row’s savepoint. It is where the session state a write needs is set (SET LOCAL), scoped to that transaction rather than to a pooled connection.- Registration:
crudcrate::upsertfinds or inserts a row on an alternate unique key inside one transaction and reportsCreated,UpdatedorUnchanged, so a source system re-sending its own content writes nothing when the content already stands. The key is declared with#[crudcrate(upsert_key(source_system, source_key))], which also generates the columns a registration compares and writes (the create model’s stored columns but the primary key);CRUDResource::upsert_key,upsert_comparableandapply_on_updateare the trait surface behind it. UpsertOutcome<K, I, S>reports one item of a registration batch keyed by what the sender holds, carrying a status, the row’s id and a note. It is generic over the status so a caller with refusals of its own reports them in the same list rather than as failures, whichBatchResult(per-item success-or-error) cannot express.#[crudcrate(routes(read, update))]names the route familiesrouter()mounts, out ofcreate,read,updateanddelete. An operation left out is not a route, so the OpenAPI document does not advertise it. Omit the attribute for every route, which stays the default.
Changed
- The
CRUDResourceandCRUDOperationstraits use nativeasync fninstead ofasync-trait. Animplof either drops its#[async_trait]attribute, andasync-traitis no longer a dependency ofcrudcrateorcrudcrate-derive. The traits declare each method as returningimpl Future + Send, so code generic over the resource spawns or boxes an operation’s future as it did underasync-trait. Animplwritesasync fnas before. - A write lifecycle holds one pooled connection from
BEGINtoCOMMIT, hooks included. A hook that opens its own connection from the same pool rather than using thedbit is handed can exhaust the pool under concurrent writes; seedocs/MIGRATION_0.12.md. - The single-row
create,updateanddeletelifecycles each run in one transaction. A hook that fails after the write takes the write with it, where before the row stayed and the error was returned. Both the write and the hooks around it now succeed or fail together. create_many,update_manyanddelete_manyrun their rows in one transaction, so a batch that fails partway leaves none of its rows. Each row’s own lifecycle is a savepoint within it. The?partial=trueendpoints are unaffected: they run each row on the connection and still report per-item outcomes with207 Multi-Status.
Fixed
- A registration whose entity maintains its own timestamps reported
Updatedon every pass and rewrotecreated_at. The columns a registration compares are those the create model can carry, so a field the entity manages is neither compared nor overwritten, and one carryingon_updateadvances only when the row does change.
0.11.1 - 2026-09-07
Fixed
CRUDOperations::create_manyandupdate_manyrun the single-rowcreate/updatelifecycle per item instead of delegating toCRUDResource, which the derive generates as a call back into the operations struct: an entity declaringoperations = Xwithout overriding both recursed until the stack overflowed on every batch request.CRUDOperations::perform_delete_manydeletes one row at a time throughdelete, sobefore_deleteandafter_deletefire for every row of a batch. An id that is not there is skipped, as before.
0.11.0 - 2026-09-03
Added
#[crudcrate(max_child_rows = N)]andSecurityProfile::max_child_rows_per_relationcap the child rows one join field may load per request; exceeding the cap returns413 Payload Too Large(newApiError::PayloadTooLarge). Unlimited unless set.join(relation = "Variant")selects the child’sRelationvariant for foreign key resolution.
Changed
build_filter_exprandbuild_comparison_exprreturnResult<Option<Expr>, ApiError>instead ofOption<Expr>. Hand-writtenresolve_joined_filtersimplementations add?at the call site; generated code is updated. Seedocs/MIGRATION_0.11.md.
Fixed
- An empty array filter (
filter={"id":[]}) dropped the clause and returned every row. It now matches nothing, which is what an emptyINlist asks for. - A numeric filter value against a date, time or uuid column (
filter={"created_at_gte":5}) was bound as a number and rejected by the backend. The clause is now parsed against the column type and dropped when it does not fit. Numeric columns are unaffected:{"year_gte":3.5}and integers abovei64::MAXbind as before. - Array filters (
filter={"created_at":["...","..."]}) bound every element as text, so anINlist over a date, timestamp, decimal or float column was rejected by Postgres and compared lexically elsewhere. Elements are now parsed against the column’s SQL type, as scalar filters already were. An element that does not parse returns400 Bad Requestrather than dropping the clause and returning unfiltered rows. build_comparison_exprbound every non-UUID string as text, so a hand-writtenresolve_joined_filterscomparing a non-text child column had the same defect. It now routes by column type on both its scalar and array arms.- Join loading at depth 2 or more read
related_model.id, so a child entity whose primary key field has another name failed to compile. Loaders now go through the new providedCRUDResource::pk_value(&model), which the derive overrides with the actual field. - Generated routers named
tracing::info!without qualification, so a crate without a directtracingdependency failed to compile. Generated code now goes throughcrudcrate::tracing. - Column variant idents for field names with a digit boundary (
is_2fa_enabled) were built withconvert_case(Is2FaEnabled) while Sea-ORM names them with heck (Is2faEnabled), so such entities failed to compile. All column idents now use heck.
Deprecated
-
Relying on the
useitems thatcrud_handlers!leaks into the calling module (StatusCode,Json,Path, …). Import them directly; the leak is removed in the next breaking release. The macro no longer needsCRUDResourcein scope at the call site. Scheduled for removal in the next breaking release: -
UuidIdResult, unused sincedelete_manybecame generic over the primary key type. -
filtering::sort::generic_sort: parses every value as a JSON pair, so a plain column name falls back to the default column. Useparse_sorting. -
ValidationErrors: generated validation returns the singularValidationError. -
DefaultCRUDOperations: never constructed by generated code. -
BatchResult::all_succeeded: checkfailed.is_empty(). -
generate_crud_router!: collapses every response model to the resource type. Use#[crudcrate(generate_router)]. -
The five- and three-argument arms of
crud_handlers!. -
The
crudcrate::databaseandcrudcrate::relationshipsmodules, which contain no items.
Removed
- The empty, hidden
crudcrate::routesmodule. - The
spring-rsfeature and its optionalspringandspring-webdependencies. No code was gated on it; spring-web applications mount the generated axum router directly.
Changed
- Joined filters (
?filter={"vehicles.make":"bmw"}) go through the same value dispatch as main-entity filters: string equality folds case, enum columns are cast on Postgres,like_filterablecolumns and_likeuse a case-insensitiveLIKE, and typed columns bind typed values. New publicbuild_filter_exprexposes that dispatch for custom resolvers. - Boolean filters with
_neqnow negate; previously the suffix was ignored. crudcrate::core::crud_operations(the handler macros, no nameable items) is nowcrudcrate::core::handler_macros. The macros are unchanged and still exported at the crate root.- The handler macros name
apply_filters,parse_pagination,FilterOptions,calculate_content_rangeandparse_sortingat the crate root instead of through thefilter,models,paginationandsortalias modules. The aliases remain exported. - Generated code is pinned by expansion snapshots in
crudcrate-derive/tests/expand, andtest_suitecarries a compile-only test naming everycrudcrate::path generated code emits.
0.10.1 - 2026-08-20
Fixed
get_one_scopedignoredread::one::body. The scoped get-one handler had no custom-body branch, so a resource combining aScopeConditionwith a custom get-one body silently ran the default query instead. Whatever the body existed to do, extra filtering, joins, shaping, never happened on scoped requests, and rows the body would have refused were served. The handler now checks scope eligibility in SQL first, answers 404 when the scope excludes the row, then delegates to the custom body with the same pre/transform/post hooks as the unscoped path.- Scoped models were generated but never mounted for non-db exclusions.
Model generation and router wiring used different definitions of “has
scoped fields”: the models counted
exclude(scoped)on join andnon_db_attrfields, the router did not. When such a field was the only exclusion, theScopedList/ScopedOnestructs existed but the router served the plain models, so the supposedly excluded field still reached scoped callers. Both sites now share one predicate. require_scopewrite semantics documented and pinned. The flag governs reads only: an unscoped read fails with 500, while writes are governed solely by scope presence (403 when aScopeConditionis present, allowed when absent). That split is what lets an application mount its scope on safe methods only, so writes arrive unscoped deliberately. The trait doc, the multi-tenant guide and new enforcement tests now state the contract explicitly. No behaviour change.- Validation docs described an API that does not exist.
docs/src/advanced/validation.mdshowed aValidationFailed(Vec<ValidationError>)tuple variant and never mentioned theValidatabletrait, the mechanism the handlers actually invoke. The page now documentsValidatableand the realApiError::validation_failed(Vec<String>)constructor.
0.10.0 - 2026-08-19
Changed
- SeaORM 2.0. The workspace now builds against
sea-orm = "2.0"(SeaQuery 1.0, SQLx 0.9). Applications must upgrade their ownsea-ormdependency in the same step; crudcrate 0.9.x remains available for SeaORM 1.x. Seedocs/MIGRATION_0.10.md. - Batch create uses a single multi-row
INSERT ... RETURNINGon backends that support it (PostgreSQL, SQLite >= 3.35); MySQL keeps the per-row insert loop inside the transaction. Response order, all-or-nothing semantics, 409 duplicate-key mapping and?partial=truebehaviour are unchanged. Thesqlitefeature now enables SeaORM’ssqlite-use-returning-for-3_35. Behaviour change: customActiveModelBehavior::before_save/after_saveimplementations are no longer invoked per row during batch create on the RETURNING backends (they still run on MySQL, and single create never invoked them). Response order relies on the engine emitting RETURNING rows in VALUES order, which PostgreSQL and SQLite implement but do not formally guarantee.
Added
- Dense entity format support. Entities written with SeaORM 2.0’s
#[sea_orm::model](inlineHasMany/HasOne/BelongsTorelation fields) can deriveEntityToModelsdirectly. The derive attaches to the scalarModeland ignores the generatedModelExcompanion, so relation wrapper fields never leak into Create/Update/List models. crudcrate::table_column_ref(table, column)builds a table-qualifiedColumnReffor customresolve_joined_filtersimplementations, replacing theColumnRef::TableColumnvariant removed in SeaQuery 1.0.- Guided compile errors for misplaced dense-format relations. A
HasMany/HasOne/BelongsTofield on a struct not processed by#[sea_orm::model]now produces a spanned error naming the fix instead of silently generating nothing. Both crates also declarerust-version = "1.85"so an old toolchain fails with a clear MSRV message. #[crudcrate(deny_unknown_fields)]. Opt-in strict input models: the generated<Name>Createand<Name>Updatereject a payload key they do not accept instead of ignoring it. Without it, a value sent for a field excluded from create or update is silently dropped and the response reports the stored value rather than the submitted one. Off by default, since clients that round-trip a full record into an update legitimately send read-only fields.crudcrate::sea_ormre-export. Applications can reference the SeaORM version crudcrate builds against (ie.use crudcrate::sea_orm;) instead of pinning a matchingsea-ormdependency themselves.
Fixed
- List pagination could repeat or skip a row.
get_allordered by the requested sort column alone, so rows tied on that column had no defined order and twoLIMIT/OFFSETqueries serving two pages could place a tied row in both or neither. The primary key is now appended as a secondary sort key whenever the sort column is not the primary key, in the trait defaultget_all, the derive-generatedget_all/get_all_scoped, the joined (dot-notation) sort, andCRUDOperations::fetch_all. A customget::all::bodyoverride supplies its own ordering and is unaffected. - List models dropped field attributes.
<Name>Listwas generated with the field name and type only, so#[serde(skip_serializing_if = ...)],#[serde(rename = ...)],#[schema(...)]and doc comments applied to the single-record response but not to entries in the collection response, e.g. a field skipped whenNonecame back as an explicitnullin a list.serde,schemaand doc attributes now carry over to<Name>List,<Name>ScopedListand<Name>ScopedResponse, matching<Name>Response, which already did this. per_page=0produced an empty page.parse_paginationclamped only the upper bound, returning a limit of 0 that yields a page with no rows however many rows match (and a zero page size that some paginators reject outright). It now clamps to1..=MAX_PAGE_SIZE.
Notes
- SeaORM 2.0’s Entity Loader was evaluated and not adopted for join loading:
it requires the dense entity format on user entities and offers no hook for
per-child scope conditions. Typed
COLUMNconstants were likewise evaluated and skipped; generated code keeps using theColumnenum paths. Rationale in the relationships documentation.
0.9.3 - 2026-07-15
Fixed
- Comparison filters on non-text columns returned 500 on PostgreSQL. A
_gte/_lte/_gt/_lt/_neq(or equality) filter whose value arrived as a JSON string wrapped the column inUPPER(col), which PostgreSQL rejects for date, timestamp, numeric, uuid and boolean columns (function upper(timestamp with time zone) does not exist); SQLite’s loose typing hid it, and even there the comparison ordered lexically ('9' > '10'). Comparison values are now parsed to the column’s Sea-ORMColumnTypeand bound as a typed parameter, so the backend compares natively. Text and enum columns keep case-insensitive matching. Requires thewith-chronoandwith-rust_decimalSea-ORM features (now enabled by default).
0.9.2 - 2026-06-10
Added
- Non-UUID primary keys.
CRUDResourceis now generic over the entity’s primary-key value type viacrudcrate::PrimaryKeyType<Self>, so integer (i32), string, and other Sea-ORM key types work end to end (CRUD, batch, filtering, sorting, pagination, relationship loading, and the generated Axum routes). UUID remains fully supported and unchanged. - Joined sorting.
sort=["children.column","ASC|DESC"]now actually orders parent rows by the joined child column, via a correlated sub-query (ORDER BY (SELECT MIN(child.col) FROM child WHERE child.fk = parent.pk)). Previously the dot-notation sort was parsed but silently ignored. - Automatic
Validatableinvocation. When a Create/Update model implementscrudcrate::validation::Validatable, the generated create/update/batch handlers now callvalidate()before any write (HTTP 422 on failure). Models that don’t implement it are unaffected (no-op). - Multi-backend coverage in CI. The PostgreSQL and MySQL CI jobs now upload
cargo llvm-covresults to Codecov, so backend-specific code paths are measured (previously only the SQLite job reported coverage).
Changed
- BREAKING (trait signatures).
CRUDResourceandCRUDOperationsmethods that tookid: uuid::Uuid/Vec<uuid::Uuid>now takecrudcrate::PrimaryKeyType<Self>/Vec<PrimaryKeyType<Self>>, includingget_one,get_one_scoped,update,delete,delete_many,update_manyand theCRUDOperationsbefore_*/after_*/perform_*/fetch_*hooks. For UUID resourcesPrimaryKeyType<Self>resolves toUuid, so runtime behavior and the generated routes are unchanged. Migration: downstream code that overrides any of these trait methods/hooks must change theUuidparameter/return types tocrudcrate::PrimaryKeyType<Self>(or the concrete PK type). Code that only derivesEntityToModelsneeds no change. POST /{resource}/batch?partial=truenow builds each item withcreate_manysemantics, so partial and all-or-nothing batch create return the same (flat) response shape and run the samecreate::manyhooks. SinglePOST /{resource}still applies join loading +read::one::transform.delete_manyde-duplicates the returned ids so the reported count matches the rows actually deleted.- Self-referencing
#[schema(no_recursion)]detection now uses an exact inner-type match (not a substring), and derivingEqnow also derivesPartialEq.
Fixed
- LIKE wildcard escaping was a no-op on SQLite for the fulltext LIKE-fallback
and joined
_likepaths: escaping used a backslash but emitted noESCAPEclause, so user-supplied%/_stayed active wildcards. These paths now use the!escape convention with an explicitESCAPE '!'clause. - Panic on oversized multi-byte search queries. Fulltext truncation sliced at a fixed byte index and panicked when it landed mid-UTF-8-codepoint; it now snaps to a char boundary.
- React-Admin
rangepagination overflow on a hugeendvalue (now uses saturating arithmetic). - Scoped
get_onemasked all errors as 404, hiding DB/internal faults; it now propagates the real error (a scope miss is still 404). - Validation length/email helpers counted UTF-8 bytes, not characters, wrongly rejecting multibyte input within the advertised character limit.
Option<T>belongs_to relations were not loaded in list endpoints (the batch loader resolved the FK in the wrong direction); now resolved viafind_related.- Duplicate-key inserts returned 500 instead of the documented 409 Conflict.
- Foreign-key violations returned 500 instead of 409 Conflict; they are now mapped to 409 like unique-constraint violations (matching the documented response), across all backends.
- Array filters on integer/boolean columns returned 500 on PostgreSQL.
?filter={"id":[1,3]}builtid IN ('1','3')(string literals), which PostgreSQL rejects asoperator does not exist: integer = text; SQLite’s loose typing hid it. Array values are now bound as their native JSON type. - Generated
depth > 1join code required atracingdependency in the downstream crate (it emitted unqualifiedtracing::warn!). The generated calls are now qualified ascrudcrate::tracing::warn!, so consumers no longer need to depend ontracingdirectly. - Examples: corrected
error_handlingprinted messages to matchApiErroroutput, fixed the misleadingrecursive_join/recursive_join_5_levelsdepth claims, trimmedjoined_filterboilerplate, and listed all examples in the examples README.
Security
- Partial batch-delete leaked row existence under the secure profile. With
expose_deleted_ids = false, the?partial=trueresponse serialized per-item not-found errors (embedding the submitted UUIDs), re-creating the enumeration oracle that profile is meant to close. It now returnsfailed_countinstead of the per-itemfailedlist.
Known limitations
- Joined-filter sub-queries and
Vec<Child>batch loading apply no per-relation row cap; they are bounded only byMAX_FILTER_CLAUSES(100) and the parent page size. On very large child tables this is a query-amplification consideration for untrusted callers. - Case-insensitive fulltext and
like_filterablematching on MySQL/SQLite is ASCII-only (Rust-sideto_uppercase()vs the database’sUPPER()); PostgreSQL (ILIKE) is unaffected. - Join loading assumes the entity’s primary-key field is named
id. The PK value type is generic, but a join target whose PK field has a different name will not compile.
0.9.1 - 2026-06-01
Fixed
-
LIKE queries broken on Postgres.
build_like_conditionand the fulltext search functions used?as the bind placeholder inExpr::cust_with_valuestemplates. Sea-query’s Postgres backend uses$as its placeholder character, so the?was passed through as a literal, and Postgres then parsed? ESCAPE '!'as a JSONB operator followed by a type cast, producingtype "escape" does not exist. Fixed by using$1for Postgres and?for MySQL/SQLite. -
build_like_conditionmissingESCAPEclause. The LIKE condition forlike_filterablefields used sea-query’s.like()which never emitted anESCAPEclause. Rewritten to useExpr::cust_with_valueswithESCAPE '!', matching the fulltext functions. -
Fulltext search on non-text columns. The fallback LIKE search path (when
fulltext_searchable_columns()is empty) appliedUPPER(col) LIKE ...to all searchable columns including booleans. Postgres and MySQL rejectUPPER(boolean). Fixed by casting columns toTEXT(Postgres/SQLite) orCHAR(MySQL) beforeUPPER(). -
LIKE escape character conflicts with Postgres string quoting. Switched
escape_like_wildcardsfrom backslash to!as the escape character. Backslash inside single-quoted SQL strings is ambiguous across backends (Postgresstandard_conforming_strings).
Changed
-
Removed dead codegen helpers (
runtime_fk_*functions) fromcrudcrate-derive. -
Resolved clippy warnings across the workspace (collapsible ifs, duplicate match arms, missing error docs,
unwrapafteris_some). -
Updated trybuild snapshot for rustc 1.96.
0.9.0 - 2026-05-19
Security
-
SecurityProfileconfig struct + presets. Newcrudcrate::SecurityProfilebundles the security-sensitive runtime defaults (strict filter parsing, scope propagation, deleted-ID exposure, and request body size) under one type with three presets:SecurityProfile::secure(),react_admin(), andlegacy(). Override individual fields via Rust’s struct-update syntax:SecurityProfile { expose_deleted_ids: true, ..SecurityProfile::secure() }. -
Per-resource override via derive attribute.
#[crudcrate(security_profile = "secure" | "react_admin" | "legacy")]generates aCRUDResource::security_profile()impl that returns the named preset. -
Global override via Axum extension. Apply
.layer(Extension(SecurityProfile::secure()))on your router to override the per-resource setting at request time. Resolution order:Extension > CRUDResource::security_profile() > trait default. -
Default profile flipped to
secure(). New resources ship hardened defaults. See MIGRATION_0.9.md for the per-flag breakdown and opt-out instructions. -
Explicit batch body limit. The generated router now applies an Axum
DefaultBodyLimit::max(...)layer derived fromSecurityProfile::max_request_body_bytes(default 2 MiB, matching axum-core’s baseline). Previous behavior relied on Axum’s implicit default and broke if any consumer wiredDefaultBodyLimit::disable()up the tree. -
Scope-propagation side-channel guard. Under
secure()profile, joined filters (?filter={"vehicles.color":"..."}) on a child entity that has noexclude(scoped)scope condition are rejected with400 Bad Requestwhen the request carries aScopeCondition. Prevents parent-existence side-channels via unscoped child columns. -
Strict filter parsing. Under
secure()profile, a malformed?filter=...value returns400instead of silently dropping the filter and returning the unfiltered result. -
Deleted-ID enumeration guard. Under
secure()profile, batch delete responses return{"deleted": N}instead of the array of UUIDs that actually existed in the database, removing the existence-enumeration side-channel through the delete endpoint. react-admin frontends that rely on the ID array for cache invalidation should pinSecurityProfile::react_admin()orlegacy(). -
Fulltext SQL bind parameterization. The Postgres / MySQL / SQLite fulltext condition builders now route the user query value through
Expr::cust_with_valuesso the value is bound as a parameter rather than interpolated into the SQL string. Defense-in-depth: column names were already compile-time-known, but rawSimpleExpr::Custom(format!(...))was removed everywhere user input could reach it.
Fixed
-
Join loading with
operationsattribute. Entities using#[crudcrate(operations = MyOps)]for create/update/delete hooks had their join loading silently bypassed onget_oneandget_all; the codegen delegated entirely toCRUDOperationswhich does plain queries with no relation loading. The operations path now falls through to the standard join-loading codegen when the entity hasjoin(...)fields, withbefore_get_one/after_get_oneandbefore_get_all/after_get_allhooks wrapping the join-loaded body.get_one_scopedandget_all_scopedare also generated for this path (previously missing entirely). -
FK column resolution in batch loading. The batch loader and join loader now resolve FK columns from the SeaORM
RelationDefat runtime instead of guessing from the struct name convention. Joins with non-standard FK names (eg.author_refinstead ofauthor_id) now load correctly.
Changed
-
Replaced unmaintained
impls = "1"(no release since 2019) with an inlinecrudcrate::impls!macro. Same autoref-specialization semantics, 30 LOC, no behavior change. -
Workspace dependencies bumped:
axum 0.8.6 → 0.8.9,sea-orm 1.1.19 → 1.1.20,serde_json → 1.0.149,uuid → 1.23.1,tokio → 1.52.3,chrono → 0.4.44,tower-http → 0.6.11,utoipa → 5.5.0, plus proc-macro andrust_decimalpatches. -
url-escape(unmaintained dev dep) replaced withpercent-encoding.
Documentation
README.md: added security caveat for themysqlfeature, which pulls inrsa 0.9.10(RUSTSEC-2023-0071, Marvin attack, no upstream fix).
0.8.1 - 2026-05-19
Security
-
Filter clause limit. Requests with more than 100 filter keys are rejected with
400 Bad Request(MAX_FILTER_CLAUSES = 100). Prevents query-planning DoS via oversized filter payloads. -
DB error sanitization. Internal database error messages are stripped from client-facing responses. Only a generic prefix is returned; the full error is logged via
tracing.
Added
-
Joined filters are now applied by the default handler. Requests like
GET /customers?filter={"vehicles.make":"BMW"}previously parsed and whitelisted the filter but silently dropped it before hitting the database; users got unfiltered results. The defaultget_all_handlernow resolves eachJoinedFilterinto a sub-query on the child table (with the child’sScopeFilterable::scope_condition()applied), collects matching parent-FK values, and addsid IN (...)to the main condition. Query shape: one extraSELECT parent_fk FROM child WHERE ...per joined-filter field plus the usual list + count queries: no JOIN, noDISTINCT. Backed bytest_suite/tests/joined_filter_http_test.rsand a runnablecargo run --example joined_filter. -
New
CRUDResource::resolve_joined_filterstrait method. Takes the parsed condition plus the&[JoinedFilter]list and returns the augmented condition to use for both the list query and the count query. Default impl logs and returns the condition unchanged (backward compatible for non-derive users); the derive macro generates an override for every resource that declaresjoin(..., filterable(...))on anyVec<Child>field. -
New public helper
crudcrate::build_comparison_expr. Translates a column +FilterOperator+serde_json::Valueinto anOption<SimpleExpr>for use in custom filter resolvers.
Changed
-
crudcrate::filtering::ParsedFilters::joined_filtersis now consumed by the handler (previously only populated by the parser and read by tests). No API change; the field was already public. -
Pruned unused dependencies from the workspace.
Documentation
docs/src/features/filtering.md“Filtering on Related Entities” rewritten to describe the actual query shape, scope-safety guarantees, and theVec<Child>-only limitation. Removed the stale “requires a customread::many::bodyhook” note.docs/src/features/relationships.mdmigrated from the deprecatedjoin_filterable(...)/join_sortable(...)syntax to the currentfilterable(...)/sortable(...)insidejoin(...).
0.8.0 - 2026-04-17
Security
- Atomic scope check in
get_one: Scopedget_onerequests now verify the scope condition in a single query (ID + scope filter), eliminating a TOCTOU race where a separatetotal_count()verification could see stale data between the fetch and the check. - FK column runtime validation: The derive macro generates
#[cfg(test)]functions that verify convention-derived FK column names match the actualRelationDeffrom SeaORM at test time. Catches silent data mismatches from FK naming convention violations before they reach production. - SQL-level scope filtering for joins (all endpoints, all depths): Child entities with
exclude(scoped)fields are now filtered at the SQL level (WHERE is_private = false) during join loading on bothget_one_scopedandget_all_scoped, and at every depth whendepth > 1. The scoped batch loader applies each child’sScopeFilterable::scope_condition()to itsEntity::find().filter(FK in parent_ids)query, and recurses viaget_one_scoped(notget_one) for nested children. The in-memoryScopeFilterable::is_scope_visible()filter remains as defense-in-depth, but privacy is now enforced in the database, not just at serialisation time: private rows never leave Postgres on public endpoints. require_scopeattribute: New#[crudcrate(require_scope)]struct-level attribute. When set, read handlers return HTTP 500 if noScopeConditionmiddleware is present; this catches misconfigured routes that should be scoped but aren’t.
Added
-
Struct-level join definitions: Join fields can now be defined at the struct level instead of on the SeaORM Model. This keeps the Model lightweight and avoids stack overflow when loading entities with heavy join types. The join field only exists on the generated API struct.
#[crudcrate( api_struct = "Site", join(name = "replicates", result = "Vec<SiteReplicate>", one, all, depth = 1) )] pub struct Model { /* no replicates field here */ }Field-level joins with
#[sea_orm(ignore)]+#[crudcrate(non_db_attr, join(...))]still work for backward compatibility. -
SQL-level column exclusion for
exclude(list): Fields marked#[crudcrate(exclude(list))]withOption<T>types are now skipped at the SQL level in list queries; the database never transfers the data. Previously,exclude(list)only removed the field from the response struct while still fetching all columns. This dramatically improves performance for entities with large fields (photos, blobs, documents). Benchmarked at 7x improvement (1,013 → 7,121 req/s) on an endpoint with base64 photo data. -
ScopeConditionfor auth-aware query filtering: NewScopeConditiontype that can be injected via AxumExtensionto add conditions to read queries. Auth-system-agnostic: users write middleware to convert their auth state into aScopeCondition. When present,get_all_handlermerges the condition into the query filter, andget_one_handlerverifies the fetched record passes the condition. Write operations are unaffected.use crudcrate::ScopeCondition; let public = Article::read_only_router(&db) .layer(Extension(ScopeCondition( Condition::all().add(article::Column::IsPrivate.eq(false)) ))); -
read_only_router()method: Generates a router with only GET endpoints (get_one + get_all), no create/update/delete. Use withScopeConditionfor public/filtered API endpoints. -
fk_columnjoin parameter: Optionalfk_column = "ColumnName"injoin(...)attributes for entities where the FK column doesn’t follow the{StructName}Idconvention. The convention remains the default; this is an escape hatch for non-standard schemas.#[crudcrate(join(one, all, depth = 1, fk_column = "OwnerUuid"))] pub items: Vec<Item>, -
ScopeFilterable::scope_condition(): New trait method that returns asea_orm::Conditionmatching an entity’sexclude(scoped)fields. Auto-generated by the derive macro. Enables SQL-level scope filtering for join queries. -
get_one_scoped/get_all_scoped: NewCRUDResourcetrait methods with scope-aware query variants. Default implementations delegate to the non-scopedget_one/get_all(safe for resources withoutjoin(all)children). The derive macro overrides both with SQL-level child-scope propagation.get_all_handlerdispatches toget_all_scopedwhenever aScopeConditionextension is present.
Fixed
- Stack overflow with many joins: All join-loading futures are now
Box::pinned, moving large async state off the stack. Prevents stack overflow in debug builds with many join fields. - Async state machine bloat in debug builds: All join-loading futures are wrapped in
Box::pin, preventing debug-build async state machine bloat fromRelated<E>monomorphization.
Changed
depth = 0is now a compile error: Usedepth = 1for shallow loading. Previouslydepth = 0could cause infinite recursion at runtime.- Compile-time bidirectional relation detection: Joins targeting an entity that has a
Related<Self>impl (bidirectional/cyclic relationship) now produce a compile error unless an explicitdepthis set. Previously, these silently caused infinite recursion at runtime via SeaORM’sRelation::def()chain. The error message explains the cycle and suggests the fix. - Compile-time warnings for risky join depths: Self-referencing joins without an explicit
depthand joins withdepth > 5now emit#[deprecated]warnings at compile time, guiding users to set safe depth values.
0.7.2 - 2026-03-27
Added
- Automatic enum field detection: Fields with types implementing
sea_orm::ActiveEnumare now detected at compile time; no#[crudcrate(enum_field)]annotation needed. Uses zero-cost compile-time trait detection (inherent impl trick) to check each field’s type. - Case-insensitive enum array filtering: Array/IN filters on enum fields now apply
UPPER(CAST(col AS TEXT))on Postgres, matching the case-insensitive behavior already used for single-value enum filters.
Deprecated
#[crudcrate(enum_field)]: No longer required. Enum fields are auto-detected from theActiveEnumtrait implementation. The attribute still works for backward compatibility but can be safely removed.
Fixed
- Array/IN filtering on enum fields:
process_array_filter()now handles enum fields by casting to TEXT and uppercasing on Postgres. Previously, array filters on enum columns could fail on native Postgres ENUM types or produce case-sensitive results.
0.7.1 - 2026-03-09
Added
- Transform Hooks: New
transformphase in hook system for result modification- Hook execution order: pre → body → transform → post
- Transform hooks receive the result and return a modified version
- Allows enriching, decorating, or transforming CRUD results before returning
- Supported for all operations: create, read, update, delete (one and many)
- Example:
#[crudcrate(read::one::transform = enrich_with_metadata)]
- Partial Success for Batch Operations: New
?partial=truequery parameter for batch endpoints- Returns HTTP 207 Multi-Status when some items succeed and some fail
- Response includes
succeededandfailedarrays with indices and error messages - Available for:
POST /batch,PATCH /batch,DELETE /batch - New types:
BatchResult<T>,BatchFailure,BatchOptions - Note: Partial mode processes items individually using single-item hooks (
create::one::*, etc.), not batch hooks (create::many::*). Each item commits independently with no shared transaction.
- Batch Create/Update Endpoints:
POST /batchandPATCH /batchfor bulk operations- Transaction-based all-or-nothing semantics by default
- Pre-validation for batch updates ensures true atomicity across all DB backends
- Runtime-Configurable Limits: Override batch and pagination limits per-resource
#[crudcrate(batch_limit = 500)]- Max items for batch create/update/delete (default: 100)#[crudcrate(max_page_size = 500)]- Max items per page (default: 1000)- Trait methods
fn batch_limit()andfn max_page_size()can be overridden for runtime logic (env vars, config)
- Security Startup Log: Info-level log message when mounting CRUD routes
- Reports resource name, table, batch_limit, max_page_size, and enabled security defaults
- Silent when no tracing subscriber is configured
- Batch Loading for Joins (N+1 Query Fix): Optimized
get_all()with joins- Reduced from N+1 queries to 2 queries for depth=1 joins (1 for parents + 1 per join field). Deeper joins (depth > 1) may issue additional queries to load nested relations.
- Uses
WHERE parent_id IN (...)with in-memory grouping
- Documentation Test Links: New mdbook preprocessor linking documentation examples to test files
- IDE Documentation: Comprehensive attribute reference in crate-level documentation
Changed
- Documentation Overhaul: Complete restructure of tutorial documentation
- New progressive tutorial: First Steps → Auto IDs → Timestamps → Filtering → Sorting → Search → Hiding Fields → Relationships → Hooks
- Simplified navigation structure in SUMMARY.md
- Enhanced examples with “Run It Now” sections
- Net reduction of ~800 lines while covering more features
- DateTimeWithTimeZone schema fix: All generated model structs (API, Create, Update, List, Response) now resolve
DateTimeWithTimeZonetochrono::DateTime<chrono::FixedOffset>so utoipa’s ToSchema derive recognizes it as a DateTime type - Generated API struct derives now use fully qualified paths (
serde::Serialize,utoipa::ToSchema, etc.) to avoid conflicts with user imports - Bumped
sea-ormfrom 1.1.17 to 1.1.19 - Batch operation limit checking now uses
Self::batch_limit()method (configurable per-resource) BATCH_LIMITandMAX_PAGE_SIZEchanged from associated constants to trait methods for runtime overridability- Batch loading uses
.remove()from HashMap instead of.get().cloned(), moving data instead of copying
Fixed
- UUID array filtering now passes native
Uuidvalues tois_in()instead of stringified values, fixing incorrect query generation for UUID column arrays max_page_size()trait method now enforced in HTTP pagination handlerdelete_many()returns only actually-deleted IDsupdate_many()removed redundant pre-validation queries outside the transaction (TOCTOU race)- Self-referencing join errors now logged via
tracing::warn!instead of silently swallowed - Nested relation loading errors (
get_one()fallbacks) now logged viatracing::warn! to_snake_casein FK derivation now handles acronyms correctly- Batch loading uses PK field name from entity metadata instead of hardcoded
id update()trait default used plural instead of singular resource name in not-found errordelete_many()trait default had no batch limit check (now enforcesbatch_limit())- Broken cross-reference links in reference documentation
- Clippy doc-markdown warnings
Removed
BatchUpdateItem<T>: Dead struct removed from public API- Dead code path: Unreachable self-referencing branch in batch loading
- Documentation: Legacy tutorial structure replaced by progressive tutorials
0.7.0 - 2025-11-26
Security
- Harden search queries with proper wildcard escaping
- Improve input sanitization in filtering and pagination
- Add pagination limits to prevent excessive queries
Added
- Join Filtering: Filter by related entity columns using dot-notation syntax
filterable("col1", "col2")nested insidejoin(...)attribute- Query:
?filter={"vehicles.make":"BMW"} - All standard operators supported (
_gt,_gte,_lt,_lte,_neq) - Single-level joins only (nested paths like
vehicles.parts.namenot supported)
- Join Sorting: Sort by related entity columns using dot-notation syntax
sortable("col1", "col2")nested insidejoin(...)attribute- Query:
?sort=["vehicles.year","DESC"]or?sort_by=vehicles.year&order=DESC - Single-level joins only (nested paths not supported)
- Hook System: Attribute-based customization with
{operation}::{cardinality}::{phase}syntax- Operations:
create,read,update,delete - Cardinality:
one(single),many(batch) - Phases:
pre,body,post - Example:
#[crudcrate(create::one::pre = validate_fn)]
- Operations:
- Batch operations:
create_manyandupdate_manywith hook support ApiErrorerror type: Consistent error handling with separate internal/client messages (fixes #3)impl From<DbErr>for Sea-ORM error conversion with automatic internal logging- Internal errors logged via
tracing, generic message sent to client - Custom errors:
ApiError::custom(StatusCode::IM_A_TEAPOT, "client msg", Some("internal log")) - Variants:
NotFound,BadRequest,Unauthorized,Forbidden,Conflict,ValidationFailed,Database,Internal,Custom
- Lifecycle hooks in
CRUDOperationstrait - Improved test coverage across modules
Changed
- Major codebase refactoring (38% size reduction)
- Removed
index_analysismodule - Simplified
relation_validator.rs - Consolidated join/recursion handling
- Modular
codegen/structure
- Removed
- Handler code generation refactored for hook flow
- Replace
eprintln!withtracingfor logging - Legacy
fn_*attributes auto-map to new hook syntax
Fixed
- Improved error handling in join path parsing
- Fixed flaky tests with serial execution
- All clippy::pedantic warnings resolved
Removed
index_analysismodule: Database index recommendations moved to external tooling (pgAdmin, MySQL Workbench, etc.)register_crud_analyser!macro: No longer needed without index analysisattributes.rs: Dead code (IDE autocomplete hints only, never used at runtime)join_strategies/module: Consolidated intocodegen/joins/field_analyzer.rs: Reorganized intofields/module- Redundant examples:
minimal_debug.rs,minimal_spring.rs,test_router_only.rs - Verbose documentation: ~400 lines of excessive doc comments trimmed
Dependencies
- Added
serial_test = "3.2"for test isolation - Added
tracingfor structured logging
0.6.1 - 2025-11-03
Fixed
- Global path resolution of joined structs
- Restructuring of crudcrate-derive into smaller modules, bit by bit.
0.6.0 - 2025-10-31
Added
- Recursive Join Loading: Multi-level relationship loading with
#[crudcrate(join(one, all))]attribute - Cyclic dependency detection at compile-time with actionable error messages
- Unlimited join depth support with default depth warnings for relationships > 3 levels
exclude()function-style syntax for model exclusion:#[crudcrate(exclude(create, update))]- The get one response is now its own model, allowing for exclusion of fields from get one/create/update responses
- New
recursive_joinexample demonstrating nested relationship loading - Debug output functionality for procedural macros with
debug_outputattribute
Changed
- derive: Removed requirement for
EqandPartialEqderives on generated API structs - derive: Improved multi-pass code generation to handle cyclic dependencies
Fixed
- Database test cleanup logic for PostgreSQL and MySQL backends
- Relationship loading in
get_one()andget_all()endpoints
Dependencies
- derive: Updated with recursive join support, cyclic dependency detection, and enhanced attribute parsing
0.5.0 - 2025-08-28
Added
- Spring-RS framework support with minimal example in
/examples - Restored CRUD benchmarks from 0.4.5
Changed
- Moved
crudcrate-deriveand examples into repository - Simplified framework architecture - removed redundant code generation paths
- Refactored macro code generation by splitting helpers.rs into focused modules
Removed
- BREAKING: Case-sensitive enum filtering functionality
0.4.5 - 2025-08-25
Fixed
- Batch delete endpoints now returns the array of successfully deleted resource UUIDs, suitable for a react-admin batch delete response.
0.4.4 - 2025-08-20
Added
- Index analysis system for database optimization recommendations
analyse_indexes_for_resourceandanalyse_all_registered_modelsfunctions- Database-specific index recommendations with priority-based output
Changed
- BREAKING (if still using CRUDResource manually): Added required
TABLE_NAMEconstant toCRUDResourcetrait. This does not affectEntityToModelfunctionality. - Made
validate_field_valuefunction const - Improved code organization with extracted helper functions
Fixed
- All clippy warnings (pessimistic and pedantic)
- Test compilation errors and naming inconsistencies
- Documentation examples and missing trait implementations
0.4.3 - 2025-08-19
Added
- Testing: Integration tests for
create_model=falsecompatibility withnon_db_attr - Testing: Comprehensive test suite for
use_target_modelsfunctionality with cross-model referencing
Fixed
- derive: Resolved lingering compilation errors from List model update
- derive: Fixed test compatibility issues following List model integration
- Filter system: Minor improvements to filtering logic consistency
Dependencies
- derive: Updated to latest version with enhanced List model support and improved compatibility
0.4.2 - 2025-08-18
Added
- List Model Support: New
Listmodel generation capability for customizing fields returned in list/getAll endpoints, similar to Create and Update models - Generated List model behavior with field deselection support
- Built-in
getAllquery optimization to only return fields specified in List model - derive: Support for reserved field names using
r#syntax (e.g.,r#type) - derive: Enhanced target model usage with CRUDResource structs for cross-model referencing
- derive: Automatic
From<>trait generation for List structs from Sea-ORM DB models
Changed
- derive: Improved trait compatibility by re-adding
PartialEq,Eq,Debug, andClonederives to models for Sea-ORM compatibility - derive: Route generation now uses root-level paths instead of prefixed routes for better user control
- derive: Enhanced
use_target_modelsfunctionality for better cross-model integration
Fixed
- derive: Fixed ActiveModel generation when create model excludes keys
- derive: Fixed
create_model=falsecompatibility withnon_db_attr - derive: Improved function linking in crudcrate function overrides
- derive: Fixed trait signature for Condition in get_all operations
- derive: Various clippy warnings resolved
Dependencies
- derive: Updated to 0.2.6 with List model support, reserved field handling, and enhanced model generation capabilities
0.4.1 - 2025-08-05
Added
- Index analysis functionality with
analyze_indexes_for_resource()andanalyze_and_display_indexes()methods - Full-text search support in filtering system with
fulltext_searchable_columns()method - REST-standard pagination and query filters alongside React Admin compatibility
- Multi-database testing support (SQLite, PostgreSQL, MySQL) via
DATABASE_URLenvironment variable - Comprehensive benchmark suite with performance testing across database backends
- Security integration tests for SQL injection protection
- Coverage reporting with Codecov integration
- Database feature flags for selective driver compilation (
mysql,postgresql,sqlite) - Binary size optimization through conditional database driver inclusion
Changed
- Enhanced filtering system with enum case insensitivity and improved edge case handling
- Updated README with minimal examples and comprehensive testing documentation
- Restructured test infrastructure to support multiple database backends
- Improved error handling in filter parsing with better validation
- Removed Clone requirement from generated API structs (Create/Update models)
- Optimized trait methods to use references instead of owned values where possible
- Sea-ORM dependency now uses
default-features = falsewith selective feature enabling - Enhanced README with database feature selection examples
Fixed
- Enum filtering now supports case-insensitive matching
- Filter edge cases handle malformed JSON gracefully
- PostgreSQL test isolation issues with race conditions during parallel execution
- Clippy warnings resolved across codebase
- derive: Improved integration tests and restructured codebase
Dependencies
- derive: Updated to 0.2.1 with full-text search support and enhanced router generation capabilities
- derive: Removed Clone derives from generated structs to reduce memory overhead
0.4.0 - 2025-07-17
Added
- Enhanced Router Generation: Automatic router generation via
generate_routerattribute inEntityToModelsmacro - Non-Database Field Support: Complete support for non-DB fields using
#[sea_orm(ignore)]+#[crudcrate(non_db_attr = true)]pattern - Single-File API Capability: Full CRUD API can now be implemented in under 60 lines of code
- Documentation improvements for non-DB field usage with examples
- derive: EntityToModels macro with complete entity-to-API generation and CRUDResource implementation
- derive: Router generation capability integrated into EntityToModels
- derive: Enhanced support for non-database fields with proper Sea-ORM integration
- derive: Comprehensive integration tests and restructured codebase
Changed
- Enhanced
EntityToModelsmacro to automatically generate router functions - Improved documentation with comprehensive non-DB field examples
- Router generation now fully automated with zero boilerplate
- derive: Enhanced
ToCreateModelandToUpdateModelwith new trait system - derive: Added
MergeIntoActiveModeltrait implementation
Fixed
- derive: Test infrastructure improvements and better error handling in macro generation
0.3.3 - 2025-06-23
Fixed
- Fix newline formatting in auto-generated OpenAPI documentation
- Remove debug messages from production builds
Changed
- Accept enum exact comparison in filter queries
- Filter on integer columns support
0.3.2 - 2025-06-06
Changed
- Bump dependencies including crudcrate-derive for improved
into()casting support
Dependencies
- derive: Updated to 0.1.6 with improved
.into()casting support and enhanced field attribute handling
0.3.1 - 2025-05-12
Changed
- Update lockfile and enhance filtering capabilities for enum and integer columns
0.3.0 - 2025-04-05
Added
- Major: Default implementations for
get_one,get_all, andupdate_oneinCRUDResourcetrait - New
MergeIntoActiveModeltrait for improved update model handling - Enhanced derive macro integration with new trait system
Changed
- Restructured core trait system for better usability
- Updated derive macro to reference new
MergeIntoActiveModeltrait
Dependencies
- derive: Updated to 0.1.5 with
IntoActiveModeltrait forUpdateModeland improved trait derivations
0.2.5 - 2025-04-04
Added
- Export
serde_withfor better serialization support - Enhanced error responses in API endpoints
- Documentation for query parameters
Changed
- Renamed
openapi.rstoroutes.rsfor better organization - Updated dependencies
0.2.4 - 2025-03-11
Added
- Description string support in CRUDResource
- Auto-populated summary and description for macro-generated endpoints
- Enhanced OpenAPI documentation generation
Dependencies
- derive: Updated to 0.1.4 with improved serialization support using exported
serde_with
0.2.3 - 2025-03-07
Added
- Comprehensive OpenAPI macro support
- Better API documentation generation
Fixed
- Improved error responses in endpoints
0.2.2 - 2025-03-06
Added
- Documentation for query parameters
0.2.1 - 2025-03-05
Added
- Description string support in CRUDResource
- Auto-populated summary and description for macro-generated endpoints
0.2.0 - 2025-03-05
Changed
- Breaking: Major refactor from route-based to macro-based approach
- Introduced
crud_handlers!macro for generating CRUD endpoints - Simplified API creation process significantly
Removed
- Legacy route-based implementation
0.1.4 - 2025-03-03
Fixed
- Fixed return type of
delete_onehandler - Applied clippy suggestions for performance improvements
0.1.3 - 2025-02-19
Changed
- Update crudcrate-derive to allow non-db parameters in update/create models
Dependencies
- derive: Updated to 0.1.3 with support for auxiliary attributes in structs that don’t relate to DB model
0.1.2 - 2025-02-18
Changed
- Update proc macro to 0.1.2
Dependencies
- derive: Updated to 0.1.2 with improved trait derivations (Clone instead of Copy where appropriate)
0.1.0 - 2025-02-18
Added
- Initial release of crudcrate
- Basic CRUD operation framework
- Sea-ORM and Axum integration
- OpenAPI documentation support
- Move common functions and traits from existing API
- Import proc-macros from crudcrate-derive
Dependencies
- derive: Initial release (0.1.0) with
ToCreateModelandToUpdateModelderive macros, field-level attribute support for CRUD customization, and integration with Sea-ORM ActiveModel system