7 Commits

29 changed files with 2363 additions and 673 deletions
+4
View File
@@ -0,0 +1,4 @@
allow-expect-in-tests = true
check-private-items = true
allowed-duplicate-crates = []
+22 -6
View File
@@ -1,7 +1,23 @@
# OS Files
*~
._*
.DS_Store
*
!**/
# Rust
/target
!.zed/settings.json
!.gitignore
!/CONTRIBUTING.md
!/LICENSE.md
!/README.md
!/.clippy.toml
!/.rustfmt.toml
!/.tombi.toml
!/Cargo.toml
!/Cargo.lock
!/justfile
!/rust-toolchain.toml
!/crates/**/*.rs
!/crates/**/Cargo.toml
!/crates/**/README.md
!/lints/*.toml
-1
View File
@@ -1,2 +1 @@
hard_tabs = true
wrap_comments = true
+10
View File
@@ -0,0 +1,10 @@
[format.rules]
indent-style = "tab"
indent-width = 4
line-width = 120
# Disable TOML 1.1 features from tombi as long as we support rust <1.94
[[schemas]]
toml-version = "v1.0.0"
path = "tombi://www.schemastore.org/cargo.json"
include = ["Cargo.toml"]
-9
View File
@@ -1,9 +0,0 @@
{
"recommendations": [
"vadimcn.vscode-lldb",
"barbosshack.crates-io",
"usernamehw.errorlens",
"tamasfe.even-better-toml",
"rust-lang.rust-analyzer",
]
}
-33
View File
@@ -1,33 +0,0 @@
{
// VSCode
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"files.exclude": {
"**/target": true,
"**/Cargo.lock": true,
},
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"files.watcherExclude": {
"**/.git/**": true,
"**/target/**": true,
},
// Extensions
"crates.listPreReleases": true,
"evenBetterToml.formatter.alignComments": true,
"evenBetterToml.formatter.alignEntries": false,
"evenBetterToml.formatter.allowedBlankLines": 1,
"evenBetterToml.formatter.arrayAutoExpand": true,
"evenBetterToml.formatter.arrayTrailingComma": true,
"evenBetterToml.formatter.columnWidth": 80,
"evenBetterToml.formatter.reorderKeys": true,
"evenBetterToml.formatter.trailingNewline": true,
"rust-analyzer.imports.granularity.enforce": true,
"rust-analyzer.imports.granularity.group": "module",
"rust-analyzer.imports.group.enable": true,
"rust-analyzer.imports.merge.glob": false,
"rust-analyzer.imports.preferNoStd": true,
"rust-analyzer.showUnlinkedFileNotification": false,
}
+26
View File
@@ -0,0 +1,26 @@
{
"lsp": {
"just-lsp": {
"initialization_options": {
"formatting": {
"indentation": "\t",
},
},
},
"rust-analyzer": {
"initialization_options": {
"imports": {
"granularity": { "enforce": true, "group": "module" },
"group": { "enable": true },
"merge": { "glob": false },
"preferNoStd": true,
},
"server": {
"extraEnv": {
"RUSTUP_TOOLCHAIN": "stable",
},
},
},
},
},
}
+2 -1
View File
@@ -3,4 +3,5 @@ How to Contribute
We'd love to accept your patches and contributions to this project.
We just need you to follow the Contributor License Agreement outlined
in the latest v0.0.x of https://github.com/Skrunix/license
in the latest v0.0.x of https://git.skrundz.dev/skrunix/license
(mirrored to https://github.com/skrunix/license)
Generated
+1367 -441
View File
File diff suppressed because it is too large Load Diff
+535 -35
View File
@@ -1,43 +1,543 @@
[workspace]
members = ["seamantic"]
resolver = "2"
resolver = "3"
members = ["crates/*"]
[workspace.package]
authors = []
edition = "2024"
rust-version = "1.94"
license-file = "LICENSE.md"
rust-version = "1.85.0"
[workspace.lints.rust]
arithmetic_overflow = "forbid"
missing_docs = "forbid"
unsafe_code = "forbid"
unused_doc_comments = "forbid"
[workspace.lints.clippy]
arithmetic_side_effects = "forbid"
as_conversions = "forbid"
checked_conversions = "forbid"
default_union_representation = "forbid"
expect_used = "forbid"
indexing_slicing = "forbid"
integer_division = "forbid"
integer_division_remainder_used = "forbid"
transmute_undefined_repr = "forbid"
unchecked_duration_subtraction = "forbid"
unwrap_used = "forbid"
[profile.release]
codegen-units = 1
lto = "fat"
opt-level = 3
overflow-checks = true
strip = "debuginfo"
publish = false
[workspace.dependencies]
sea-orm = { version = "2.0.0-rc.17", default-features = false }
sea-orm-migration = { version = "2.0.0-rc.17", default-features = false }
seamantic = { path = "crates/seamantic", version = "=1.0.1", default-features = false }
serde = { version = "^1", default-features = false }
serde_test = { version = "^1", default-features = false }
tokio = { version = "^1", default-features = false }
sea-orm = { version = "^2.0.0", default-features = false }
sea-orm-migration = { version = "^2.0.0", default-features = false }
serde = { version = "^1.0.220", default-features = false }
serde_test = { version = "^1.0.0", default-features = false }
tokio = { version = "^1.38.2", default-features = false }
[workspace.lints.clippy]
cargo = { level = "warn", priority = -1 }
complexity = { level = "warn", priority = -1 }
correctness = { level = "deny", priority = -1 }
perf = { level = "warn", priority = -1 }
style = { level = "warn", priority = -1 }
suspicious = { level = "warn", priority = -1 }
absolute-paths = "allow"
alloc-instead-of-core = "warn"
allow-attributes = "warn"
allow-attributes-without-reason = "deny"
arbitrary-source-item-ordering = "allow"
arithmetic-side-effects = "deny"
as-conversions = "deny"
as-pointer-underscore = "deny"
as-ptr-cast-mut = "deny"
as-underscore = "deny"
assertions-on-result-states = "warn"
assigning-clones = "warn"
big-endian-bytes = "allow"
bool-to-int-with-if = "warn"
borrow-as-ptr = "warn"
branches-sharing-code = "warn"
case-sensitive-file-extension-comparisons = "warn"
cast-lossless = "deny"
cast-possible-truncation = "deny"
cast-possible-wrap = "deny"
cast-precision-loss = "deny"
cast-ptr-alignment = "deny"
cast-sign-loss = "deny"
cfg-not-test = "deny"
checked-conversions = "deny"
clear-with-drain = "warn"
clone-on-ref-ptr = "warn"
cloned-instead-of-copied = "warn"
coerce-container-to-any = "warn"
cognitive-complexity = "allow"
collapsible-else-if = "warn"
collection-is-never-read = "warn"
comparison-chain = "warn"
copy-iterator = "warn"
create-dir = "warn"
dbg-macro = "warn"
debug-assert-with-mut-call = "deny"
decimal-bitwise-operands = "warn"
decimal-literal-representation = "warn"
default-numeric-fallback = "warn"
default-trait-access = "warn"
default-union-representation = "allow"
deref-by-slicing = "warn"
derive-partial-eq-without-eq = "warn"
disallowed-script-idents = "deny"
doc-broken-link = "warn"
doc-comment-double-space-linebreaks = "warn"
doc-include-without-cfg = "warn"
doc-link-code = "warn"
doc-link-with-quotes = "warn"
doc-markdown = "warn"
doc-paragraphs-missing-punctuation = "warn"
duration-suboptimal-units = "warn"
elidable-lifetime-names = "warn"
else-if-without-else = "warn"
empty-drop = "warn"
empty-enum-variants-with-brackets = "warn"
empty-enums = "warn"
empty-structs-with-brackets = "warn"
enum-glob-use = "warn"
equatable-if-let = "warn"
error-impl-error = "warn"
exhaustive-enums = "warn"
exhaustive-structs = "warn"
exit = "warn"
expect-used = "warn"
expl-impl-clone-on-copy = "warn"
explicit-deref-methods = "warn"
explicit-into-iter-loop = "warn"
explicit-iter-loop = "warn"
fallible-impl-from = "warn"
field-scoped-visibility-modifiers = "warn"
filetype-is-file = "warn"
filter-map-next = "warn"
flat-map-option = "warn"
float-arithmetic = "warn"
float-cmp = "warn"
float-cmp-const = "warn"
fn-params-excessive-bools = "allow"
fn-to-numeric-cast-any = "warn"
format-collect = "warn"
format-push-string = "warn"
future-not-send = "warn"
get-unwrap = "warn"
host-endian-bytes = "allow"
if-not-else = "warn"
if-then-some-else-none = "warn"
ignore-without-reason = "warn"
ignored-unit-patterns = "warn"
impl-trait-in-params = "warn"
implicit-clone = "warn"
implicit-hasher = "warn"
implicit-return = "allow"
imprecise-flops = "warn"
inconsistent-struct-constructor = "warn"
index-refutable-slice = "deny"
indexing-slicing = "deny"
inefficient-to-string = "warn"
infinite-loop = "warn"
inline-always = "warn"
inline-asm-x86-att-syntax = "warn"
inline-asm-x86-intel-syntax = "warn"
inline-modules = "allow"
inline-trait-bounds = "allow"
integer-division = "deny"
integer-division-remainder-used = "deny"
into-iter-without-iter = "warn"
invalid-upcast-comparisons = "warn"
ip-constant = "warn"
items-after-statements = "warn"
iter-filter-is-ok = "warn"
iter-filter-is-some = "warn"
iter-not-returning-iterator = "warn"
iter-on-empty-collections = "warn"
iter-on-single-items = "warn"
iter-over-hash-type = "warn"
iter-with-drain = "warn"
iter-without-into-iter = "warn"
large-digit-groups = "warn"
large-futures = "warn"
large-include-file = "warn"
large-stack-arrays = "warn"
large-stack-frames = "warn"
large-types-passed-by-value = "warn"
let-underscore-must-use = "warn"
let-underscore-untyped = "warn"
linkedlist = "warn"
literal-string-with-formatting-args = "warn"
little-endian-bytes = "allow"
lossy-float-literal = "warn"
macro-use-imports = "warn"
manual-assert = "warn"
manual-assert-eq = "warn"
manual-ilog2 = "warn"
manual-instant-elapsed = "warn"
manual-is-power-of-two = "warn"
manual-is-variant-and = "warn"
manual-let-else = "warn"
manual-midpoint = "warn"
manual-string-new = "warn"
many-single-char-names = "warn"
map-err-ignore = "warn"
map-unwrap-or = "warn"
map-with-unused-argument-over-ranges = "warn"
match-bool = "warn"
match-same-arms = "warn"
match-wild-err-arm = "warn"
match-wildcard-for-single-variants = "warn"
maybe-infinite-iter = "warn"
mem-forget = "warn"
min-ident-chars = "allow"
mismatching-type-param-order = "warn"
missing-assert-message = "warn"
missing-asserts-for-indexing = "warn"
missing-const-for-fn = "warn"
missing-docs-in-private-items = "warn"
missing-errors-doc = "warn"
missing-fields-in-debug = "warn"
missing-inline-in-public-items = "warn"
missing-panics-doc = "warn"
missing-trait-methods = "warn"
mixed-read-write-in-expression = "warn"
mod-module-files = "allow"
module-name-repetitions = "warn"
modulo-arithmetic = "warn"
multiple-crate-versions = "allow"
multiple-inherent-impl = "warn"
multiple-unsafe-ops-per-block = "warn"
must-use-candidate = "warn"
mut-mut = "warn"
mutex-atomic = "warn"
mutex-integer = "warn"
naive-bytecount = "warn"
needless-bitwise-bool = "warn"
needless-collect = "warn"
needless-continue = "warn"
needless-for-each = "warn"
needless-pass-by-ref-mut = "warn"
needless-pass-by-value = "warn"
needless-raw-string-hashes = "warn"
needless-raw-strings = "warn"
needless-type-cast = "warn"
no-effect-underscore-binding = "warn"
no-mangle-with-rust-abi = "warn"
non-ascii-literal = "allow"
non-send-fields-in-send-ty = "warn"
non-std-lazy-statics = "warn"
non-zero-suggestions = "warn"
nonminimal-bool = "warn"
nonstandard-macro-braces = "warn"
option-as-ref-cloned = "warn"
option-if-let-else = "warn"
option-option = "warn"
or-fun-call = "warn"
overly-complex-bool-expr = "allow"
panic = "warn"
panic-in-result-fn = "warn"
partial-pub-fields = "warn"
path-buf-push-overwrite = "warn"
pathbuf-init-then-push = "warn"
pattern-type-mismatch = "allow"
pointer-format = "warn"
precedence-bits = "warn"
print-stderr = "warn"
print-stdout = "warn"
ptr-as-ptr = "warn"
ptr-cast-constness = "warn"
ptr-offset-by-literal = "warn"
pub-underscore-fields = "warn"
pub-use = "allow"
pub-with-shorthand = "allow"
pub-without-shorthand = "warn"
question-mark-used = "allow"
range-minus-one = "warn"
range-plus-one = "warn"
rc-buffer = "warn"
rc-mutex = "warn"
read-zero-byte-vec = "warn"
redundant-clone = "warn"
redundant-closure-for-method-calls = "warn"
redundant-else = "warn"
redundant-pub-crate = "allow"
redundant-test-prefix = "warn"
redundant-type-annotations = "warn"
ref-as-ptr = "warn"
ref-binding-to-reference = "warn"
ref-option = "warn"
ref-option-ref = "warn"
ref-patterns = "warn"
renamed-function-params = "warn"
rest-pat-in-fully-bound-structs = "warn"
return-and-then = "warn"
return-self-not-must-use = "warn"
same-functions-in-if-condition = "warn"
same-length-and-capacity = "warn"
same-name-method = "warn"
search-is-some = "warn"
self-named-module-files = "warn"
self-only-used-in-recursion = "warn"
semicolon-if-nothing-returned = "warn"
semicolon-inside-block = "warn"
semicolon-outside-block = "allow"
separated-literal-suffix = "allow"
set-contains-or-insert = "warn"
shadow-reuse = "allow"
shadow-same = "allow"
shadow-unrelated = "allow"
should-panic-without-expect = "warn"
significant-drop-in-scrutinee = "warn"
significant-drop-tightening = "warn"
similar-names = "allow"
single-call-fn = "allow"
single-char-lifetime-names = "allow"
single-char-pattern = "allow"
single-match-else = "warn"
single-option-map = "warn"
stable-sort-primitive = "warn"
std-instead-of-alloc = "warn"
std-instead-of-core = "warn"
str-split-at-newline = "warn"
str-to-string = "warn"
string-add = "warn"
string-add-assign = "warn"
string-lit-as-bytes = "warn"
string-lit-chars-any = "warn"
string-slice = "warn"
struct-excessive-bools = "allow"
struct-field-names = "warn"
suboptimal-flops = "warn"
suspicious-operation-groupings = "warn"
suspicious-xor-used-as-pow = "warn"
tests-outside-test-module = "warn"
todo = "warn"
too-long-first-doc-paragraph = "allow"
too-many-lines = "allow"
trailing-empty-array = "warn"
trait-duplication-in-bounds = "warn"
transmute-ptr-to-ptr = "warn"
transmute-undefined-repr = "warn"
trivial-regex = "warn"
trivially-copy-pass-by-ref = "warn"
try-err = "warn"
tuple-array-conversions = "warn"
type-repetition-in-bounds = "warn"
unchecked-time-subtraction = "deny"
undocumented-unsafe-blocks = "deny"
unicode-not-nfc = "warn"
unimplemented = "warn"
uninhabited-references = "warn"
uninlined-format-args = "warn"
unnecessary-box-returns = "warn"
unnecessary-debug-formatting = "warn"
unnecessary-join = "warn"
unnecessary-literal-bound = "warn"
unnecessary-safety-comment = "warn"
unnecessary-safety-doc = "warn"
unnecessary-self-imports = "warn"
unnecessary-semicolon = "warn"
unnecessary-struct-initialization = "warn"
unnecessary-trailing-comma = "warn"
unnecessary-wraps = "warn"
unneeded-field-pattern = "warn"
unnested-or-patterns = "warn"
unreachable = "warn"
unreadable-literal = "warn"
unsafe-derive-deserialize = "warn"
unseparated-literal-suffix = "warn"
unused-async = "warn"
unused-async-trait-impl = "warn"
unused-peekable = "warn"
unused-result-ok = "warn"
unused-rounding = "warn"
unused-self = "warn"
unused-trait-names = "warn"
unwrap-in-result = "warn"
unwrap-used = "warn"
use-debug = "warn"
use-self = "warn"
used-underscore-binding = "warn"
used-underscore-items = "warn"
useless-let-if-seq = "warn"
verbose-bit-mask = "warn"
verbose-file-reads = "warn"
volatile-composites = "warn"
while-float = "warn"
wildcard-enum-match-arm = "warn"
wildcard-imports = "warn"
with-capacity-zero = "warn"
zero-sized-map-values = "warn"
[workspace.lints.rust]
deprecated-safe = "forbid"
future-incompatible = "deny"
keyword-idents = "deny"
let-underscore = "warn"
nonstandard-style = "warn"
refining-impl-trait = "warn"
rust-2018-compatibility = "deny"
rust-2018-idioms = "deny"
rust-2021-compatibility = "deny"
rust-2024-compatibility = "deny"
unknown-or-malformed-diagnostic-attributes = "deny"
unused = { level = "warn", priority = -1 }
ambiguous-glob-reexports = "warn"
ambiguous-negative-literals = "deny"
ambiguous-wide-pointer-comparisons = "warn"
arithmetic-overflow = "deny"
asm-sub-register = "warn"
async-fn-in-trait = "warn"
bad-asm-style = "warn"
binary-asm-labels = "deny"
bindings-with-variant-name = "deny"
break-with-label-and-loop = "warn"
c-void-returns = "warn"
clashing-extern-declarations = "warn"
closure-returning-async-block = "warn"
confusable-idents = "warn"
const-item-interior-mutations = "warn"
const-item-mutation = "warn"
dangerous-implicit-autorefs = "deny"
dangling-pointers-from-locals = "warn"
dangling-pointers-from-temporaries = "warn"
dead-code-pub-in-binary = "warn"
deprecated = "warn"
deprecated-in-future = "allow"
deprecated-where-clause-location = "warn"
deref-into-dyn-supertrait = "warn"
deref-nullptr = "deny"
double-negations = "warn"
drop-bounds = "warn"
dropping-copy-types = "warn"
dropping-references = "warn"
duplicate-features = "deny"
duplicate-macro-attributes = "warn"
dyn-drop = "warn"
enum-intrinsics-non-enums = "deny"
explicit-builtin-cfgs-in-flags = "deny"
exported-private-dependencies = "warn"
ffi-unwind-calls = "warn"
for-loops-over-fallibles = "warn"
forgetting-copy-types = "warn"
forgetting-references = "warn"
function-casts-as-integer = "warn"
function-item-references = "warn"
hidden-glob-reexports = "warn"
impl-trait-redundant-captures = "warn"
improper-ctypes = "warn"
improper-ctypes-definitions = "warn"
improper-gpu-kernel-arg = "warn"
incomplete-features = "warn"
incomplete-include = "deny"
ineffective-unstable-trait-impl = "deny"
inline-no-sanitize = "warn"
integer-to-ptr-transmutes = "warn"
internal-features = "warn"
invalid-atomic-ordering = "deny"
invalid-doc-attributes = "warn"
invalid-from-utf8 = "warn"
invalid-from-utf8-unchecked = "deny"
invalid-nan-comparisons = "warn"
invalid-null-arguments = "deny"
invalid-reference-casting = "deny"
invalid-runtime-symbol-definitions = "deny"
invalid-value = "warn"
irrefutable-let-patterns = "warn"
large-assignments = "warn"
linker-info = "allow"
linker-messages = "warn"
long-running-const-eval = "deny"
macro-use-extern-crate = "warn"
meta-variable-misuse = "warn"
mismatched-lifetime-syntaxes = "warn"
missing-abi = "warn"
missing-copy-implementations = "warn"
missing-debug-implementations = "warn"
missing-docs = "warn"
missing-gpu-kernel-export-name = "warn"
mixed-script-confusables = "warn"
mutable-transmutes = "deny"
named-arguments-used-positionally = "warn"
named-asm-labels = "deny"
no-mangle-const-items = "deny"
no-mangle-generic-items = "warn"
non-ascii-idents = "deny"
non-contiguous-range-endpoints = "warn"
non-local-definitions = "warn"
non-shorthand-field-patterns = "warn"
noop-method-call = "warn"
opaque-hidden-inferred-bound = "warn"
overflowing-literals = "deny"
overlapping-range-endpoints = "warn"
private-bounds = "warn"
private-interfaces = "warn"
ptr-to-integer-transmute-in-consts = "warn"
redundant-imports = "warn"
redundant-lifetimes = "warn"
renamed-and-removed-lints = "warn"
rtsan-nonblocking-async = "warn"
single-use-lifetimes = "warn"
special-module-name = "warn"
stable-features = "warn"
suspicious-double-ref-op = "warn"
suspicious-runtime-symbol-definitions = "warn"
text-direction-codepoint-in-comment = "deny"
text-direction-codepoint-in-literal = "deny"
trivial-bounds = "warn"
trivial-casts = "warn"
trivial-numeric-casts = "warn"
type-alias-bounds = "warn"
uncommon-codepoints = "warn"
unconditional-panic = "deny"
unconditional-recursion = "warn"
undropped-manually-drops = "deny"
unexpected-cfgs = "warn"
unfulfilled-lint-expectations = "warn"
ungated-async-fn-track-caller = "warn"
unit-bindings = "warn"
unknown-crate-types = "deny"
unknown-lints = "warn"
unnameable-test-items = "warn"
unnameable-types = "warn"
unnecessary-transmutes = "warn"
unpredictable-function-pointer-comparisons = "warn"
unreachable-cfg-select-predicates = "warn"
unreachable-pub = "warn"
unsafe-code = "forbid"
unstable-features = "forbid"
unused-associated-type-bounds = "warn"
unused-comparisons = "warn"
unused-crate-dependencies = "warn"
unused-import-braces = "warn"
unused-lifetimes = "warn"
unused-qualifications = "warn"
unused-results = "warn"
useless-deprecated = "deny"
useless-ptr-null-checks = "warn"
uses-power-alignment = "warn"
variant-size-differences = "warn"
warnings = "warn"
while-true = "warn"
# unstable lints
# default-overrides-default-fields = "deny"
# deprecated-llvm-intrinsic = "warn"
# implicit-provenance-casts = "deny"
# multiple-supertrait-upcastable = "warn"
# must-not-suspend = "warn"
# non-exhaustive-omitted-patterns = "warn"
# resolving-to-items-shadowing-supertrait-items = "warn"
# shadowing-supertrait-items = "warn"
# tail-call-track-caller = "warn"
# test-unstable-lint = "deny"
# unqualified-local-imports = "deny"
[workspace.lints.rustdoc]
bare-urls = "warn"
broken-intra-doc-links = "warn"
invalid-codeblock-attributes = "warn"
invalid-html-tags = "warn"
invalid-rust-codeblocks = "warn"
missing-crate-level-docs = "warn"
private-doc-tests = "warn"
private-intra-doc-links = "warn"
redundant-explicit-links = "warn"
unescaped-backticks = "warn"
# unstable lints
# missing-doc-code-examples = "warn"
[profile.release]
opt-level = 3
strip = "debuginfo"
overflow-checks = true
lto = "fat"
codegen-units = 1
+11 -12
View File
@@ -1,17 +1,16 @@
# Seamantic
[![Crates Version](https://img.shields.io/crates/v/seamantic.svg)](https://crates.io/crates/seamantic)
## Development
A library to enhance SeaORM
Run the full validation suite before committing:
## Various builds
```sh
just all
just install
```
- build: `cargo hack --feature-powerset build`
- clippy: `cargo hack --feature-powerset clippy -- -D warnings`
- test: `cargo hack --feature-powerset test`
- test old: `cargo +1.85 hack --feature-powerset test`
- example: `cargo run --example=migrations --features=sqlite`
- fmt: `cargo fmt --check`
- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features`
- semver: `cargo semver-checks --all-features`
- publish: `cargo publish --dry-run -p seamantic`
##### Publish
- `cargo semver-checks --all-features`
- `cargo publish --dry-run -p <crate>`
- `cargo publish --dry-run --workspace`
@@ -1,41 +1,41 @@
[package]
name = "seamantic"
version = "0.0.10"
categories = []
version = "1.0.1"
description = "A library to enhance SeaORM"
repository = "https://github.com/QuantumShade/seamantic"
repository = "https://git.skrundz.dev/quantumshade/seamantic"
keywords = ["SeaORM"]
categories = ["database"]
include = ["/src"]
publish = true
authors.workspace = true
edition.workspace = true
license-file.workspace = true
rust-version.workspace = true
license-file.workspace = true
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[[example]]
name = "migrations"
path = "examples/migrations/main.rs"
required-features = ["sqlite"]
[features]
default = []
serde = ["dep:serde"]
sqlite = ["sea-orm-migration/sqlx-sqlite"]
[dependencies]
sea-orm = { workspace = true }
sea-orm-migration = { workspace = true }
serde = { workspace = true, features = ["derive", "std"], optional = true }
[dev-dependencies]
sea-orm = { workspace = true, features = ["entity-registry", "schema-sync"] }
sea-orm-migration = { workspace = true, features = ["runtime-tokio-rustls"] }
serde_test = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros"] }
tokio = { workspace = true, features = ["macros", "rt"] }
[features]
default = ["sqlite"]
serde = ["dep:serde"]
sqlite = ["sea-orm-migration/sqlx-sqlite"]
[lints]
workspace = true
@@ -14,6 +14,10 @@ pub(super) struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
@@ -28,6 +32,10 @@ impl MigrationTrait for Migration {
Ok(())
}
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Objects::Table).to_owned())
@@ -15,3 +15,6 @@ async fn main() {
let database = Database::connect(options).await.expect("Database::connect");
Migrator::up(&database, None).await.expect("Migrator::up");
}
// dev-dependencies cannot be optional and `serde_test` is not used
use {serde_test as _, tokio as _};
@@ -1,4 +1,4 @@
//! A library for enhacing SeaORM
//! A library for enhacing `SeaORM`.
#![cfg_attr(docsrs, feature(doc_cfg))]
@@ -31,6 +31,7 @@ macro_rules! migrations {
/// Auto-generated migration manager
#[automatically_derived]
#[non_exhaustive]
pub struct Migrator;
#[automatically_derived]
@@ -46,3 +47,8 @@ macro_rules! migrations {
}
};
}
// dev-dependencies cannot be optional and `tokio` and `serde_test` are only used
// when the `sqlite` feature is active
#[cfg(test)]
use {serde_test as _, tokio as _};
@@ -1,37 +1,45 @@
//! [Duration] utilities
//! [Duration] utilities.
use core::time::Duration;
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
// "u64 unsupported by sqlx-sqlite", so use i64 as the bit representation
/// The internal representation that `SeaORM` uses for durations.
///
/// "u64 unsupported by sqlx-sqlite", so use i64 as the bit representation.
type SeaOrmRepr = i64;
/// The internal represenation that [Duration] uses.
type DurationRepr = u64;
/// Wrapper around [Duration] to store a number of seconds
/// Wrapper around [Duration] to store a number of seconds.
///
/// ### Warning:
/// Sub-second precision will be lost
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
/// # Warning:
/// Sub-second precision will be lost.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct Seconds(pub Duration);
impl From<Duration> for Seconds {
#[inline]
fn from(value: Duration) -> Self {
Self(Duration::from_secs(value.as_secs()))
}
}
impl From<Seconds> for Duration {
#[inline]
fn from(value: Seconds) -> Self {
value.0
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ValueType for Seconds {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
@@ -39,26 +47,32 @@ impl ValueType for Seconds {
.map(Self)
}
#[inline]
fn type_name() -> String {
core::any::type_name::<Self>().to_string()
core::any::type_name::<Self>().to_owned()
}
#[inline]
fn array_type() -> ArrayType {
SeaOrmRepr::array_type()
}
#[inline]
fn column_type() -> ColumnType {
SeaOrmRepr::column_type()
}
}
impl From<Seconds> for Value {
#[inline]
fn from(value: Seconds) -> Self {
value.0.as_secs().into()
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl TryGetable for Seconds {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
@@ -68,6 +82,7 @@ impl TryGetable for Seconds {
}
impl TryFromU64 for Seconds {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
@@ -77,6 +92,7 @@ impl TryFromU64 for Seconds {
}
impl Nullable for Seconds {
#[inline]
fn null() -> Value {
SeaOrmRepr::null()
}
@@ -91,10 +107,9 @@ mod tests {
use super::Seconds;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "seconds")]
pub struct Model {
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
@@ -102,9 +117,9 @@ mod tests {
nullable: Option<Seconds>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
enum Relation {}
}
@@ -1,4 +1,4 @@
//! Typed IDs for use as primary keys
//! Typed IDs for use as primary keys.
use core::cmp::Ordering;
use core::fmt;
@@ -8,55 +8,70 @@ use core::marker::PhantomData;
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
/// The internal representation used by the database
/// The internal representation used by the database.
pub type SeaOrmRepr = i64;
/// An opaque type representing a row ID
/// An opaque type representing a row ID.
///
/// IDs should be tagged with `#[sea_orm(primary_key, auto_increment = false)]`.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct Id<T> {
/// The ID.
id: SeaOrmRepr,
/// `PhantomData` for `T`.
#[cfg_attr(feature = "serde", serde(skip_serializing, default))]
_phantom: PhantomData<T>,
}
// Manual implementation since `T: Clone` is not required
// Manual implementation since `T: Clone` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Clone for Id<T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
// Manual implementation since `T: Copy` is not required
// Manual implementation since `T: Copy` is not required.
impl<T> Copy for Id<T> {}
// Manual implementation since `T: PartialEq` is not required
// Manual implementation since `T: PartialEq` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> PartialEq for Id<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id == other.id
self.id.eq(&other.id)
}
}
// Manual implementation since `T: Eq` is not required
// Manual implementation since `T: Eq` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Eq for Id<T> {}
// Manual implementation since `T: PartialOrd` is not required
// Manual implementation since `T: PartialOrd` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> PartialOrd for Id<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
// Manual implementation since `T: Ord` is not required
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Ord for Id<T> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
// Manual implementation since `T: Hash` is not required
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Hash for Id<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
@@ -64,7 +79,9 @@ impl<T> Hash for Id<T> {
impl<T> Id<T> {
/// Allows the conversion from a raw value to [Id], though the use is discouraged.
pub fn from_raw(raw: SeaOrmRepr) -> Self {
#[inline]
#[must_use]
pub const fn from_raw(raw: SeaOrmRepr) -> Self {
Self {
id: raw,
_phantom: PhantomData,
@@ -72,13 +89,16 @@ impl<T> Id<T> {
}
/// Allows extracting the raw value, though the use is discouraged.
pub fn into_raw(self) -> SeaOrmRepr {
#[inline]
#[must_use]
pub const fn into_raw(self) -> SeaOrmRepr {
self.id
}
}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Id")
.field("T", &core::any::type_name::<T>())
.field("id", &self.id)
@@ -86,7 +106,9 @@ impl<T> fmt::Debug for Id<T> {
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> ValueType for Id<T> {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v).map(|id| Self {
id,
@@ -94,26 +116,32 @@ impl<T> ValueType for Id<T> {
})
}
#[inline]
fn type_name() -> String {
format!("Id<{}>", &core::any::type_name::<T>())
format!("Id<{}>", core::any::type_name::<T>())
}
#[inline]
fn array_type() -> ArrayType {
SeaOrmRepr::array_type()
}
#[inline]
fn column_type() -> ColumnType {
SeaOrmRepr::column_type()
}
}
impl<T> From<Id<T>> for Value {
#[inline]
fn from(value: Id<T>) -> Self {
value.id.into()
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> TryGetable for Id<T> {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index).map(|id| Self {
id,
@@ -123,6 +151,7 @@ impl<T> TryGetable for Id<T> {
}
impl<T> TryFromU64 for Id<T> {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n).map(|id| Self {
id,
@@ -132,6 +161,7 @@ impl<T> TryFromU64 for Id<T> {
}
impl<T> Nullable for Id<T> {
#[inline]
fn null() -> Value {
SeaOrmRepr::null()
}
@@ -146,24 +176,32 @@ mod tests {
use super::Id;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ids")]
pub struct Model {
#[expect(clippy::use_self, reason = "derive macros cause Self to be invalid")]
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>,
nullable: Option<Id<Model>>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
enum Relation {}
#[test]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
fn round_trip() {
let raw = 1234;
let id = Id::<()>::from_raw(raw);
assert_eq!(raw, id.into_raw());
}
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
fn ser_de() {
let id: Id<()> = Id::from_raw(1234);
serde_test::assert_tokens(&id, &[serde_test::Token::I64(1234)]);
}
+6
View File
@@ -0,0 +1,6 @@
//! `SeaORM` column types for enforcing data consistency.
pub mod duration;
pub mod id;
pub mod net;
pub mod path;
@@ -1,74 +1,89 @@
//! [IpAddr], [Ipv4Addr], and [Ipv6Addr] utilities
//! [`IpAddr`], [`Ipv4Addr`], and [`Ipv6Addr`] utilities.
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
/// The internal representation used by the database.
type SeaOrmRepr = String;
/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// Wrapper around [`IpAddr`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct IpAddress(pub IpAddr);
/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// Wrapper around [`Ipv4Addr`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct Ipv4Address(pub Ipv4Addr);
/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// Wrapper around [`Ipv6Addr`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct Ipv6Address(pub Ipv6Addr);
/// Implements `SeaORM` traits for `IP` types.
macro_rules! impl_addr {
($t:ty, $inner:ty, $repr:ty) => {
impl From<$inner> for $t {
#[inline]
fn from(value: $inner) -> Self {
Self(value)
}
}
impl From<$t> for $inner {
#[inline]
fn from(value: $t) -> Self {
value.0
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ValueType for $t {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v)
.and_then(|s| s.parse().map_err(|_| ValueTypeErr))
.map(Self)
}
#[inline]
fn type_name() -> String {
core::any::type_name::<Self>().to_string()
}
#[inline]
fn array_type() -> ArrayType {
SeaOrmRepr::array_type()
}
#[inline]
fn column_type() -> ColumnType {
SeaOrmRepr::column_type()
}
}
impl From<$t> for Value {
#[inline]
fn from(value: $t) -> Self {
value.0.to_string().into()
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl TryGetable for $t {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index)
.and_then(|s| s.parse().map_err(|_| TryGetError::Null(Self::type_name())))
@@ -77,6 +92,7 @@ macro_rules! impl_addr {
}
impl TryFromU64 for $t {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n)
.and_then(|s| {
@@ -88,6 +104,7 @@ macro_rules! impl_addr {
}
impl Nullable for $t {
#[inline]
fn null() -> Value {
SeaOrmRepr::null()
}
@@ -109,10 +126,9 @@ mod tests {
use super::super::IpAddress;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipaddress")]
pub struct Model {
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
@@ -120,11 +136,11 @@ mod tests {
nullable: Option<IpAddress>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
enum Relation {}
}
mod ipv4address {
use sea_orm::{
@@ -134,10 +150,9 @@ mod tests {
use super::super::Ipv4Address;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipv4address")]
pub struct Model {
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
@@ -145,11 +160,11 @@ mod tests {
nullable: Option<Ipv4Address>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
enum Relation {}
}
mod ipv6address {
use sea_orm::{
@@ -159,10 +174,9 @@ mod tests {
use super::super::Ipv6Address;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipv6address")]
pub struct Model {
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
@@ -170,10 +184,10 @@ mod tests {
nullable: Option<Ipv6Address>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
enum Relation {}
}
}
@@ -1,4 +1,4 @@
//! [Path] and [PathBuf] utilities
//! [Path] and [`PathBuf`] utilities.
use std::ffi::OsString;
use std::path::{Path, PathBuf};
@@ -11,34 +11,42 @@ compile_error!("PathBytes is not supported on Windows");
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
/// The internal representation used by the database.
type SeaOrmRepr = Vec<u8>;
/// Wrapper around [PathBuf] to store paths as bytes in SeaORM
/// Wrapper around [`PathBuf`] to store paths as bytes in `SeaORM`.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
#[expect(clippy::module_name_repetitions, reason = "Bytes wouldn't make sense")]
pub struct PathBytes(pub PathBuf);
impl From<PathBuf> for PathBytes {
#[inline]
fn from(value: PathBuf) -> Self {
Self(value)
}
}
impl From<PathBytes> for PathBuf {
#[inline]
fn from(value: PathBytes) -> Self {
value.0
}
}
impl AsRef<Path> for PathBytes {
#[inline]
fn as_ref(&self) -> &Path {
&self.0
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ValueType for PathBytes {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v)
.map(OsString::from_vec)
@@ -46,26 +54,32 @@ impl ValueType for PathBytes {
.map(Self)
}
#[inline]
fn type_name() -> String {
core::any::type_name::<Self>().to_string()
core::any::type_name::<Self>().to_owned()
}
#[inline]
fn array_type() -> ArrayType {
SeaOrmRepr::array_type()
}
#[inline]
fn column_type() -> ColumnType {
SeaOrmRepr::column_type()
}
}
impl From<PathBytes> for Value {
#[inline]
fn from(value: PathBytes) -> Self {
value.0.into_os_string().into_vec().into()
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl TryGetable for PathBytes {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index)
.map(OsString::from_vec)
@@ -75,6 +89,7 @@ impl TryGetable for PathBytes {
}
impl TryFromU64 for PathBytes {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n)
.map(OsString::from_vec)
@@ -84,6 +99,7 @@ impl TryFromU64 for PathBytes {
}
impl Nullable for PathBytes {
#[inline]
fn null() -> Value {
SeaOrmRepr::null()
}
@@ -98,10 +114,9 @@ mod tests {
use super::PathBytes;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "paths")]
pub struct Model {
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
@@ -109,9 +124,9 @@ mod tests {
nullable: Option<PathBytes>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
enum Relation {}
}
@@ -1,4 +1,4 @@
//! Helpers for working with SeaORM
//! Helpers for working with `SeaORM`.
mod upsert;
pub use upsert::UpsertTrait;
+66
View File
@@ -0,0 +1,66 @@
//! Provides [`UpsertTrait`] for performing an upsert into a database.
use sea_orm::sea_query::{IntoColumnRef as _, OnConflict};
use sea_orm::{ActiveModelTrait, EntityTrait, Insert, Iterable as _};
/// This trait add a method on [Insert] to allow for upsert behavior.
pub trait UpsertTrait: private::Sealed {
/// Set ON CONFLICT on primary key to update all other columns.
#[must_use]
fn on_conflict_upsert(self) -> Self;
}
/// Returns an [Iterator] over the `PrimaryKey` columns of an [`ActiveModelTrait`].
fn primary_key_iter<A: ActiveModelTrait>()
-> impl Iterator<Item = <A::Entity as EntityTrait>::PrimaryKey> {
<A::Entity as EntityTrait>::PrimaryKey::iter()
}
/// Returns an [Iterator] over the [`EntityTrait`] columns of an [`ActiveModelTrait`].
fn column_iter<A: ActiveModelTrait>() -> impl Iterator<Item = <A::Entity as EntityTrait>::Column> {
<<A as ActiveModelTrait>::Entity as EntityTrait>::Column::iter()
}
impl<A: ActiveModelTrait> private::Sealed for Insert<A> {}
impl<A: ActiveModelTrait> UpsertTrait for Insert<A> {
#[inline]
fn on_conflict_upsert(self) -> Self {
self.on_conflict(
OnConflict::columns(primary_key_iter::<A>())
.update_columns(column_iter::<A>().filter(|col| {
!primary_key_iter::<A>().any(|pk| pk.into_column_ref() == col.into_column_ref())
}))
.to_owned(),
)
}
}
/// Private module.
mod private {
/// Sealed trait to prevent implementations of `UpsertTrait`.
#[expect(unnameable_types, reason = "sealed trait")]
pub trait Sealed {}
}
#[cfg(test)]
mod tests {
use super::{UpsertTrait, private::Sealed};
#[test]
fn seal() {
struct S;
impl Sealed for S {}
impl UpsertTrait for S {
fn on_conflict_upsert(self) -> Self {
self
}
}
fn is_sealed<T: Sealed>() {}
fn is_upsert<T: UpsertTrait>() {}
is_sealed::<S>();
is_upsert::<S>();
}
}
@@ -1,4 +1,4 @@
//! Helpers for defining SeaORM schemas
//! Helpers for defining `SeaORM` schemas.
#[cfg(feature = "sqlite")]
mod sqlite;
@@ -1,16 +1,18 @@
//! Helper functions for declaring `SQLite` schemas.
use sea_orm_migration::schema::{integer_null, string};
use sea_orm_migration::sea_query::{ColumnDef, IntoIden};
/// Sets the column to be an alias for SQLite's rowid.
///
/// Required conditions:
/// - This column must *not* be auto_increment
/// - There cannot be other primary_key columns in the table
/// - This column must *not* be `auto_increment`
/// - There cannot be other `primary_key` columns in the table
///
/// When using `DeriveEntityModel`, the type must be `i64` (or equivalent)
/// and should be tagged with:
///
/// `#[sea_orm(primary_key, auto_increment = false)]`
/// > `#[sea_orm(primary_key, auto_increment = false)]`.
///
/// When using the new entity format:
///
@@ -21,15 +23,16 @@ use sea_orm_migration::sea_query::{ColumnDef, IntoIden};
/// #[derive(Debug, Clone, DeriveEntityModel)]
/// #[sea_orm(table_name = "rowid_test")]
/// pub struct Model {
/// #[sea_orm(column_type = "Integer", primary_key, nullable, auto_increment = false)]
/// #[sea_orm(primary_key, auto_increment = false)]
/// id: Id<Model>,
/// }
/// impl ActiveModelBehavior for ActiveModel {}
#[inline]
pub fn sqlite_rowid_alias<T: IntoIden>(name: T) -> ColumnDef {
integer_null(name).primary_key().take()
}
/// Set the column to be a case insensitive string
/// Set the column to be a case insensitive string.
///
/// When using the new entity format:
///
@@ -46,6 +49,7 @@ pub fn sqlite_rowid_alias<T: IntoIden>(name: T) -> ColumnDef {
/// nocase: String,
/// }
/// impl ActiveModelBehavior for ActiveModel {}
#[inline]
pub fn sqlite_case_insensitive_string<T: IntoIden>(name: T) -> ColumnDef {
string(name).extra("COLLATE NOCASE").take()
}
@@ -54,7 +58,12 @@ pub fn sqlite_case_insensitive_string<T: IntoIden>(name: T) -> ColumnDef {
mod entity_tests {
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
pub async fn new_initialized_memory_db() -> DatabaseConnection {
/// Create and initialize a new in-memory SQLite database.
///
/// # Panics
/// If the database driver fails to connect, or if the schema registry fails
/// to sync the database schema.
async fn new_initialized_memory_db() -> DatabaseConnection {
let options = ConnectOptions::new("sqlite::memory:");
let db = Database::connect(options)
@@ -67,6 +76,22 @@ mod entity_tests {
db
}
#[expect(
clippy::unused_async_trait_impl,
reason = "sea_orm::model generates unpolled futures"
)]
#[expect(
clippy::same_name_method,
reason = "sea_orm::model generates the same method names"
)]
#[expect(
clippy::allow_attributes,
reason = "under different conditions sea_orm::model generates different code"
)]
#[allow(
clippy::future_not_send,
reason = "sea_orm::model generates un-send futures"
)]
mod rowid_test {
use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::entity::prelude::*;
@@ -76,14 +101,18 @@ mod entity_tests {
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "rowid_test")]
pub struct Model {
#[sea_orm(column_type = "Integer", primary_key, nullable, auto_increment = false)]
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[cfg(not(miri))]
#[tokio::test]
async fn test_sqlite_rowid_alias() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "numbers are not ambiguous")]
async fn sqlite_rowid_alias() {
let db = super::new_initialized_memory_db().await;
// Starts at 1 and increments
@@ -98,7 +127,7 @@ mod entity_tests {
let model = ActiveModel {
id: Set(Id::from_raw(i)),
};
model.delete(&db).await.expect("delete");
_ = model.delete(&db).await.expect("delete");
let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
@@ -156,6 +185,22 @@ mod entity_tests {
}
}
#[expect(
clippy::unused_async_trait_impl,
reason = "sea_orm::model generates unpolled futures"
)]
#[expect(
clippy::same_name_method,
reason = "sea_orm::model generates the same method names"
)]
#[expect(
clippy::allow_attributes,
reason = "under different conditions sea_orm::model generates different code"
)]
#[allow(
clippy::future_not_send,
reason = "sea_orm::model generates un-send futures"
)]
mod nocase_test {
use sea_orm::ActiveValue::Set;
use sea_orm::entity::prelude::*;
@@ -163,17 +208,20 @@ mod entity_tests {
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "nocase_test")]
pub struct Model {
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: i64,
case: String,
#[sea_orm(extra = "COLLATE NOCASE")]
nocase: String,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
#[cfg(not(miri))]
#[tokio::test]
async fn test_sqlite_case_insensitive_string() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn sqlite_case_insensitive_string() {
let db = super::new_initialized_memory_db().await;
let i = 50;
@@ -185,7 +233,7 @@ mod entity_tests {
case: Set("abcd".to_owned()),
nocase: Set("abcd".to_owned()),
};
model.insert(&db).await.expect("insert");
drop(model.insert(&db).await.expect("insert"));
}
// Query by uppercase string
@@ -216,7 +264,7 @@ mod entity_tests {
mod tests {
use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::entity::prelude::*;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use sea_orm::{ConnectOptions, Database};
use sea_orm_migration::async_trait::async_trait;
use sea_orm_migration::prelude::*;
@@ -224,14 +272,21 @@ mod tests {
use super::{sqlite_case_insensitive_string, sqlite_rowid_alias};
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn new_memory_db() -> DatabaseConnection {
let options = ConnectOptions::new("sqlite::memory:");
Database::connect(options).await.expect("Database::connect")
}
#[cfg(not(miri))]
#[tokio::test]
#[expect(unused_qualifications, reason = "derive macro qualifies everything")]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "numbers are not ambiguous")]
#[expect(clippy::redundant_test_prefix, reason = "name collision")]
async fn test_sqlite_rowid_alias() {
struct Migrator;
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(Migration)]
@@ -246,7 +301,12 @@ mod tests {
#[derive(DeriveMigrationName)]
struct Migration;
#[async_trait]
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigrationTrait for Migration {
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
@@ -263,17 +323,21 @@ mod tests {
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "test_table")]
pub struct Model {
#[expect(clippy::use_self, reason = "derive macros cause Self to be invalid")]
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>,
}
#[derive(Debug, EnumIter)]
pub enum Relation {}
enum Relation {}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl RelationTrait for Relation {
#[expect(clippy::panic, reason = "no relation")]
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
let db = new_memory_db().await;
@@ -291,7 +355,7 @@ mod tests {
let model = ActiveModel {
id: Set(Id::from_raw(i)),
};
model.delete(&db).await.expect("delete");
_ = model.delete(&db).await.expect("delete");
let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
@@ -348,9 +412,14 @@ mod tests {
}
}
#[cfg(not(miri))]
#[tokio::test]
#[expect(unused_qualifications, reason = "derive macro qualifies everything")]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::redundant_test_prefix, reason = "name collision")]
async fn test_sqlite_case_insensitive_string() {
struct Migrator;
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(Migration)]
@@ -366,7 +435,12 @@ mod tests {
#[derive(DeriveMigrationName)]
struct Migration;
#[async_trait]
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigrationTrait for Migration {
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
@@ -384,18 +458,21 @@ mod tests {
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "test_table")]
pub struct Model {
struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: i64,
ci_str: String,
}
#[derive(Debug, EnumIter)]
pub enum Relation {}
enum Relation {}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl RelationTrait for Relation {
#[expect(clippy::panic, reason = "no relation")]
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
let db = new_memory_db().await;
@@ -409,7 +486,7 @@ mod tests {
id: Set(i),
ci_str: Set("abcd".to_owned()),
};
model.insert(&db).await.expect("insert");
drop(model.insert(&db).await.expect("insert"));
}
// Query by uppercase string
+43
View File
@@ -0,0 +1,43 @@
set minimum-version := '1.55.0'
set default-list
alias update := update-min
all:
just all-min
just all-max
all-min: update-min build lint test example doc format
all-max: update-max build lint test example doc format
update-min:
cargo +nightly update -Z direct-minimal-versions
update-max:
cargo update
build:
cargo hack --feature-powerset build
lint:
cargo hack --feature-powerset clippy -- -D warnings
cargo hack --feature-powerset clippy --tests -- -D warnings
test:
cargo hack --feature-powerset test
cargo +1.94 hack --feature-powerset test
cargo +nightly hack --feature-powerset miri test
example:
cargo run --example=migrations --features=sqlite
doc *args:
RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo +nightly doc \
--workspace --all-features --document-private-items \
--lib --bins --no-deps --locked \
{{ args }}
format:
cargo fmt --check
install:
cargo install --path crates/lint-policy-cli
+1 -1
View File
@@ -1,2 +1,2 @@
[toolchain]
channel = "stable"
channel = "1.98"
-6
View File
@@ -1,6 +0,0 @@
//! SeaORM column types for enforcing data consistency
pub mod duration;
pub mod id;
pub mod ip;
pub mod path;
-34
View File
@@ -1,34 +0,0 @@
use sea_orm::sea_query::{IntoColumnRef, OnConflict};
use sea_orm::{ActiveModelTrait, EntityTrait, Insert, Iterable};
/// This trait add a method on [Insert] to allow for upsert behavior
pub trait UpsertTrait: private::Sealed {
/// Set ON CONFLICT on primary key to update all other columns
fn on_conflict_upsert(self) -> Self;
}
fn primary_key_iter<A: ActiveModelTrait>()
-> impl Iterator<Item = <A::Entity as EntityTrait>::PrimaryKey> {
<A::Entity as EntityTrait>::PrimaryKey::iter()
}
fn column_iter<A: ActiveModelTrait>() -> impl Iterator<Item = <A::Entity as EntityTrait>::Column> {
<<A as ActiveModelTrait>::Entity as EntityTrait>::Column::iter()
}
impl<A: ActiveModelTrait> UpsertTrait for Insert<A> {
fn on_conflict_upsert(self) -> Self {
self.on_conflict(
OnConflict::columns(primary_key_iter::<A>())
.update_columns(column_iter::<A>().filter(|col| {
!primary_key_iter::<A>().any(|pk| pk.into_column_ref() == col.into_column_ref())
}))
.to_owned(),
)
}
}
mod private {
pub trait Sealed {}
impl<A: ::sea_orm::ActiveModelTrait> Sealed for ::sea_orm::Insert<A> {}
}