Repository: SeaQL/sea-orm Branch: master Commit: 75b325bd0d1e Files: 1524 Total size: 6.3 MB Directory structure: gitextract_ko6xbeae/ ├── .gitattributes ├── .github/ │ ├── .well-known/ │ │ └── funding-manifest-urls │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ ├── config.yml │ │ └── feature-request.md │ └── workflows/ │ ├── release-bot.yml │ └── rust.yml ├── .gitignore ├── .rustfmt.toml ├── .taplo.toml ├── CHANGELOG.md ├── CLAUDE.md ├── COMMUNITY.md ├── CONTRIBUTING.md ├── Cargo.toml ├── DEVELOPMENT.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README-zh.md ├── README.md ├── VERSIONS.md ├── build-tools/ │ ├── back-async.rs │ ├── bump.sh │ ├── clean.sh │ ├── del-rel-dep.sh │ ├── docker-compose.yml │ ├── docker-create.sh │ ├── make-sync.sh │ ├── publish.sh │ ├── readme.sh │ ├── rustclippy.sh │ ├── rustfmt.sh │ └── update-strum-macros.sh ├── changelog/ │ ├── 2.0.0-rc.20.md │ ├── 2.0.0-rc.21.md │ ├── 2.0.0-rc.22.md │ ├── 2.0.0-rc.23.md │ ├── 2.0.0-rc.24.md │ ├── 2.0.0-rc.25.md │ ├── 2.0.0-rc.26.md │ ├── 2.0.0-rc.27.md │ ├── 2.0.0-rc.28.md │ ├── 2.0.0-rc.29.md │ ├── 2.0.0-rc.30.md │ ├── 2.0.0-rc.31.md │ ├── 2.0.0-rc.32.md │ ├── 2.0.0-rc.34.md │ ├── 2.0.0-rc.35.md │ ├── 2.0.0-rc.36.md │ └── 2.0.0-rc.37.md ├── examples/ │ ├── actix_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── lib.rs │ │ │ │ └── service/ │ │ │ │ ├── mod.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ ├── static/ │ │ │ │ └── css/ │ │ │ │ ├── normalize.css │ │ │ │ ├── skeleton.css │ │ │ │ └── style.css │ │ │ ├── templates/ │ │ │ │ ├── edit.html.tera │ │ │ │ ├── error/ │ │ │ │ │ └── 404.html.tera │ │ │ │ ├── index.html.tera │ │ │ │ ├── layout.html.tera │ │ │ │ └── new.html.tera │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── post.rs │ │ │ └── prelude.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_post_table.rs │ │ │ ├── m20220120_000002_seed_posts.rs │ │ │ └── main.rs │ │ └── src/ │ │ └── main.rs │ ├── axum_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── flash.rs │ │ │ │ ├── lib.rs │ │ │ │ └── service/ │ │ │ │ ├── mod.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ ├── static/ │ │ │ │ └── css/ │ │ │ │ ├── normalize.css │ │ │ │ ├── skeleton.css │ │ │ │ └── style.css │ │ │ ├── templates/ │ │ │ │ ├── edit.html.tera │ │ │ │ ├── error/ │ │ │ │ │ └── 404.html.tera │ │ │ │ ├── index.html.tera │ │ │ │ ├── layout.html.tera │ │ │ │ └── new.html.tera │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── post.rs │ │ │ └── prelude.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_post_table.rs │ │ │ ├── m20220120_000002_seed_posts.rs │ │ │ └── main.rs │ │ └── src/ │ │ └── main.rs │ ├── basic/ │ │ ├── Cargo.toml │ │ ├── Readme.md │ │ ├── bakery.sql │ │ └── src/ │ │ ├── entity/ │ │ │ ├── cake.rs │ │ │ ├── cake_filling.rs │ │ │ ├── filling.rs │ │ │ ├── fruit.rs │ │ │ ├── mod.rs │ │ │ └── sea_orm_active_enums.rs │ │ ├── main.rs │ │ ├── mutation.rs │ │ └── query.rs │ ├── graphql_example/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── db.rs │ │ │ │ ├── graphql/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── mutation/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── note.rs │ │ │ │ │ ├── query/ │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ └── note.rs │ │ │ │ │ └── schema.rs │ │ │ │ ├── lib.rs │ │ │ │ └── service/ │ │ │ │ ├── mod.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── note.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_note_table.rs │ │ │ ├── m20220120_000002_seed_notes.rs │ │ │ └── main.rs │ │ └── src/ │ │ └── main.rs │ ├── jsonrpsee_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── lib.rs │ │ │ │ └── service/ │ │ │ │ ├── mod.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── post.rs │ │ │ └── prelude.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_post_table.rs │ │ │ ├── m20220120_000002_seed_posts.rs │ │ │ └── main.rs │ │ └── src/ │ │ └── main.rs │ ├── loco_example/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── .rustfmt.toml │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── config/ │ │ │ └── development.yaml │ │ ├── dockerfile │ │ ├── frontend/ │ │ │ ├── .eslintrc.cjs │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── App.css │ │ │ │ ├── App.jsx │ │ │ │ ├── index.css │ │ │ │ └── main.jsx │ │ │ └── vite.config.js │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20231103_114510_notes.rs │ │ │ └── main.rs │ │ ├── src/ │ │ │ ├── app.rs │ │ │ ├── bin/ │ │ │ │ └── main.rs │ │ │ ├── controllers/ │ │ │ │ ├── mod.rs │ │ │ │ └── notes.rs │ │ │ ├── fixtures/ │ │ │ │ └── notes.yaml │ │ │ ├── lib.rs │ │ │ └── models/ │ │ │ ├── _entities/ │ │ │ │ ├── mod.rs │ │ │ │ ├── notes.rs │ │ │ │ └── prelude.rs │ │ │ ├── mod.rs │ │ │ └── notes.rs │ │ └── tests/ │ │ ├── mod.rs │ │ ├── models/ │ │ │ └── mod.rs │ │ ├── requests/ │ │ │ ├── mod.rs │ │ │ ├── notes.rs │ │ │ └── snapshots/ │ │ │ ├── can_add_note@notes_request.snap │ │ │ ├── can_delete_note@notes_request.snap │ │ │ ├── can_get_note@notes_request.snap │ │ │ └── can_get_notes@notes_request.snap │ │ └── tasks/ │ │ ├── mod.rs │ │ └── seed.rs │ ├── loco_seaography/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .devcontainer/ │ │ │ ├── Dockerfile │ │ │ ├── devcontainer.json │ │ │ └── docker-compose.yml │ │ ├── .github/ │ │ │ └── workflows/ │ │ │ └── ci.yaml │ │ ├── .gitignore │ │ ├── .rustfmt.toml │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── config/ │ │ │ └── development.yaml │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220101_000001_users.rs │ │ │ ├── m20231103_114510_notes.rs │ │ │ ├── m20240520_173001_files.rs │ │ │ └── main.rs │ │ └── src/ │ │ ├── app.rs │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── controllers/ │ │ │ ├── auth.rs │ │ │ ├── files.rs │ │ │ ├── graphql.rs │ │ │ ├── mod.rs │ │ │ ├── notes.rs │ │ │ └── user.rs │ │ ├── fixtures/ │ │ │ ├── notes.yaml │ │ │ └── users.yaml │ │ ├── graphql/ │ │ │ ├── mod.rs │ │ │ └── query_root.rs │ │ ├── initializers/ │ │ │ ├── graphql.rs │ │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── mailers/ │ │ │ ├── auth/ │ │ │ │ ├── forgot/ │ │ │ │ │ ├── html.t │ │ │ │ │ ├── subject.t │ │ │ │ │ └── text.t │ │ │ │ └── welcome/ │ │ │ │ ├── html.t │ │ │ │ ├── subject.t │ │ │ │ └── text.t │ │ │ ├── auth.rs │ │ │ └── mod.rs │ │ ├── models/ │ │ │ ├── _entities/ │ │ │ │ ├── files.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── notes.rs │ │ │ │ ├── prelude.rs │ │ │ │ └── users.rs │ │ │ ├── files.rs │ │ │ ├── mod.rs │ │ │ ├── notes.rs │ │ │ └── users.rs │ │ ├── tasks/ │ │ │ ├── mod.rs │ │ │ └── seed.rs │ │ ├── views/ │ │ │ ├── auth.rs │ │ │ ├── mod.rs │ │ │ └── user.rs │ │ └── workers/ │ │ ├── downloader.rs │ │ └── mod.rs │ ├── loco_starter/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .devcontainer/ │ │ │ ├── Dockerfile │ │ │ ├── devcontainer.json │ │ │ └── docker-compose.yml │ │ ├── .github/ │ │ │ └── workflows/ │ │ │ └── ci.yaml │ │ ├── .gitignore │ │ ├── .rustfmt.toml │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── config/ │ │ │ └── development.yaml │ │ ├── examples/ │ │ │ └── playground.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220101_000001_users.rs │ │ │ ├── m20231103_114510_notes.rs │ │ │ ├── m20240520_173001_files.rs │ │ │ └── main.rs │ │ └── src/ │ │ ├── app.rs │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── controllers/ │ │ │ ├── auth.rs │ │ │ ├── files.rs │ │ │ ├── mod.rs │ │ │ ├── notes.rs │ │ │ └── user.rs │ │ ├── fixtures/ │ │ │ ├── notes.yaml │ │ │ └── users.yaml │ │ ├── lib.rs │ │ ├── mailers/ │ │ │ ├── auth/ │ │ │ │ ├── forgot/ │ │ │ │ │ ├── html.t │ │ │ │ │ ├── subject.t │ │ │ │ │ └── text.t │ │ │ │ └── welcome/ │ │ │ │ ├── html.t │ │ │ │ ├── subject.t │ │ │ │ └── text.t │ │ │ ├── auth.rs │ │ │ └── mod.rs │ │ ├── models/ │ │ │ ├── _entities/ │ │ │ │ ├── files.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── notes.rs │ │ │ │ ├── prelude.rs │ │ │ │ └── users.rs │ │ │ ├── files.rs │ │ │ ├── mod.rs │ │ │ ├── notes.rs │ │ │ └── users.rs │ │ ├── tasks/ │ │ │ ├── mod.rs │ │ │ └── seed.rs │ │ ├── views/ │ │ │ ├── auth.rs │ │ │ ├── mod.rs │ │ │ └── user.rs │ │ └── workers/ │ │ ├── downloader.rs │ │ └── mod.rs │ ├── parquet_example/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── poem_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── lib.rs │ │ │ │ └── service/ │ │ │ │ ├── mod.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ ├── static/ │ │ │ │ └── css/ │ │ │ │ ├── normalize.css │ │ │ │ ├── skeleton.css │ │ │ │ └── style.css │ │ │ ├── templates/ │ │ │ │ ├── edit.html.tera │ │ │ │ ├── error/ │ │ │ │ │ └── 404.html.tera │ │ │ │ ├── index.html.tera │ │ │ │ ├── layout.html.tera │ │ │ │ └── new.html.tera │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── post.rs │ │ │ └── prelude.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_post_table.rs │ │ │ ├── m20220120_000002_seed_posts.rs │ │ │ └── main.rs │ │ └── src/ │ │ └── main.rs │ ├── proxy_cloudflare_worker_example/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── Wrangler.toml │ │ └── src/ │ │ ├── entity.rs │ │ ├── lib.rs │ │ ├── orm.rs │ │ └── route.rs │ ├── proxy_gluesql_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── entity/ │ │ │ ├── mod.rs │ │ │ └── post.rs │ │ └── main.rs │ ├── quickstart/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── react_admin/ │ │ ├── README.md │ │ ├── backend/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── .devcontainer/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── devcontainer.json │ │ │ │ └── docker-compose.yml │ │ │ ├── .github/ │ │ │ │ └── workflows/ │ │ │ │ └── ci.yaml │ │ │ ├── .gitignore │ │ │ ├── .rustfmt.toml │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ ├── config/ │ │ │ │ └── development.yaml │ │ │ ├── examples/ │ │ │ │ └── playground.rs │ │ │ ├── migration/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── README.md │ │ │ │ └── src/ │ │ │ │ ├── lib.rs │ │ │ │ ├── m20220101_000001_users.rs │ │ │ │ ├── m20231103_114510_notes.rs │ │ │ │ ├── m20240520_173001_files.rs │ │ │ │ └── main.rs │ │ │ └── src/ │ │ │ ├── app.rs │ │ │ ├── bin/ │ │ │ │ └── main.rs │ │ │ ├── controllers/ │ │ │ │ ├── auth.rs │ │ │ │ ├── files.rs │ │ │ │ ├── graphql.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── notes.rs │ │ │ │ └── user.rs │ │ │ ├── fixtures/ │ │ │ │ ├── notes.yaml │ │ │ │ └── users.yaml │ │ │ ├── graphql/ │ │ │ │ ├── mod.rs │ │ │ │ └── query_root.rs │ │ │ ├── lib.rs │ │ │ ├── mailers/ │ │ │ │ ├── auth/ │ │ │ │ │ ├── forgot/ │ │ │ │ │ │ ├── html.t │ │ │ │ │ │ ├── subject.t │ │ │ │ │ │ └── text.t │ │ │ │ │ └── welcome/ │ │ │ │ │ ├── html.t │ │ │ │ │ ├── subject.t │ │ │ │ │ └── text.t │ │ │ │ ├── auth.rs │ │ │ │ └── mod.rs │ │ │ ├── models/ │ │ │ │ ├── _entities/ │ │ │ │ │ ├── files.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── notes.rs │ │ │ │ │ ├── prelude.rs │ │ │ │ │ └── users.rs │ │ │ │ ├── files.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── notes.rs │ │ │ │ └── users.rs │ │ │ ├── tasks/ │ │ │ │ ├── mod.rs │ │ │ │ └── seed.rs │ │ │ ├── views/ │ │ │ │ ├── auth.rs │ │ │ │ ├── mod.rs │ │ │ │ └── user.rs │ │ │ └── workers/ │ │ │ ├── downloader.rs │ │ │ └── mod.rs │ │ └── frontend/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── prettier.config.js │ │ ├── public/ │ │ │ └── manifest.json │ │ ├── src/ │ │ │ ├── App.tsx │ │ │ ├── Layout.tsx │ │ │ ├── authProvider.ts │ │ │ ├── dataProvider.ts │ │ │ ├── index.tsx │ │ │ ├── users.json │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── rocket_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── Rocket.toml │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── lib.rs │ │ │ │ ├── pool.rs │ │ │ │ └── service/ │ │ │ │ ├── mod.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ ├── static/ │ │ │ │ └── css/ │ │ │ │ ├── normalize.css │ │ │ │ ├── skeleton.css │ │ │ │ └── style.css │ │ │ ├── templates/ │ │ │ │ ├── base.html.tera │ │ │ │ ├── edit.html.tera │ │ │ │ ├── error/ │ │ │ │ │ └── 404.html.tera │ │ │ │ ├── index.html.tera │ │ │ │ └── new.html.tera │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── post.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_post_table.rs │ │ │ ├── m20220120_000002_seed_posts.rs │ │ │ └── main.rs │ │ └── src/ │ │ └── main.rs │ ├── rocket_okapi_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── Rocket.toml │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── okapi_example.rs │ │ │ └── pool.rs │ │ ├── dto/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── dto.rs │ │ │ └── lib.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── post.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_post_table.rs │ │ │ └── main.rs │ │ ├── service/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── lib.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ └── src/ │ │ └── main.rs │ ├── salvo_example/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── api/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── lib.rs │ │ │ │ └── service/ │ │ │ │ ├── mod.rs │ │ │ │ ├── mutation.rs │ │ │ │ └── query.rs │ │ │ ├── static/ │ │ │ │ └── css/ │ │ │ │ ├── normalize.css │ │ │ │ ├── skeleton.css │ │ │ │ └── style.css │ │ │ ├── templates/ │ │ │ │ ├── edit.html.tera │ │ │ │ ├── error/ │ │ │ │ │ └── 404.html.tera │ │ │ │ ├── index.html.tera │ │ │ │ ├── layout.html.tera │ │ │ │ └── new.html.tera │ │ │ └── tests/ │ │ │ └── crud_tests.rs │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── post.rs │ │ │ └── prelude.rs │ │ ├── migration/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ ├── m20220120_000001_create_post_table.rs │ │ │ ├── m20220120_000002_seed_posts.rs │ │ │ └── main.rs │ │ └── src/ │ │ └── main.rs │ ├── seaography_example/ │ │ ├── README.md │ │ ├── graphql/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── entities/ │ │ │ │ │ ├── baker.rs │ │ │ │ │ ├── bakery.rs │ │ │ │ │ ├── cake.rs │ │ │ │ │ ├── cake_baker.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── prelude.rs │ │ │ │ ├── lib.rs │ │ │ │ ├── main.rs │ │ │ │ └── query_root.rs │ │ │ └── tests/ │ │ │ └── query_tests.rs │ │ └── migration/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── entity/ │ │ │ ├── baker.rs │ │ │ ├── bakery.rs │ │ │ ├── cake.rs │ │ │ ├── cake_baker.rs │ │ │ ├── mod.rs │ │ │ └── prelude.rs │ │ ├── lib.rs │ │ ├── m20230101_000001_create_bakery_table.rs │ │ ├── m20230101_000002_create_baker_table.rs │ │ ├── m20230101_000003_create_cake_table.rs │ │ ├── m20230101_000004_create_cake_baker_table.rs │ │ ├── m20230101_000005_create_customer_table.rs │ │ ├── m20230101_000006_create_order_table.rs │ │ ├── m20230101_000007_create_lineitem_table.rs │ │ ├── m20230102_000001_seed_bakery_data.rs │ │ └── main.rs │ └── tonic_example/ │ ├── Cargo.toml │ ├── README.md │ ├── api/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── proto/ │ │ │ └── post.proto │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ └── service/ │ │ │ ├── mod.rs │ │ │ ├── mutation.rs │ │ │ └── query.rs │ │ └── tests/ │ │ └── crud_tests.rs │ ├── entity/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── post.rs │ │ └── prelude.rs │ ├── migration/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── lib.rs │ │ ├── m20220120_000001_create_post_table.rs │ │ ├── m20220120_000002_seed_posts.rs │ │ └── main.rs │ └── src/ │ ├── client.rs │ └── server.rs ├── issues/ │ ├── 1143/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── entity/ │ │ │ ├── mod.rs │ │ │ └── sea_orm_active_enums.rs │ │ └── main.rs │ ├── 1278/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── entity.rs │ │ └── main.rs │ ├── 1357/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── entity.rs │ │ └── main.rs │ ├── 1473/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── 1582/ │ │ └── schema.sql │ ├── 1599/ │ │ ├── Cargo.toml │ │ ├── entity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── cake.rs │ │ │ ├── cake_filling.rs │ │ │ ├── filling.rs │ │ │ ├── fruit.rs │ │ │ └── lib.rs │ │ └── graphql/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── query_root.rs │ ├── 1790/ │ │ ├── Cargo.toml │ │ └── insert_test.rs │ ├── 249/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── app/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── service/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 262/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── cake.rs │ │ └── main.rs │ ├── 319/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── material.rs │ ├── 324/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── model.rs │ ├── 352/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── binary_primary_key.rs │ │ ├── main.rs │ │ ├── quaternary_primary_key.rs │ │ ├── quinary_primary_key.rs │ │ ├── senary_primary_key.rs │ │ ├── ternary_primary_key.rs │ │ └── unary_primary_key.rs │ ├── 356/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── model.rs │ ├── 400/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── model.rs │ ├── 471/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── main.rs │ │ ├── post.rs │ │ └── setup.rs │ ├── 630/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── create_underscores_table.sql │ │ └── src/ │ │ ├── entity/ │ │ │ ├── mod.rs │ │ │ ├── prelude.rs │ │ │ ├── underscores.rs │ │ │ └── underscores_workaround.rs │ │ └── main.rs │ ├── 693/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── container.rs │ │ ├── content.rs │ │ └── main.rs │ ├── 86/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── cake.rs │ │ └── main.rs │ └── 892/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── sea-orm-arrow/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── sea-orm-cli/ │ ├── Cargo.toml │ ├── README.md │ ├── src/ │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── cli.rs │ │ ├── commands/ │ │ │ ├── generate.rs │ │ │ ├── migrate.rs │ │ │ └── mod.rs │ │ └── lib.rs │ └── template/ │ └── migration/ │ ├── README.md │ ├── _Cargo.toml │ ├── _gitignore │ └── src/ │ ├── lib.rs │ ├── m20220101_000001_create_table.rs │ └── main.rs ├── sea-orm-codegen/ │ ├── Cargo.toml │ ├── README.md │ ├── rustfmt.toml │ ├── src/ │ │ ├── entity/ │ │ │ ├── active_enum.rs │ │ │ ├── base_entity.rs │ │ │ ├── column.rs │ │ │ ├── conjunct_relation.rs │ │ │ ├── mod.rs │ │ │ ├── primary_key.rs │ │ │ ├── relation.rs │ │ │ ├── transformer.rs │ │ │ ├── writer/ │ │ │ │ ├── compact.rs │ │ │ │ ├── dense.rs │ │ │ │ ├── expanded.rs │ │ │ │ ├── frontend.rs │ │ │ │ └── mermaid.rs │ │ │ └── writer.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── merge/ │ │ │ ├── extract.rs │ │ │ └── mod.rs │ │ ├── tests_cfg/ │ │ │ ├── compact/ │ │ │ │ ├── indexes.rs │ │ │ │ └── mod.rs │ │ │ ├── dense/ │ │ │ │ ├── indexes.rs │ │ │ │ └── mod.rs │ │ │ ├── duplicated_many_to_many_paths/ │ │ │ │ ├── bills.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── prelude.rs │ │ │ │ ├── users.rs │ │ │ │ ├── users_saved_bills.rs │ │ │ │ └── users_votes.rs │ │ │ ├── many_to_many/ │ │ │ │ ├── bills.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── prelude.rs │ │ │ │ ├── users.rs │ │ │ │ └── users_votes.rs │ │ │ ├── many_to_many_multiple/ │ │ │ │ ├── bills.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── prelude.rs │ │ │ │ ├── users.rs │ │ │ │ └── users_votes.rs │ │ │ ├── mod.rs │ │ │ └── self_referencing/ │ │ │ ├── bills.rs │ │ │ ├── mod.rs │ │ │ ├── prelude.rs │ │ │ └── users.rs │ │ └── util.rs │ └── tests/ │ ├── compact/ │ │ ├── cake.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_with_double.rs │ │ ├── cake_with_float.rs │ │ ├── child.rs │ │ ├── collection.rs │ │ ├── collection_float.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── mod.rs │ │ ├── parent.rs │ │ ├── prelude.rs │ │ ├── rust_keyword.rs │ │ └── vendor.rs │ ├── compact_with_attributes/ │ │ ├── cake_multiple.rs │ │ ├── cake_none.rs │ │ └── cake_one.rs │ ├── compact_with_derives/ │ │ ├── cake_multiple.rs │ │ ├── cake_none.rs │ │ └── cake_one.rs │ ├── compact_with_schema_name/ │ │ ├── cake.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_with_double.rs │ │ ├── cake_with_float.rs │ │ ├── child.rs │ │ ├── collection.rs │ │ ├── collection_float.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── mod.rs │ │ ├── parent.rs │ │ ├── prelude.rs │ │ ├── rust_keyword.rs │ │ └── vendor.rs │ ├── compact_with_serde/ │ │ ├── cake_both.rs │ │ ├── cake_deserialize.rs │ │ ├── cake_none.rs │ │ ├── cake_serialize.rs │ │ └── cake_serialize_with_hidden_column.rs │ ├── dense/ │ │ ├── cake.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_with_double.rs │ │ ├── cake_with_float.rs │ │ ├── child.rs │ │ ├── collection.rs │ │ ├── collection_float.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── mod.rs │ │ ├── parent.rs │ │ ├── prelude.rs │ │ ├── rust_keyword.rs │ │ └── vendor.rs │ ├── expanded/ │ │ ├── cake.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_with_double.rs │ │ ├── cake_with_float.rs │ │ ├── child.rs │ │ ├── collection.rs │ │ ├── collection_float.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── mod.rs │ │ ├── parent.rs │ │ ├── prelude.rs │ │ ├── rust_keyword.rs │ │ └── vendor.rs │ ├── expanded_with_attributes/ │ │ ├── cake_multiple.rs │ │ ├── cake_none.rs │ │ └── cake_one.rs │ ├── expanded_with_column_derives/ │ │ ├── cake_multiple.rs │ │ ├── cake_none.rs │ │ └── cake_one.rs │ ├── expanded_with_derives/ │ │ ├── cake_multiple.rs │ │ ├── cake_none.rs │ │ └── cake_one.rs │ ├── expanded_with_schema_name/ │ │ ├── cake.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_with_double.rs │ │ ├── cake_with_float.rs │ │ ├── child.rs │ │ ├── collection.rs │ │ ├── collection_float.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── mod.rs │ │ ├── parent.rs │ │ ├── prelude.rs │ │ ├── rust_keyword.rs │ │ └── vendor.rs │ ├── expanded_with_serde/ │ │ ├── cake_both.rs │ │ ├── cake_deserialize.rs │ │ ├── cake_none.rs │ │ ├── cake_serialize.rs │ │ └── cake_serialize_with_hidden_column.rs │ ├── frontend/ │ │ ├── cake.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_with_double.rs │ │ ├── cake_with_float.rs │ │ ├── child.rs │ │ ├── collection.rs │ │ ├── collection_float.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── mod.rs │ │ ├── parent.rs │ │ ├── prelude.rs │ │ ├── rust_keyword.rs │ │ └── vendor.rs │ ├── frontend_with_attributes/ │ │ ├── cake_multiple.rs │ │ ├── cake_none.rs │ │ └── cake_one.rs │ ├── frontend_with_derives/ │ │ ├── cake_multiple.rs │ │ ├── cake_none.rs │ │ └── cake_one.rs │ ├── frontend_with_schema_name/ │ │ ├── cake.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_with_double.rs │ │ ├── cake_with_float.rs │ │ ├── child.rs │ │ ├── collection.rs │ │ ├── collection_float.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── mod.rs │ │ ├── parent.rs │ │ ├── prelude.rs │ │ ├── rust_keyword.rs │ │ └── vendor.rs │ ├── frontend_with_serde/ │ │ ├── cake_both.rs │ │ ├── cake_deserialize.rs │ │ ├── cake_none.rs │ │ ├── cake_serialize.rs │ │ └── cake_serialize_with_hidden_column.rs │ ├── postgres/ │ │ ├── binary_json.rs │ │ └── binary_json_expanded.rs │ └── with_seaography/ │ ├── cake.rs │ ├── cake_expanded.rs │ ├── cake_frontend.rs │ └── mod.rs ├── sea-orm-macros/ │ ├── Cargo.toml │ ├── src/ │ │ ├── derives/ │ │ │ ├── active_enum.rs │ │ │ ├── active_enum_display.rs │ │ │ ├── active_model.rs │ │ │ ├── active_model_behavior.rs │ │ │ ├── active_model_ex.rs │ │ │ ├── arrow_schema.rs │ │ │ ├── attributes.rs │ │ │ ├── case_style.rs │ │ │ ├── column.rs │ │ │ ├── derive_iden.rs │ │ │ ├── entity.rs │ │ │ ├── entity_loader.rs │ │ │ ├── entity_model.rs │ │ │ ├── from_query_result.rs │ │ │ ├── into_active_model.rs │ │ │ ├── migration.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ ├── model_ex.rs │ │ │ ├── partial_model.rs │ │ │ ├── primary_key.rs │ │ │ ├── related_entity.rs │ │ │ ├── relation.rs │ │ │ ├── try_getable_from_json.rs │ │ │ ├── typed_column.rs │ │ │ ├── util.rs │ │ │ ├── value_type.rs │ │ │ └── value_type_match.rs │ │ ├── lib.rs │ │ ├── raw_sql.rs │ │ └── strum/ │ │ ├── LICENSE │ │ ├── enum_iter.rs │ │ ├── helpers/ │ │ │ ├── case_style.rs │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ ├── type_props.rs │ │ │ └── variant_props.rs │ │ └── mod.rs │ └── tests/ │ ├── derive_active_enum_test.rs │ ├── derive_entity_model_auto_increment_test.rs │ ├── derive_entity_model_column_name_test.rs │ └── derive_value_type_test.rs ├── sea-orm-migration/ │ ├── Cargo.toml │ ├── README.md │ ├── src/ │ │ ├── cli.rs │ │ ├── connection.rs │ │ ├── lib.rs │ │ ├── manager.rs │ │ ├── migrator/ │ │ │ ├── exec.rs │ │ │ ├── queries.rs │ │ │ └── with_self.rs │ │ ├── migrator.rs │ │ ├── prelude.rs │ │ ├── schema.rs │ │ ├── seaql_migrations.rs │ │ └── util.rs │ └── tests/ │ ├── common/ │ │ ├── migration/ │ │ │ ├── m20220118_000001_create_cake_table.rs │ │ │ ├── m20220118_000002_create_fruit_table.rs │ │ │ ├── m20220118_000003_seed_cake_table.rs │ │ │ ├── m20220118_000004_create_tea_enum.rs │ │ │ ├── m20220923_000001_seed_cake_table.rs │ │ │ ├── m20230109_000001_seed_cake_table.rs │ │ │ ├── m20250101_000001_create_test_table.rs │ │ │ ├── m20250101_000002_manual_transaction.rs │ │ │ └── mod.rs │ │ ├── migrator/ │ │ │ ├── default.rs │ │ │ ├── mod.rs │ │ │ ├── override_migration_table_name.rs │ │ │ ├── transaction_test.rs │ │ │ └── with_self.rs │ │ └── mod.rs │ ├── main.rs │ └── postgres.rs ├── sea-orm-rocket/ │ ├── Cargo.toml │ ├── README.md │ ├── codegen/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── database.rs │ │ └── lib.rs │ └── lib/ │ ├── Cargo.toml │ └── src/ │ ├── config.rs │ ├── database.rs │ ├── error.rs │ ├── lib.rs │ └── pool.rs ├── sea-orm-sync/ │ ├── CLAUDE.md │ ├── Cargo.toml │ ├── README.md │ ├── examples/ │ │ ├── parquet_example/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── pi_spigot/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── bdigits.html │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── main.rs │ │ └── quickstart/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── main.rs │ ├── src/ │ │ ├── database/ │ │ │ ├── connection.rs │ │ │ ├── db_connection.rs │ │ │ ├── executor.rs │ │ │ ├── mock.rs │ │ │ ├── mod.rs │ │ │ ├── proxy.rs │ │ │ ├── restricted_connection.rs │ │ │ ├── sea_schema_rusqlite.rs │ │ │ ├── sea_schema_shim.rs │ │ │ ├── statement.rs │ │ │ ├── stream/ │ │ │ │ ├── metric.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── query.rs │ │ │ │ └── transaction.rs │ │ │ ├── tracing_spans.rs │ │ │ └── transaction.rs │ │ ├── docs.rs │ │ ├── driver/ │ │ │ ├── mock.rs │ │ │ ├── mod.rs │ │ │ ├── proxy.rs │ │ │ ├── rusqlite.rs │ │ │ ├── sqlite.rs │ │ │ ├── sqlx_common.rs │ │ │ ├── sqlx_mysql.rs │ │ │ ├── sqlx_postgres.rs │ │ │ └── sqlx_sqlite.rs │ │ ├── dynamic/ │ │ │ ├── entity.rs │ │ │ ├── execute.rs │ │ │ ├── mod.rs │ │ │ └── model.rs │ │ ├── entity/ │ │ │ ├── ARROW.md │ │ │ ├── DESIGN.md │ │ │ ├── active_enum.rs │ │ │ ├── active_model.rs │ │ │ ├── active_model_ex.rs │ │ │ ├── active_value.rs │ │ │ ├── arrow_schema.rs │ │ │ ├── base_entity.rs │ │ │ ├── column/ │ │ │ │ ├── types/ │ │ │ │ │ ├── postgres_array.rs │ │ │ │ │ ├── with_datetime.rs │ │ │ │ │ ├── with_ipnetwork.rs │ │ │ │ │ ├── with_json.rs │ │ │ │ │ └── with_uuid.rs │ │ │ │ └── types.rs │ │ │ ├── column.rs │ │ │ ├── column_def.rs │ │ │ ├── compound/ │ │ │ │ ├── has_many.rs │ │ │ │ └── has_one.rs │ │ │ ├── compound.rs │ │ │ ├── identity.rs │ │ │ ├── link.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ ├── partial_model.rs │ │ │ ├── prelude.rs │ │ │ ├── primary_key.rs │ │ │ ├── registry.rs │ │ │ ├── relation.rs │ │ │ └── with_arrow.rs │ │ ├── error.rs │ │ ├── executor/ │ │ │ ├── consolidate.rs │ │ │ ├── cursor.rs │ │ │ ├── delete.rs │ │ │ ├── execute.rs │ │ │ ├── insert.rs │ │ │ ├── mod.rs │ │ │ ├── paginator.rs │ │ │ ├── query.rs │ │ │ ├── returning.rs │ │ │ ├── select/ │ │ │ │ ├── five.rs │ │ │ │ ├── four.rs │ │ │ │ ├── six.rs │ │ │ │ └── three.rs │ │ │ ├── select.rs │ │ │ ├── select_ext.rs │ │ │ └── update.rs │ │ ├── lib.rs │ │ ├── metric.rs │ │ ├── query/ │ │ │ ├── combine.rs │ │ │ ├── debug.rs │ │ │ ├── delete.rs │ │ │ ├── helper.rs │ │ │ ├── insert.rs │ │ │ ├── join.rs │ │ │ ├── json.rs │ │ │ ├── loader.rs │ │ │ ├── mod.rs │ │ │ ├── select.rs │ │ │ ├── traits.rs │ │ │ ├── update.rs │ │ │ └── util.rs │ │ ├── rbac/ │ │ │ ├── context.rs │ │ │ ├── engine/ │ │ │ │ ├── loader.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── permission_request.rs │ │ │ │ ├── resource_request.rs │ │ │ │ ├── role_hierarchy_impl.rs │ │ │ │ └── snapshot.rs │ │ │ ├── entity/ │ │ │ │ ├── mod.rs │ │ │ │ ├── permission.rs │ │ │ │ ├── resource.rs │ │ │ │ ├── role.rs │ │ │ │ ├── role_hierarchy.rs │ │ │ │ ├── role_permission.rs │ │ │ │ ├── user.rs │ │ │ │ ├── user_override.rs │ │ │ │ └── user_role.rs │ │ │ ├── error.rs │ │ │ ├── mod.rs │ │ │ └── schema.rs │ │ ├── schema/ │ │ │ ├── builder.rs │ │ │ ├── entity.rs │ │ │ ├── json.rs │ │ │ ├── mod.rs │ │ │ └── topology.rs │ │ ├── tests_cfg/ │ │ │ ├── cake.rs │ │ │ ├── cake_compact.rs │ │ │ ├── cake_expanded.rs │ │ │ ├── cake_filling.rs │ │ │ ├── cake_filling_price.rs │ │ │ ├── cake_seaography.rs │ │ │ ├── comment.rs │ │ │ ├── entity_linked.rs │ │ │ ├── filling.rs │ │ │ ├── fruit.rs │ │ │ ├── indexes.rs │ │ │ ├── ingredient.rs │ │ │ ├── lunch_set.rs │ │ │ ├── lunch_set_expanded.rs │ │ │ ├── mod.rs │ │ │ ├── post.rs │ │ │ ├── post_tag.rs │ │ │ ├── profile.rs │ │ │ ├── registry.rs │ │ │ ├── rust_keyword.rs │ │ │ ├── sea_orm_active_enums.rs │ │ │ ├── serde_rename.rs │ │ │ ├── tag.rs │ │ │ ├── user.rs │ │ │ └── vendor.rs │ │ ├── util.rs │ │ ├── value/ │ │ │ ├── text_uuid.rs │ │ │ ├── timestamp.rs │ │ │ ├── with_chrono.rs │ │ │ └── with_time.rs │ │ └── value.rs │ └── tests/ │ ├── active_enum_tests.rs │ ├── active_model_ex_tests.rs │ ├── arrow_schema_tests.rs │ ├── arrow_tests.rs │ ├── basic.rs │ ├── bits_tests.rs │ ├── byte_primary_key_tests.rs │ ├── collection_tests.rs │ ├── common/ │ │ ├── bakery_chain/ │ │ │ ├── Readme.md │ │ │ ├── baker.rs │ │ │ ├── bakery.rs │ │ │ ├── cake.rs │ │ │ ├── cakes_bakers.rs │ │ │ ├── customer.rs │ │ │ ├── lineitem.rs │ │ │ ├── mod.rs │ │ │ ├── order.rs │ │ │ ├── schema.rs │ │ │ └── seed_data.rs │ │ ├── bakery_dense/ │ │ │ ├── NOTES.md │ │ │ ├── baker.rs │ │ │ ├── bakery.rs │ │ │ ├── cake.rs │ │ │ ├── cakes_bakers.rs │ │ │ ├── customer.rs │ │ │ ├── entities.mermaid │ │ │ ├── lineitem.rs │ │ │ ├── mod.rs │ │ │ ├── order.rs │ │ │ └── prelude.rs │ │ ├── blogger/ │ │ │ ├── attachment.rs │ │ │ ├── comment.rs │ │ │ ├── mod.rs │ │ │ ├── post.rs │ │ │ ├── post_tag.rs │ │ │ ├── profile.rs │ │ │ ├── tag.rs │ │ │ ├── user.rs │ │ │ ├── user_follower.rs │ │ │ └── user_mono.rs │ │ ├── features/ │ │ │ ├── active_enum.rs │ │ │ ├── active_enum_child.rs │ │ │ ├── active_enum_vec.rs │ │ │ ├── applog.rs │ │ │ ├── binary.rs │ │ │ ├── bits.rs │ │ │ ├── byte_primary_key.rs │ │ │ ├── categories.rs │ │ │ ├── collection.rs │ │ │ ├── collection_expanded.rs │ │ │ ├── custom_active_model.rs │ │ │ ├── dyn_table_name.rs │ │ │ ├── edit_log.rs │ │ │ ├── embedding.rs │ │ │ ├── event_trigger.rs │ │ │ ├── host_network.rs │ │ │ ├── insert_default.rs │ │ │ ├── json_struct.rs │ │ │ ├── json_vec.rs │ │ │ ├── json_vec_derive.rs │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ ├── pi.rs │ │ │ ├── repository.rs │ │ │ ├── satellite.rs │ │ │ ├── schema.rs │ │ │ ├── sea_orm_active_enums.rs │ │ │ ├── self_join.rs │ │ │ ├── teas.rs │ │ │ ├── transaction_log.rs │ │ │ ├── uuid_fmt.rs │ │ │ └── value_type.rs │ │ ├── film_store.rs │ │ ├── mod.rs │ │ ├── runtime.rs │ │ ├── sakila/ │ │ │ ├── NOTES.md │ │ │ └── entities.mermaid │ │ └── setup/ │ │ └── mod.rs │ ├── connection_tests.rs │ ├── crud/ │ │ ├── create_baker.rs │ │ ├── create_cake.rs │ │ ├── create_lineitem.rs │ │ ├── create_order.rs │ │ ├── deletes.rs │ │ ├── error.rs │ │ ├── mod.rs │ │ └── updates.rs │ ├── crud_tests.rs │ ├── cursor_tests.rs │ ├── database_executor_tests.rs │ ├── delete_by_id_tests.rs │ ├── derive_iden_tests.rs │ ├── derive_model_tests.rs │ ├── derive_tests.rs │ ├── dyn_table_name_tests.rs │ ├── embedding_tests.rs │ ├── empty_insert_tests.rs │ ├── entity_loader_tests.rs │ ├── enum_primary_key_tests.rs │ ├── event_trigger_tests.rs │ ├── execute_unprepared_tests.rs │ ├── exists_tests.rs │ ├── from_query_result_tests.rs │ ├── host_network_tests.rs │ ├── impl_from_for_active_model.rs │ ├── insert_default_tests.rs │ ├── json_struct_tests.rs │ ├── json_vec_tests.rs │ ├── loader_tests.rs │ ├── multi_select_tests.rs │ ├── paginator_tests.rs │ ├── parallel_tests.rs │ ├── partial_model_nested/ │ │ ├── local/ │ │ │ ├── mod.rs │ │ │ └── model/ │ │ │ ├── bakery.rs │ │ │ ├── mod.rs │ │ │ ├── schema.rs │ │ │ └── worker.rs │ │ ├── main.rs │ │ └── nested_alias.rs │ ├── partial_model_tests.rs │ ├── pi_tests.rs │ ├── query_tests.rs │ ├── raw_sql_tests.rs │ ├── rbac_tests.rs │ ├── relational_tests.rs │ ├── returning_tests.rs │ ├── schema_sync_tests.rs │ ├── self_join_tests.rs │ ├── sequential_op_tests.rs │ ├── sql_err_tests.rs │ ├── stream_tests.rs │ ├── string_primary_key_tests.rs │ ├── text_uuid_tests.rs │ ├── time_crate_tests.rs │ ├── timestamp_tests.rs │ ├── transaction_tests.rs │ ├── type_tests.rs │ ├── upsert_tests.rs │ ├── uuid_fmt_tests.rs │ ├── uuid_tests.rs │ └── value_type_tests.rs ├── src/ │ ├── database/ │ │ ├── connection.rs │ │ ├── db_connection.rs │ │ ├── executor.rs │ │ ├── mock.rs │ │ ├── mod.rs │ │ ├── proxy.rs │ │ ├── restricted_connection.rs │ │ ├── sea_schema_rusqlite.rs │ │ ├── sea_schema_shim.rs │ │ ├── statement.rs │ │ ├── stream/ │ │ │ ├── metric.rs │ │ │ ├── mod.rs │ │ │ ├── query.rs │ │ │ └── transaction.rs │ │ ├── tracing_spans.rs │ │ └── transaction.rs │ ├── docs.rs │ ├── driver/ │ │ ├── mock.rs │ │ ├── mod.rs │ │ ├── proxy.rs │ │ ├── rusqlite.rs │ │ ├── sqlite.rs │ │ ├── sqlx_common.rs │ │ ├── sqlx_mysql.rs │ │ ├── sqlx_postgres.rs │ │ └── sqlx_sqlite.rs │ ├── dynamic/ │ │ ├── entity.rs │ │ ├── execute.rs │ │ ├── mod.rs │ │ └── model.rs │ ├── entity/ │ │ ├── ARROW.md │ │ ├── DESIGN.md │ │ ├── active_enum.rs │ │ ├── active_model.rs │ │ ├── active_model_ex.rs │ │ ├── active_value.rs │ │ ├── arrow_schema.rs │ │ ├── base_entity.rs │ │ ├── column/ │ │ │ ├── types/ │ │ │ │ ├── postgres_array.rs │ │ │ │ ├── with_datetime.rs │ │ │ │ ├── with_ipnetwork.rs │ │ │ │ ├── with_json.rs │ │ │ │ └── with_uuid.rs │ │ │ └── types.rs │ │ ├── column.rs │ │ ├── column_def.rs │ │ ├── compound/ │ │ │ ├── has_many.rs │ │ │ └── has_one.rs │ │ ├── compound.rs │ │ ├── identity.rs │ │ ├── link.rs │ │ ├── mod.rs │ │ ├── model.rs │ │ ├── partial_model.rs │ │ ├── prelude.rs │ │ ├── primary_key.rs │ │ ├── registry.rs │ │ ├── relation.rs │ │ └── with_arrow.rs │ ├── error.rs │ ├── executor/ │ │ ├── consolidate.rs │ │ ├── cursor.rs │ │ ├── delete.rs │ │ ├── execute.rs │ │ ├── insert.rs │ │ ├── mod.rs │ │ ├── paginator.rs │ │ ├── query.rs │ │ ├── returning.rs │ │ ├── select/ │ │ │ ├── five.rs │ │ │ ├── four.rs │ │ │ ├── six.rs │ │ │ └── three.rs │ │ ├── select.rs │ │ ├── select_ext.rs │ │ └── update.rs │ ├── lib.rs │ ├── metric.rs │ ├── query/ │ │ ├── combine.rs │ │ ├── debug.rs │ │ ├── delete.rs │ │ ├── helper.rs │ │ ├── insert.rs │ │ ├── join.rs │ │ ├── json.rs │ │ ├── loader.rs │ │ ├── mod.rs │ │ ├── select.rs │ │ ├── traits.rs │ │ ├── update.rs │ │ └── util.rs │ ├── rbac/ │ │ ├── context.rs │ │ ├── engine/ │ │ │ ├── loader.rs │ │ │ ├── mod.rs │ │ │ ├── permission_request.rs │ │ │ ├── resource_request.rs │ │ │ ├── role_hierarchy_impl.rs │ │ │ └── snapshot.rs │ │ ├── entity/ │ │ │ ├── mod.rs │ │ │ ├── permission.rs │ │ │ ├── resource.rs │ │ │ ├── role.rs │ │ │ ├── role_hierarchy.rs │ │ │ ├── role_permission.rs │ │ │ ├── user.rs │ │ │ ├── user_override.rs │ │ │ └── user_role.rs │ │ ├── error.rs │ │ ├── mod.rs │ │ └── schema.rs │ ├── schema/ │ │ ├── builder.rs │ │ ├── entity.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ └── topology.rs │ ├── tests_cfg/ │ │ ├── cake.rs │ │ ├── cake_compact.rs │ │ ├── cake_expanded.rs │ │ ├── cake_filling.rs │ │ ├── cake_filling_price.rs │ │ ├── cake_seaography.rs │ │ ├── comment.rs │ │ ├── entity_linked.rs │ │ ├── filling.rs │ │ ├── fruit.rs │ │ ├── indexes.rs │ │ ├── ingredient.rs │ │ ├── lunch_set.rs │ │ ├── lunch_set_expanded.rs │ │ ├── mod.rs │ │ ├── post.rs │ │ ├── post_tag.rs │ │ ├── profile.rs │ │ ├── registry.rs │ │ ├── rust_keyword.rs │ │ ├── sea_orm_active_enums.rs │ │ ├── serde_rename.rs │ │ ├── tag.rs │ │ ├── user.rs │ │ └── vendor.rs │ ├── util.rs │ ├── value/ │ │ ├── text_uuid.rs │ │ ├── timestamp.rs │ │ ├── with_chrono.rs │ │ └── with_time.rs │ └── value.rs └── tests/ ├── active_enum_tests.rs ├── active_model_ex_tests.rs ├── arrow_schema_tests.rs ├── arrow_tests.rs ├── basic.rs ├── bits_tests.rs ├── byte_primary_key_tests.rs ├── collection_tests.rs ├── common/ │ ├── bakery_chain/ │ │ ├── Readme.md │ │ ├── baker.rs │ │ ├── bakery.rs │ │ ├── cake.rs │ │ ├── cakes_bakers.rs │ │ ├── customer.rs │ │ ├── lineitem.rs │ │ ├── mod.rs │ │ ├── order.rs │ │ ├── schema.rs │ │ └── seed_data.rs │ ├── bakery_dense/ │ │ ├── NOTES.md │ │ ├── baker.rs │ │ ├── bakery.rs │ │ ├── cake.rs │ │ ├── cakes_bakers.rs │ │ ├── customer.rs │ │ ├── entities.mermaid │ │ ├── lineitem.rs │ │ ├── mod.rs │ │ ├── order.rs │ │ └── prelude.rs │ ├── blogger/ │ │ ├── attachment.rs │ │ ├── comment.rs │ │ ├── mod.rs │ │ ├── post.rs │ │ ├── post_tag.rs │ │ ├── profile.rs │ │ ├── tag.rs │ │ ├── user.rs │ │ ├── user_follower.rs │ │ └── user_mono.rs │ ├── features/ │ │ ├── active_enum.rs │ │ ├── active_enum_child.rs │ │ ├── active_enum_vec.rs │ │ ├── applog.rs │ │ ├── binary.rs │ │ ├── bits.rs │ │ ├── byte_primary_key.rs │ │ ├── categories.rs │ │ ├── collection.rs │ │ ├── collection_expanded.rs │ │ ├── custom_active_model.rs │ │ ├── dyn_table_name.rs │ │ ├── edit_log.rs │ │ ├── embedding.rs │ │ ├── event_trigger.rs │ │ ├── host_network.rs │ │ ├── insert_default.rs │ │ ├── json_struct.rs │ │ ├── json_vec.rs │ │ ├── json_vec_derive.rs │ │ ├── metadata.rs │ │ ├── mod.rs │ │ ├── pi.rs │ │ ├── repository.rs │ │ ├── satellite.rs │ │ ├── schema.rs │ │ ├── sea_orm_active_enums.rs │ │ ├── self_join.rs │ │ ├── teas.rs │ │ ├── transaction_log.rs │ │ ├── uuid_fmt.rs │ │ └── value_type.rs │ ├── film_store.rs │ ├── mod.rs │ ├── runtime.rs │ ├── sakila/ │ │ ├── NOTES.md │ │ └── entities.mermaid │ └── setup/ │ └── mod.rs ├── connection_tests.rs ├── crud/ │ ├── create_baker.rs │ ├── create_cake.rs │ ├── create_lineitem.rs │ ├── create_order.rs │ ├── deletes.rs │ ├── error.rs │ ├── mod.rs │ └── updates.rs ├── crud_tests.rs ├── cursor_tests.rs ├── database_executor_tests.rs ├── delete_by_id_tests.rs ├── derive_iden_tests.rs ├── derive_model_tests.rs ├── derive_tests.rs ├── dyn_table_name_tests.rs ├── embedding_tests.rs ├── empty_insert_tests.rs ├── entity_loader_tests.rs ├── enum_primary_key_tests.rs ├── event_trigger_tests.rs ├── execute_unprepared_tests.rs ├── exists_tests.rs ├── from_query_result_tests.rs ├── host_network_tests.rs ├── impl_from_for_active_model.rs ├── insert_default_tests.rs ├── json_struct_tests.rs ├── json_vec_tests.rs ├── loader_tests.rs ├── multi_select_tests.rs ├── paginator_tests.rs ├── parallel_tests.rs ├── partial_model_nested/ │ ├── local/ │ │ ├── mod.rs │ │ └── model/ │ │ ├── bakery.rs │ │ ├── mod.rs │ │ ├── schema.rs │ │ └── worker.rs │ ├── main.rs │ └── nested_alias.rs ├── partial_model_tests.rs ├── pi_tests.rs ├── query_tests.rs ├── raw_sql_tests.rs ├── rbac_tests.rs ├── relational_tests.rs ├── returning_tests.rs ├── schema_sync_tests.rs ├── self_join_tests.rs ├── sequential_op_tests.rs ├── sql_err_tests.rs ├── stream_tests.rs ├── string_primary_key_tests.rs ├── text_uuid_tests.rs ├── time_crate_tests.rs ├── timestamp_tests.rs ├── transaction_tests.rs ├── type_tests.rs ├── upsert_tests.rs ├── uuid_fmt_tests.rs ├── uuid_tests.rs └── value_type_tests.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.html.tera linguist-language=HTML ================================================ FILE: .github/.well-known/funding-manifest-urls ================================================ https://www.sea-ql.org/funding.json ================================================ FILE: .github/FUNDING.yml ================================================ github: SeaQL ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug Report about: Report a bug or design flaw title: "" labels: "" assignees: "" type: Bug --- ## Description ## Steps to Reproduce 1. 2. 3. ### Expected Behavior ### Actual Behavior ### Reproduces How Often ### Workarounds ## Versions ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Q & A url: https://github.com/SeaQL/sea-orm/discussions/new?category=q-a about: Ask a question or look for help. Try to provide sufficient context, snippets to reproduce and error messages. - name: SeaQL Discord Server url: https://discord.com/invite/uCPdDXzbdv about: Join our Discord server to chat with others in the SeaQL community! - name: Professional Support url: https://github.com/sponsors/SeaQL about: Startup and Enterprise-tier sponsors enjoy direct communication with the SeaQL team and professional support for SeaORM usage, data engineering, and Rust development. ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.md ================================================ --- name: Feature Request about: Suggest a new feature for this project title: "" labels: "" assignees: "" type: Feature --- ## Motivation ## Proposed Solutions ## Additional Information ================================================ FILE: .github/workflows/release-bot.yml ================================================ name: Release Bot on: release: types: [published] jobs: comment: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - name: Commenting on `${{ github.event.release.tag_name }}` release uses: billy1624/release-comment-on-pr@master with: release-tag: ${{ github.event.release.tag_name }} token: ${{ github.token }} message: | ### :tada: Released In [${releaseTag}](${releaseUrl}) :tada: Huge thanks for the contribution! This feature has now been released, so it's a great time to upgrade. Show some love with a ⭐ on our repo, every star counts! ================================================ FILE: .github/workflows/rust.yml ================================================ # GitHub Actions with Conditional Job Running Based on Commit Message # # -------------------------------------------------------------------------------- # # Following jobs will always run # # - `clippy` # - `rustfmt` # - `taplo` # - `test` # - `examples` # # Following jobs will be run when no keywords were found in commit message) # # - `sqlite` # - `mysql` # - `mariadb` # - `postgres` # # Following jobs will be run if keywords `[issues]` were found in commit message # # - Jobs that will always run # - `issues` # # Following jobs will be run if keywords `[cli]` were found in commit message # # - Jobs that will always run # - `cli` # # Following jobs will be run if keywords `[sqlite]` were found in commit message # # - Jobs that will always run # - `compile` # - `sqlite` # # Following jobs will be run if keywords `[mysql]` were found in commit message # # - Jobs that will always run # - `compile` # - `mysql` # - `mariadb` # # Following jobs will be run if keywords `[postgres]` were found in commit message # # - Jobs that will always run # - `compile` # - `postgres` name: tests on: pull_request: paths-ignore: - "**.md" - ".github/ISSUE_TEMPLATE/**" push: paths-ignore: - "**.md" - ".github/ISSUE_TEMPLATE/**" branches: - master - 1.*.x - 0.*.x - pr/**/ci - ci-* concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref || github.run_id }} cancel-in-progress: true env: CARGO_TERM_COLOR: always RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: true RUSTFLAGS: "-C debuginfo=0" CARGO_INCREMENTAL: 0 jobs: init: name: Init runs-on: ubuntu-latest outputs: run-sqlite: ${{ contains(steps.git-log.outputs.message, '[sqlite]') }} run-mysql: ${{ contains(steps.git-log.outputs.message, '[mysql]') }} run-postgres: ${{ contains(steps.git-log.outputs.message, '[postgres]') }} run-cli: ${{ contains(steps.git-log.outputs.message, '[cli]') }} run-issues: ${{ contains(steps.git-log.outputs.message, '[issues]') }} run-partial: >- ${{ contains(steps.git-log.outputs.message, '[sqlite]') || contains(steps.git-log.outputs.message, '[mysql]') || contains(steps.git-log.outputs.message, '[postgres]') || contains(steps.git-log.outputs.message, '[cli]') || contains(steps.git-log.outputs.message, '[issues]') }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - id: git-log run: echo "message=$(git log --no-merges -1 --oneline)" >> $GITHUB_OUTPUT clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo clippy --all -- -D warnings - run: cargo clippy --all --features runtime-tokio-native-tls,sqlx-all -- -D warnings - run: cargo clippy --manifest-path sea-orm-cli/Cargo.toml -- -D warnings - run: cargo clippy --manifest-path sea-orm-migration/Cargo.toml -- -D warnings rustfmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: rustfmt - run: cargo fmt --all -- --check - run: cargo fmt --manifest-path sea-orm-cli/Cargo.toml --all -- --check - run: cargo fmt --manifest-path sea-orm-migration/Cargo.toml --all -- --check taplo: name: Taplo runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo install --locked taplo-cli - run: taplo fmt --check compile: name: Compile (${{ matrix.label }}) needs: init runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - label: build kind: build - label: tokio native-tls mysql kind: test features: sqlx-mysql,runtime-tokio-native-tls - label: tokio rustls mysql kind: test features: sqlx-mysql,runtime-tokio-rustls - label: tokio native-tls postgres kind: test features: sqlx-postgres,runtime-tokio-native-tls - label: tokio rustls postgres kind: test features: sqlx-postgres,runtime-tokio-rustls - label: tokio sqlite kind: test features: sqlx-sqlite,runtime-tokio steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-compile-${{ matrix.label }}-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - if: matrix.kind == 'build' run: | cargo build --no-default-features cargo build --no-default-features --features seaography cargo build --features rbac,schema-sync - if: matrix.kind == 'test' run: cargo test --test '*' --features tests-features,${{ matrix.features }} --no-run test: name: Unit Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo test --workspace --no-run - run: cargo test --workspace - run: cargo test --lib --features rbac - run: cargo test --lib --features entity-registry -- registry - run: cargo test --manifest-path sea-orm-cli/Cargo.toml --no-run - run: cargo test --manifest-path sea-orm-cli/Cargo.toml cli: name: CLI needs: init if: ${{ (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-cli == 'true') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install --path sea-orm-cli --debug examples: name: Examples runs-on: ubuntu-latest strategy: fail-fast: false matrix: path: [ actix_example, axum_example, basic, graphql_example, jsonrpsee_example, loco_example, loco_seaography, loco_starter, parquet_example, poem_example, proxy_gluesql_example, quickstart, react_admin, rocket_example, rocket_okapi_example, salvo_example, seaography_example, tonic_example, ] steps: - uses: actions/checkout@v4 - if: ${{ contains(matrix.path, 'tonic_example') }} uses: arduino/setup-protoc@v3 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-examples-${{ matrix.path }} - uses: mozilla-actions/sccache-action@v0.0.9 - working-directory: ./examples/ run: | find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo fmt --manifest-path {} -- --check find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo update --manifest-path {} find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo test --manifest-path {} ${{'! '}}${{ '[ -d "' }}${{ matrix.path }}${{ '/api" ]' }} || find ${{ matrix.path }}/api -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo test --manifest-path {} - if: matrix.path == 'quickstart' working-directory: ./examples/${{ matrix.path }} run: cargo run issues-matrix: name: Issues Matrix needs: init if: ${{ (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-issues == 'true') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - id: set-matrix run: echo "path_matrix=$(find issues -type f -name 'Cargo.toml' -printf '%P\0' | jq -Rc '[ split("\u0000") | .[] | "issues/\(.)" ]')" >> $GITHUB_OUTPUT outputs: path_matrix: ${{ steps.set-matrix.outputs.path_matrix }} issues: name: Issues needs: - init - issues-matrix if: ${{ (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-issues == 'true') }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: path: ${{ fromJson(needs.issues-matrix.outputs.path_matrix) }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-issue-${{ matrix.path }} - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo build --manifest-path ${{ matrix.path }} - run: cargo test --manifest-path ${{ matrix.path }} sqlite: name: SQLite needs: - init - compile if: >- ${{ needs.init.outputs.run-partial == 'false' || (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-sqlite == 'true') }} runs-on: ubuntu-latest env: DATABASE_URL: "sqlite::memory:" strategy: fail-fast: false matrix: runtime: [tokio] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-sqlite-tests-sqlite-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo test --test '*' --features tests-features,sqlx-sqlite,runtime-${{ matrix.runtime }} --no-run - run: cargo test --test '*' --features tests-features,sqlx-sqlite,runtime-${{ matrix.runtime }} - run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-sqlite,runtime-${{ matrix.runtime }} --no-run - run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-sqlite,runtime-${{ matrix.runtime }} rusqlite: name: rusqlite needs: - init - compile if: >- ${{ needs.init.outputs.run-partial == 'false' || (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-sqlite == 'true') }} runs-on: ubuntu-latest env: DATABASE_URL: "sqlite::memory:" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-sqlite-tests-rusqlite-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - working-directory: ./sea-orm-sync run: cargo test --test '*' --features tests-features,rusqlite - working-directory: ./sea-orm-sync/examples/quickstart run: cargo run - working-directory: ./sea-orm-sync/examples/parquet_example run: cargo run mysql: name: MySQL needs: - init - compile if: >- ${{ needs.init.outputs.run-partial == 'false' || (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-mysql == 'true') }} runs-on: ubuntu-latest env: DATABASE_URL: "mysql://root:@localhost" strategy: fail-fast: false matrix: version: [lts, 5.7] runtime: [tokio] tls: [native-tls] services: mysql: image: mysql:${{ matrix.version }} env: MYSQL_HOST: 127.0.0.1 MYSQL_DB: mysql MYSQL_USER: sea MYSQL_PASSWORD: sea MYSQL_ALLOW_EMPTY_PASSWORD: yes ports: - "3306:3306" options: >- --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-mysql-tests-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo test --test '*' --features tests-features,sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} --no-run - run: cargo test --test '*' --features tests-features,sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} - run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} --no-run - run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} mariadb: name: MariaDB needs: - init - compile if: >- ${{ needs.init.outputs.run-partial == 'false' || (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-mysql == 'true') }} runs-on: ubuntu-latest env: DATABASE_URL: "mysql://root:@localhost" strategy: fail-fast: false matrix: version: [lts] runtime: [tokio] tls: [native-tls] services: mariadb: image: mariadb:${{ matrix.version }} env: MARIADB_HOST: 127.0.0.1 MARIADB_DB: mysql MARIADB_USER: sea MARIADB_PASSWORD: sea MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes ports: - "3306:3306" options: >- --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-mariadb-tests-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo test --test '*' --features tests-features,sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} --no-run - run: cargo test --test '*' --features tests-features,sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} postgres: name: Postgres needs: - init - compile if: >- ${{ needs.init.outputs.run-partial == 'false' || (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-postgres == 'true') }} runs-on: ubuntu-latest env: DATABASE_URL: "postgres://root:root@localhost" strategy: fail-fast: false matrix: version: [14, 16] runtime: [tokio] tls: [native-tls] services: postgres: image: postgres:${{ matrix.version }} env: POSTGRES_HOST: 127.0.0.1 POSTGRES_USER: root POSTGRES_PASSWORD: root ports: - "5432:5432" options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-postgres-tests-${{ matrix.runtime }}-${{ matrix.tls }}-${{ hashFiles('**/Cargo.toml') }} - uses: mozilla-actions/sccache-action@v0.0.9 - run: cargo test --test '*' --features tests-features,sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }} --no-run - run: cargo test --test '*' --features tests-features,sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }} - run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }} --no-run - run: cargo test --manifest-path sea-orm-migration/Cargo.toml --test '*' --features sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }} ================================================ FILE: .gitignore ================================================ target firedbg Cargo.lock *.sublime* .vscode .idea/* */.idea/* .env.local .DS_Store ================================================ FILE: .rustfmt.toml ================================================ format_code_in_doc_comments = true ignore = [ # These files are used to test the output of derive macros. # Formatting them will cause the tests to fail. "sea-orm-codegen/src/tests_cfg", ] ================================================ FILE: .taplo.toml ================================================ include = ["**/*.toml"] [formatting] align_entries = true allowed_blank_lines = 1 array_auto_collapse = false indent_string = ' ' reorder_keys = true ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## 2.0.0 - pending ### Release Candidates - [2.0.0-rc.37](changelog/2.0.0-rc.37.md) — ER Diagram Generation - [2.0.0-rc.36](changelog/2.0.0-rc.36.md) — Per-migration transaction control - [2.0.0-rc.35](changelog/2.0.0-rc.35.md) — SQLite transaction modes, DeriveIntoActiveModel extensions, Decimal64/Bytes, schema sync fix - [2.0.0-rc.34](changelog/2.0.0-rc.34.md) — Arrow/Parquet support, `try_from_u64` for DeriveValueType - [2.0.0-rc.32](changelog/2.0.0-rc.32.md) — `MigratorTrait` with `self`, PostgreSQL `application_name` - [2.0.0-rc.31](changelog/2.0.0-rc.31.md) — `ne_all`, typed `TextUuid`, COUNT overflow fix - [2.0.0-rc.30](changelog/2.0.0-rc.30.md) — Maintenance release, `sea-query` bump - [2.0.0-rc.29](changelog/2.0.0-rc.29.md) — Tracing spans, UUID-as-TEXT, relation filtering, LEFT JOIN fix - [2.0.0-rc.28](changelog/2.0.0-rc.28.md) — `sqlx-all` in migration, `set_if_not_equals_and`, auto_increment for String/Uuid PKs - [2.0.0-rc.27](changelog/2.0.0-rc.27.md) — `DeriveValueType` implements `NotU8` for PostgreSQL arrays - [2.0.0-rc.26](changelog/2.0.0-rc.26.md) — `postgres-use-serial-pk` feature for legacy serial PKs - [2.0.0-rc.25](changelog/2.0.0-rc.25.md) — Value system restoration, `sea-query` bump - [2.0.0-rc.24](changelog/2.0.0-rc.24.md) — `sea-query` bump to rc.27 - [2.0.0-rc.23](changelog/2.0.0-rc.23.md) — `DeriveValueType` implements `IntoActiveValue`, remove `NotU8` - [2.0.0-rc.22](changelog/2.0.0-rc.22.md) — `DatabaseExecutor` unified type, value array refactor - [2.0.0-rc.21](changelog/2.0.0-rc.21.md) — Rusqlite / `sea-orm-sync` crate, `exists` on PaginatorTrait - [2.0.0-rc.20](changelog/2.0.0-rc.20.md) — Stringy newtypes, M2M self-ref, nullable columns, bug fixes ### New Features * Role Based Access Control https://github.com/SeaQL/sea-orm/pull/2683 1. a hierarchical RBAC engine that is table scoped + a user has 1 (and only 1) role + a role has a set of permissions on a set of resources + permissions here are CRUD operations and resources are tables + but the engine is generic so can be used for other things + roles have hierarchy, and so can inherit permissions + there is a wildcard `*` to grant all permissions or resources + individual users can have rules override 3. a set of Entities to load / store the access control rules to / from database 4. a query auditor that dissect queries for necessary permissions (implemented in SeaQuery) 5. integration of RBAC into SeaORM in form of `RestrictedConnection`. it implements `ConnectionTrait`, and will audit all queries and perform permission check, and reject them accordingly. all Entity operations except raw SQL are supported. complex joins, insert select from, and even CTE queries are supported. ```rust // load rules from database db_conn.load_rbac().await?; // admin can create bakery let db = db_conn.restricted_for(admin)?; let seaside_bakery = bakery::ActiveModel { name: Set("SeaSide Bakery".to_owned()), ..Default::default() }; assert!(Bakery::insert(seaside_bakery).exec(&db).await.is_ok()); // public cannot create bakery let db = db_conn.restricted_for(public)?; assert!(matches!( Bakery::insert(bakery::ActiveModel::default()) .exec(&db) .await, Err(DbErr::AccessDenied { .. }) )); ``` * Overhauled `Entity::insert_many`. We've made a number of changes https://github.com/SeaQL/sea-orm/pull/2628 1. removed APIs that can panic 2. new helper struct `InsertMany`, `last_insert_id` is now `Option` 3. on empty iterator, `None` or `vec![]` is returned on exec operations 4. `TryInsert` API is unchanged Previously, `insert_many` shares the same helper struct with `insert_one`, which led to an awkard API. ```rust let res = Bakery::insert_many(std::iter::empty()) .on_empty_do_nothing() // <- you need to add this .exec(db) .await; assert!(matches!(res, Ok(TryInsertResult::Empty))); ``` `last_insert_id` is now `Option`: ```rust struct InsertManyResult { pub last_insert_id: Option< as PrimaryKeyTrait>::ValueType>, } ``` Which means the awkardness is removed: ```rust let res = Entity::insert_many::([]).exec(db).await; assert_eq!(res?.last_insert_id, None); // insert nothing return None let res = Entity::insert_many([ActiveModel { id: Set(1) }, ActiveModel { id: Set(2) }]) .exec(db) .await; assert_eq!(res?.last_insert_id, Some(2)); // insert something return Some ``` Same on conflict API as before: ```rust let res = Entity::insert_many([ActiveModel { id: Set(3) }, ActiveModel { id: Set(4) }]) .on_conflict_do_nothing() .exec(db) .await; assert!(matches!(conflict_insert, Ok(TryInsertResult::Conflicted))); ``` Exec with returning now returns a `Vec`, so it feels intuitive: ```rust assert!( Entity::insert_many::([]) .exec_with_returning(db) .await? .is_empty() // no footgun, nice ); assert_eq!( Entity::insert_many([ ActiveModel { id: NotSet, value: Set("two".into()), } ]) .exec_with_returning(db) .await .unwrap(), [ Model { id: 2, value: "two".into(), } ] ); ``` * Improved utility of `ActiveModel::from_json`. Consider the following Entity https://github.com/SeaQL/sea-orm/pull/2599 ```rust #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, // <- not nullable pub name: String, } ``` Previously, the following would result in error "missing field `id`": ```rust assert!( cake::ActiveModel::from_json(json!({ "name": "Apple Pie", })).is_err(); ); ``` Now, the ActiveModel will be partially filled: ```rust assert_eq!( cake::ActiveModel::from_json(json!({ "name": "Apple Pie", })) .unwrap(), cake::ActiveModel { id: NotSet, name: Set("Apple Pie".to_owned()), } ); ``` * A full `Model` can now be used as `PartialModel` in nested query https://github.com/SeaQL/sea-orm/pull/2642 ```rust #[derive(DerivePartialModel)] #[sea_orm(entity = "cake::Entity")] struct Cake { id: i32, name: String, #[sea_orm(nested)] bakery: Option, } let cake: Cake = cake::Entity::find() .left_join(bakery::Entity) .order_by_asc(cake::Column::Id) .into_partial_model() .one(&ctx.db) .await? .unwrap(); assert_eq!(cake.id, 13); assert_eq!(cake.name, "Cheesecake"); assert_eq!( cake.bakery.unwrap(), bakery::Model { id: 42, name: "cool little bakery".to_string(), } ); ``` * Wrapper type derived with `DeriveValueType` can now be used as primary key https://github.com/SeaQL/sea-orm/pull/2643 ```rust #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "my_value_type")] pub struct Model { #[sea_orm(primary_key)] pub id: MyInteger, } #[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] pub struct MyInteger(pub i32); // only for i8 | i16 | i32 | i64 | u8 | u16 | u32 | u64 ``` * You can now define unique keys that span multiple columns in Entity https://github.com/SeaQL/sea-orm/pull/2651 ```rust #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "lineitem")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(unique_key = "item")] pub order_id: i32, #[sea_orm(unique_key = "item")] pub cake_id: i32, } let stmts = Schema::new(backend).create_index_from_entity(lineitem::Entity); assert_eq!( stmts[0], Index::create() .name("idx-lineitem-item") .table(lineitem::Entity) .col(lineitem::Column::OrderId) .col(lineitem::Column::CakeId) .unique() .take() ); assert_eq!( backend.build(stmts[0]), r#"CREATE UNIQUE INDEX "idx-lineitem-item" ON "lineitem" ("order_id", "cake_id")"# ); ``` * Overhauled `ConnectionTrait` API: `execute`, `query_one`, `query_all`, `stream` now takes in SeaQuery statement instead of raw SQL statement https://github.com/SeaQL/sea-orm/pull/2657 ```rust // old let query: SelectStatement = Entity::find().filter(..).into_query(); let backend = self.db.get_database_backend(); let stmt = backend.build(&query); let rows = self.db.query_all(stmt).await?; // new let query: SelectStatement = Entity::find().filter(..).into_query(); let rows = self.db.query_all(&query).await?; ``` * Added `raw_sql` macro for ergonomic parameter injection ```rust #[derive(FromQueryResult)] struct Cake { name: String, #[sea_orm(nested)] bakery: Option, } #[derive(FromQueryResult)] struct Bakery { #[sea_orm(alias = "bakery_name")] name: String, } let cake_ids = [2, 3, 4]; // expanded by the `..` operator let cake: Option = Cake::find_by_statement(raw_sql!( Sqlite, r#"SELECT "cake"."name", "bakery"."name" AS "bakery_name" FROM "cake" LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id" WHERE "cake"."id" IN ({..cake_ids})"# )) .one(db) .await?; ``` * Added `consolidate` method to `SelectThree`. This output has different shape depending on the topology of the join. ```rust // Order -> Customer // -> Lineitem let items: Vec<(order::Model, Option, Option)> = order::Entity::find() .find_also_related(customer::Entity) .find_also_related(lineitem::Entity) .order_by_asc(order::Column::Id) .order_by_asc(lineitem::Column::Id) .all(&ctx.db) .await?; // flat result assert_eq!( items, vec![ (order, Some(customer), Some(line_1)), (order, Some(customer), Some(line_2)), ] ); let items: Vec<(order::Model, Vec, Vec)> = order::Entity::find() .find_also_related(customer::Entity) .find_also_related(lineitem::Entity) .order_by_asc(order::Column::Id) .order_by_asc(lineitem::Column::Id) .consolidate() // <- .all(&ctx.db) .await?; // consolidated by order assert_eq!( items, vec![( order, vec![customer], vec![line_1, line_2] )] ); // Order -> Lineitem -> Cake let items: Vec<(order::Model, Option, Option)> = order::Entity::find() .find_also_related(lineitem::Entity) .and_also_related(cake::Entity) .order_by_asc(order::Column::Id) .order_by_asc(lineitem::Column::Id) .all(&ctx.db) .await?; // flat result assert_eq!( items, vec![ (order, Some(line_1), Some(cake_1)), (order, Some(line_2), Some(cake_2)), ] ); let items: Vec<(order::Model, Vec<(lineitem::Model, Vec)>)> = order::Entity::find() .find_also_related(lineitem::Entity) .and_also_related(cake::Entity) .order_by_asc(order::Column::Id) .order_by_asc(lineitem::Column::Id) .consolidate() // <- .all(&ctx.db) .await?; // consolidated by order first, then by line assert_eq!( items, vec![( order, vec![(line_1, vec![cake_1]), (line_2, vec![cake_2])] )] ); ``` * Added `Select::has_related` ```rust // cake -> fruit: find all cakes containing mango assert_eq!( cake::Entity::find() .has_related(fruit::Entity, fruit::Column::Name.eq("Mango")) .build(DbBackend::Sqlite) .to_string(), [ r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, r#"WHERE EXISTS(SELECT 1 FROM "fruit""#, r#"WHERE "fruit"."name" = 'Mango'"#, r#"AND "cake"."id" = "fruit"."cake_id")"#, ] .join(" ") ); // cake -> cake_filling -> filling: find all cakes with orange fillings assert_eq!( cake::Entity::find() .has_related(filling::Entity, filling::Column::Name.eq("Marmalade")) .build(DbBackend::Sqlite) .to_string(), [ r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, r#"WHERE EXISTS(SELECT 1 FROM "filling""#, r#"INNER JOIN "cake_filling" ON "cake_filling"."filling_id" = "filling"."id""#, r#"WHERE "filling"."name" = 'Marmalade'"#, r#"AND "cake"."id" = "cake_filling"."cake_id")"#, ] .join(" ") ); ``` * Support self-referencing relations in loader ```rust #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "staff")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub reports_to_id: Option, #[sea_orm(self_ref, relation_enum = "ReportsTo", from = "reports_to_id", to = "id")] pub reports_to: HasOne, } // Entity Loader let staff = staff::Entity::load() .with(staff::Relation::ReportsTo) .all(db) .await?; assert_eq!(staff[0].name, "Alan"); assert_eq!(staff[0].reports_to, None); assert_eq!(staff[1].name, "Ben"); assert_eq!(staff[1].reports_to.as_ref().unwrap().name, "Alan"); assert_eq!(staff[2].name, "Alice"); assert_eq!(staff[2].reports_to.as_ref().unwrap().name, "Alan"); // Model Loader let staff = staff::Entity::find().all(db).await?; let reports_to = staff .load_self(staff::Entity, staff::Relation::ReportsTo, db) .await?; assert_eq!(staff[0].name, "Alan"); assert_eq!(reports_to[0], None); assert_eq!(staff[1].name, "Ben"); assert_eq!(reports_to[1].unwrap().name, "Alan"); assert_eq!(staff[2].name, "Alice"); assert_eq!(reports_to[2].unwrap().name, "Alan"); ``` * Strongly-typed column https://github.com/SeaQL/sea-orm/pull/2794 ```rust // old user::Entity::find().filter(user::Column::Name.contains("Bob")) // new user::Entity::find().filter(user::COLUMN.name.contains("Bob")) // compile error: the trait `From<{integer}>` is not implemented for `String` user::Entity::find().filter(user::COLUMN.name.like(2)) ``` * Unix timestamp column type that will be mapped to big integer in database ```rust #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "access_log")] pub struct Model { .. // with `chrono` crate pub ts: ChronoUnixTimestamp, pub ms: ChronoUnixTimestampMillis, .. // with `time` crate pub ts: TimeUnixTimestamp, pub ms: TimeUnixTimestampMillis, } ``` * Nested ActiveModel (ActiveModelEx) and cascade operations https://github.com/SeaQL/sea-orm/pull/2818 The following operation saves a new set of user + profile + post + tag + post_tag into the database atomically: ```rust let user = user::ActiveModel::builder() .set_name("Bob") .set_email("bob@sea-ql.org") .set_profile(profile::ActiveModel::builder().set_picture("image.jpg")) .add_post( post::ActiveModel::builder() .set_title("Nice weather") .add_tag(tag::ActiveModel::builder().set_tag("sunny")), ) .save(db) .await?; ``` ### Enhancements * [sea-orm-cli] Added `--column-extra-derives` https://github.com/SeaQL/sea-orm/pull/2212 * [sea-orm-cli] Added `--big-integer-type=i32` to use i32 for bigint (for SQLite) * [sea-orm-cli] Fix codegen to not generate relations to filtered entities https://github.com/SeaQL/sea-orm/pull/2913 * [sea-orm-cli] Added `--experimental-preserve-user-modifications` https://github.com/SeaQL/sea-orm/pull/2755 https://github.com/SeaQL/sea-orm/pull/2964 * Added `Model::try_set` * Added new error variant `BackendNotSupported`. Previously, it panics with e.g. "Database backend doesn't support RETURNING" https://github.com/SeaQL/sea-orm/pull/2630 ```rust let result = cake::Entity::insert_many([]) .exec_with_returning_keys(db) .await; if db.support_returning() { // Postgres and SQLite assert_eq!(result.unwrap(), []); } else { // MySQL assert!(matches!(result, Err(DbErr::BackendNotSupported { .. }))); } ``` * Added new error variant `PrimaryKeyNotSet`. Previously, it panics with "PrimaryKey is not set" https://github.com/SeaQL/sea-orm/pull/2627 ```rust assert!(matches!( Update::one(cake::ActiveModel { ..Default::default() }) .exec(&db) .await, Err(DbErr::PrimaryKeyNotSet { .. }) )); ``` * Remove panics in `Schema::create_enum_from_active_enum` https://github.com/SeaQL/sea-orm/pull/2634 ```rust fn create_enum_from_active_enum(&self) -> Option // method can now return None ``` * Added `ColumnTrait::eq_any` as a shorthand for the ` = ANY` operator. Postgres only. ```rust assert_eq!( cake::Entity::find() .filter(cake::Column::Id.eq_any(vec![4, 5])) .build(DbBackend::Postgres) .to_string(), r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = ANY(ARRAY [4,5])"# ); ``` * Added `ActiveModelTrait::try_set` ```rust pub trait ActiveModelTrait { /// old: set the Value of a ActiveModel field, panic if failed fn set(&mut self, c: ::Column, v: Value) { self.try_set(c, v).unwrap_or_else(|e| panic!(..)) } /// new: same as above but non-panicking fn try_set(&mut self, c: ::Column, v: Value) -> Result<(), DbErr>; } ``` * `Linked` can now be used in partial select, in case `Related` cannot be defined ```rust pub struct ToBakery; impl Linked for ToBakery { type FromEntity = super::cake::Entity; type ToEntity = super::bakery::Entity; fn link(&self) -> Vec { vec![Relation::Bakery.def()] } } #[derive(Debug, DerivePartialModel)] #[sea_orm(entity = "cake::Entity", into_active_model)] struct Cake2 { id: i32, name: String, #[sea_orm(nested, alias = "r0")] bakery: Option, #[sea_orm(skip)] ignore: Ignore, } let cake2: Cake2 = cake::Entity::find() .left_join_linked(ToBakery) .order_by_asc(cake::Column::Id) .into_partial_model() .one(&ctx.db) .await? .unwrap(); ``` * `RelationDef` now implements `Clone`. `on_condition` is changed to `Arc` but this is a minor breaking change. * Added `extra` on column attribute: ```rust #[cfg(feature = "with-rust_decimal")] #[sea_orm(extra = "CHECK (price > 0)")] pub price: Decimal, // results in: ColumnDef::new("price") .decimal() .not_null() .extra("CHECK (price > 0)"), ``` * Added `ColumnTrait::avg`, in addition to `sum`, `min`, `max` etc ```rust let average: Decimal = order::Entity::find() .select_only() .column_as(order::Column::Total.avg(), "avg") .into_tuple() .one(&ctx.db) .await? .unwrap(); ``` * `SchemaBuilder::sync` can now be used in migrations ```rust #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); db.get_schema_builder() .register(note::Entity) .sync(db) .await } } ``` * Allowed None for `max_lifetime` and `idle_timeout` Parameters https://github.com/SeaQL/sea-orm/pull/2748 * Try to parse `u32` in Postgres as `i32` https://github.com/SeaQL/sea-orm/pull/2753 * `DeriveActiveEnum` now also impl `IntoActiveValue` https://github.com/SeaQL/sea-orm/issues/1972 * `DeriveValueType` now also supports any structs that can be converted to / from string https://github.com/SeaQL/sea-orm/issues/2811 ```rust #[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)] #[sea_orm(value_type = "String")] pub struct Tag3 { pub i: i64, } impl std::fmt::Display for Tag3 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.i) } } impl std::str::FromStr for Tag3 { type Err = std::num::ParseIntError; fn from_str(s: &str) -> Result { let i: i64 = s.parse()?; Ok(Self { i }) } } ``` * Fix `DeriveIntoActiveModel` on `Option` fields https://github.com/SeaQL/sea-orm/pull/2926 ```rust #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "fruit")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub cake_id: Option, } #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "::ActiveModel")] struct PartialFruit { cake_id: Option, } assert_eq!( PartialFruit { cake_id: Some(1) }.into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, cake_id: Set(Some(1)) } ); assert_eq!( PartialFruit { cake_id: None }.into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, cake_id: NotSet } ); ``` * `FromQueryResult` now supports nullable nested model https://github.com/SeaQL/sea-orm/pull/2845 ```rust #[derive(FromQueryResult)] struct CakeWithOptionalBakeryModel { #[sea_orm(alias = "cake_id")] id: i32, #[sea_orm(alias = "cake_name")] name: String, #[sea_orm(nested)] bakery: Option, // can be null } ``` * Added `try_from_u64` to `DeriveValueType` https://github.com/SeaQL/sea-orm/pull/2958 ```rust // Test for try_from_u64 attribute with type alias type UserId = i32; #[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] #[sea_orm(try_from_u64)] pub struct MyUserId(pub UserId); ``` * Arrow / Parquet support https://github.com/SeaQL/sea-orm/pull/2957 + Added `ArrowSchema`, `DeriveArrowSchema` + Support decimal with different formats + Support timestamp with different timezone / resolution + Added parquet example ### Breaking Changes Please read [SeaQuery's breaking changes](https://github.com/SeaQL/sea-query/blob/master/CHANGELOG.md#breaking-changes) as well. But for most compile errors, you can simply add `use sea_orm::ExprTrait;` in scope. ```rust error[E0599]: no method named `like` found for enum `sea_query::Expr` in the current scope | | Expr::col((self.entity_name(), *self)).like(s) | | fn like(self, like: L) -> Expr | ---- the method is available for `sea_query::Expr` here | = help: items from traits can only be used if the trait is in scope help: trait `ExprTrait` which provides `like` is implemented but not in scope; perhaps you want to import it | -> + use sea_orm::ExprTrait; ``` ```rust error[E0308]: mismatched types --> src/sqlite/discovery.rs:27:57 | | .and_where(Expr::col(Alias::new("type")).eq("table")) | -- ^^^^^^^ expected `&Expr`, found `&str` | | | arguments to this method are incorrect | = note: expected reference `&sea_query::Expr` found reference `&'static str` ``` ```rust error[E0308]: mismatched types | 390 | Some(Expr::col(Name).eq(PgFunc::any(query.symbol))) | -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&Expr`, found `FunctionCall` | | | arguments to this method are incorrect | note: method defined here --> /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/core/src/cmp.rs:254:8 ``` ```rust error[E0277]: the trait bound `sea_orm::Condition: From` is not satisfied | 367 | .add_option(option) | ---------- ^^^^^^ the trait `From` is not implemented for `sea_orm::Condition` | | | required by a bound introduced by this call | = note: required for `bool` to implement `Into` ``` * Removed `runtime-actix` feature flag. It's been an alias of `runtime-tokio` for more than a year, so there should be no impact. * Enabled `sqlite-use-returning-for-3_35` by default. SQLite `3.35` was released in 2021, it should be the default by now. * Now implemented `impl PartialModelTrait for T`, there may be a potential conflict https://github.com/SeaQL/sea-orm/pull/2642 * Now `DeriveValueType` will also `TryFromU64` if applicable, there may be a potential conflict https://github.com/SeaQL/sea-orm/pull/2643 * Now `DeriveValueType` also impl `IntoActiveValue` and `NotU8`, there may be a potential conflict * Added `TryIntoModel` and `Serialize` to trait bounds of `ActiveModel::from_json`. There should be no impact if your models are derived with `DeriveEntityModel` https://github.com/SeaQL/sea-orm/pull/2599 ```rust fn from_json(mut json: serde_json::Value) -> Result where Self: TryIntoModel<::Model>, <::Entity as EntityTrait>::Model: IntoActiveModel, for<'de> <::Entity as EntityTrait>::Model: serde::de::Deserialize<'de> + serde::Serialize, ``` * `DerivePartialModel` now implement `FromQueryResult` by default, so there may be a potential conflict. Remove `FromQueryResult` in these cases https://github.com/SeaQL/sea-orm/pull/2653 ```rust error[E0119]: conflicting implementations of trait `sea_orm::FromQueryResult` for type `CakeWithFruit` | > | #[derive(DerivePartialModel, FromQueryResult)] | ------------------ ^^^^^^^^^^^^^^^ conflicting implementation for `CakeWithFruit` ``` * Changed `IdenStatic` and `EntityName` definition https://github.com/SeaQL/sea-orm/pull/2667 ```rust trait IdenStatic { fn as_str(&self) -> &'static str; // added static lifetime } trait EntityName { fn table_name(&self) -> &'static str; // added static lifetime } ``` * Removed `DeriveCustomColumn` and `default_as_str` https://github.com/SeaQL/sea-orm/pull/2667 ```rust // This is no longer supported: #[derive(Copy, Clone, Debug, EnumIter, DeriveCustomColumn)] pub enum Column { Id, Name, } impl IdenStatic for Column { fn as_str(&self) -> &str { match self { Self::Name => "my_name", _ => self.default_as_str(), } } } // Do the following instead: #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, #[sea_orm(column_name = "my_name")] Name, } ``` * `execute`, `query_one`, `query_all`, `stream` now takes in SeaQuery statement instead of raw SQL statement. a new set of methods `execute_raw`, `query_one_raw`, `query_all_raw`, `stream_raw` is added https://github.com/SeaQL/sea-orm/pull/2657 ```rust --> src/executor/paginator.rs:53:38 | > | let rows = self.db.query_all(stmt).await?; | --------- ^^^^ expected `&_`, found `Statement` | | | arguments to this method are incorrect | = note: expected reference `&_` found struct `statement::Statement` ``` ```rust let backend = self.db.get_database_backend(); let stmt = backend.build(&query); // change to: let rows = self.db.query_all_raw(stmt).await?; // if the query is a SeaQuery statement, then just do this: let rows = self.db.query_all(&query).await?; // no need to build query ``` * `DatabaseConnection` is changed from enum to struct. The original enum is moved into `DatabaseConnection::inner`. The new enum is named `DatabaseConnectionType` https://github.com/SeaQL/sea-orm/pull/2671 ```rust error[E0599]: no associated item named `Disconnected` found for struct `db_connection::DatabaseConnection` in the current scope --> src/database/db_connection.rs:137:33 | > | pub struct DatabaseConnection { | ----------------------------- associated item `Disconnected` not found for this struct ... > | DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), | ^^^^^^^^^^^^ associated item not found in `DatabaseConnection` ``` ```rust match conn.inner { DatabaseConnectionType::Disconnected => (), _ => (), } ``` * `DeleteOne` and `UpdateOne` no longer implement `QueryFilter` and `QueryTrait` directly. Those implementations could expose an incomplete SQL query with an incomplete condition that touches too many records. To generate the right condition, we must make sure that the primary key is set on the input `ActiveModel`. If you need to access the generated SQL query, convert into `ValidatedDeleteOne`/`ValidatedUpdateOne` first. ```rust error[E0599]: no method named `build` found for struct `query::update::UpdateOne` in the current scope --> src/entity/column.rs:607:22 | > | / Update::one(active_model) > | | .build(DbBackend::Postgres) | | -^^^^^ method not found in `UpdateOne` | |_____________________| | ``` Call the `validate()` method: ```rust Update::one(active_model) + .validate()? .build(DbBackend::Postgres) ``` * Removed `DbBackend::get_query_builder()` because `QueryBuilder` is not longer object safe. ```rust - fn get_query_builder(&self) -> Box ``` * A number of methods has been removed from `SelectTwoMany`: `into_partial_model`, `into_json`, `stream`. These methods are same as those in `SelectTwo`. Please use `Cake::find().find_also_related(Fruit).into_json()` instead. * The `delete_by_id` method has changed to returning `DeleteOne` instead of `DeleteMany`. It doesn't change normal `exec` usage, but would change return type of `exec_with_returning` to `Option` ```rust fn delete_by_id(values: T) -> DeleteMany // old fn delete_by_id(values: T) -> ValidatedDeleteOne // new ``` * `DeriveActiveEnum` now also automatically impl `IntoActiveValue`, if you have a custom impl before, there would be a collision * `with-bigdecimal` is now removed from default features * `RuntimeErr::SqlxError` is now held in `Arc` to make `DbErr` clonable and smaller: ```rust pub enum RuntimeErr { SqlxError(Arc), ``` ### Upgrades * Upgraded Rust Edition to 2024 https://github.com/SeaQL/sea-orm/pull/2596 * Upgraded `strum` to `0.27` ## 1.1.19 - 2025-11-11 ### Enhancements * Add `find_linked_recursive` method to ModelTrait https://github.com/SeaQL/sea-orm/pull/2480 * Skip drop extension type in fresh https://github.com/SeaQL/sea-orm/pull/2716 ### Bug Fixes * Handle null values in `from_sqlx_*_row_to_proxy_row` functions https://github.com/SeaQL/sea-orm/pull/2744 ## 1.1.17 - 2025-10-09 ### New Features * Added `map_sqlx_mysql_opts`, `map_sqlx_postgres_opts`, `map_sqlx_sqlite_opts` to `ConnectOptions` https://github.com/SeaQL/sea-orm/pull/2731 ```rust let mut opt = ConnectOptions::new(url); opt.map_sqlx_postgres_opts(|pg_opt: PgConnectOptions| { pg_opt.ssl_mode(PgSslMode::Require) }); ``` * Added `mariadb-use-returning` to use returning syntax for MariaDB https://github.com/SeaQL/sea-orm/pull/2710 * Released `sea-orm-rocket` 0.6 https://github.com/SeaQL/sea-orm/pull/2732 ## 1.1.16 - 2025-09-11 ### Bug Fixes * Fix enum casting in DerivePartialModel https://github.com/SeaQL/sea-orm/pull/2719 https://github.com/SeaQL/sea-orm/pull/2720 ```rust #[derive(DerivePartialModel)] #[sea_orm(entity = "active_enum::Entity", from_query_result, alias = "zzz")] struct PartialWithEnumAndAlias { #[sea_orm(from_col = "tea")] foo: Option, } let sql = active_enum::Entity::find() .into_partial_model::() .into_statement(DbBackend::Postgres) .sql; assert_eq!( sql, r#"SELECT CAST("zzz"."tea" AS "text") AS "foo" FROM "public"."active_enum""#, ); ``` ### Enhancements * [sea-orm-cli] Use tokio (optional) instead of async-std https://github.com/SeaQL/sea-orm/pull/2721 ## 1.1.15 - 2025-08-31 ### Enhancements * Allow `DerivePartialModel` to have nested aliases https://github.com/SeaQL/sea-orm/pull/2686 ```rust #[derive(DerivePartialModel)] #[sea_orm(entity = "bakery::Entity", from_query_result)] struct Factory { id: i32, #[sea_orm(from_col = "name")] plant: String, } #[derive(DerivePartialModel)] #[sea_orm(entity = "cake::Entity", from_query_result)] struct CakeFactory { id: i32, name: String, #[sea_orm(nested, alias = "factory")] // <- new bakery: Option, } ``` * Add `ActiveModelTrait::try_set` https://github.com/SeaQL/sea-orm/pull/2706 ```rust fn set(&mut self, c: ::Column, v: Value); /// New: a non-panicking version of above fn try_set(&mut self, c: ::Column, v: Value) -> Result<(), DbErr>; ``` ### Bug Fixes * [sea-orm-cli] Fix compilation issue https://github.com/SeaQL/sea-orm/pull/2713 ## 1.1.14 - 2025-07-21 ### Enhancements * [sea-orm-cli] Mask sensitive ENV values https://github.com/SeaQL/sea-orm/pull/2658 ### Bug Fixes * `FromJsonQueryResult`: panic on serialization failures https://github.com/SeaQL/sea-orm/pull/2635 ```rust #[derive(Clone, Debug, PartialEq, Deserialize, FromJsonQueryResult)] pub struct NonSerializableStruct; impl Serialize for NonSerializableStruct { fn serialize(&self, _serializer: S) -> Result where S: Serializer, { Err(serde::ser::Error::custom( "intentionally failing serialization", )) } } let model = Model { json: Some(NonSerializableStruct), }; let _ = model.into_active_model().insert(&ctx.db).await; // panic here ``` ## 1.1.13 - 2025-06-29 ### New Features * [sea-orm-cli] New `--frontend-format` flag to generate entities in pure Rust https://github.com/SeaQL/sea-orm/pull/2631 ```rust // for example, below is the normal (compact) Entity: use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } // this is the generated frontend model, there is no SeaORM dependency: use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Model { #[serde(skip_deserializing)] pub id: i32, pub name: Option , } ``` ### Enhancements * Removed potential panics from `Loader` https://github.com/SeaQL/sea-orm/pull/2637 ## 1.1.12 - 2025-05-27 ### Enhancements * Make sea-orm-cli & sea-orm-migration dependencies optional https://github.com/SeaQL/sea-orm/pull/2367 * Relax TransactionError's trait bound for errors to allow `anyhow::Error` https://github.com/SeaQL/sea-orm/pull/2602 ### Bug Fixes * Include custom `column_name` in DeriveColumn `Column::from_str` impl https://github.com/SeaQL/sea-orm/pull/2603 ```rust #[derive(DeriveEntityModel)] pub struct Model { #[sea_orm(column_name = "lAsTnAmE")] last_name: String, } assert!(matches!(Column::from_str("lAsTnAmE").unwrap(), Column::LastName)); ``` ## 1.1.11 - 2025-05-07 ### Enhancements * Added `ActiveModelTrait::default_values` ```rust assert_eq!( fruit::ActiveModel::default_values(), fruit::ActiveModel { id: Set(0), name: Set("".into()), cake_id: Set(None), type_without_default: NotSet, }, ); ``` * Impl `IntoCondition` for `RelationDef` https://github.com/SeaQL/sea-orm/pull/2587 ```rust // This allows using `RelationDef` directly where sea-query expects an `IntoCondition` let query = Query::select() .from(fruit::Entity) .inner_join(cake::Entity, fruit::Relation::Cake.def()) .to_owned(); ``` * Loader: retain only unique key values in the query condition https://github.com/SeaQL/sea-orm/pull/2569 * Add proxy transaction impl https://github.com/SeaQL/sea-orm/pull/2573 * [sea-orm-cli] Fix `PgVector` codegen https://github.com/SeaQL/sea-orm/pull/2589 ### Bug fixes * Quote type properly in `AsEnum` casting https://github.com/SeaQL/sea-orm/pull/2570 ```rust assert_eq!( lunch_set::Entity::find() .select_only() .column(lunch_set::Column::Tea) .build(DbBackend::Postgres) .to_string(), r#"SELECT CAST("lunch_set"."tea" AS "text") FROM "lunch_set""# // "text" is now quoted; will work for "text"[] as well ); ``` * Fix unicode string enum https://github.com/SeaQL/sea-orm/pull/2218 ### Upgrades * Upgrade `heck` to `0.5` https://github.com/SeaQL/sea-orm/pull/2218 * Upgrade `sea-query` to `0.32.5` * Upgrade `sea-schema` to `0.16.2` ## 1.1.10 - 2025-04-14 ### Upgrades * Upgrade sqlx to 0.8.4 https://github.com/SeaQL/sea-orm/pull/2562 ## 1.1.9 - 2025-04-14 ### Enhancements * [sea-orm-macros] Use fully-qualified syntax for ActiveEnum associated type https://github.com/SeaQL/sea-orm/pull/2552 * Accept `LikeExpr` in `like` and `not_like` https://github.com/SeaQL/sea-orm/pull/2549 ### Bug fixes * Check if url is well-formed before parsing https://github.com/SeaQL/sea-orm/pull/2558 * `QuerySelect::column_as` method cast ActiveEnum column https://github.com/SeaQL/sea-orm/pull/2551 ### House keeping * Remove redundant `Expr::expr` from internal code https://github.com/SeaQL/sea-orm/pull/2554 ## 1.1.8 - 2025-03-30 ### New Features * Implement `DeriveValueType` for enum strings ```rust #[derive(DeriveValueType)] #[sea_orm(value_type = "String")] pub enum Tag { Hard, Soft, } // `from_str` defaults to `std::str::FromStr::from_str` impl std::str::FromStr for Tag { type Err = sea_orm::sea_query::ValueTypeErr; fn from_str(s: &str) -> Result { .. } } // `to_str` defaults to `std::string::ToString::to_string`. impl std::fmt::Display for Tag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. } } // you can override from_str and to_str with custom functions #[derive(DeriveValueType)] #[sea_orm(value_type = "String", from_str = "Tag::from_str", to_str = "Tag::to_str")] pub enum Tag { Color, Grey, } impl Tag { fn from_str(s: &str) -> Result { .. } fn to_str(&self) -> &'static str { .. } } ``` * Support Postgres Ipnetwork (under feature flag `with-ipnetwork`) https://github.com/SeaQL/sea-orm/pull/2395 ```rust // Model #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "host_network")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub ipaddress: IpNetwork, #[sea_orm(column_type = "Cidr")] pub network: IpNetwork, } // Schema sea_query::Table::create() .table(host_network::Entity) .col(ColumnDef::new(host_network::Column::Id).integer().not_null().auto_increment().primary_key()) .col(ColumnDef::new(host_network::Column::Ipaddress).inet().not_null()) .col(ColumnDef::new(host_network::Column::Network).cidr().not_null()) .to_owned(); // CRUD host_network::ActiveModel { ipaddress: Set(IpNetwork::new(Ipv6Addr::new(..))), network: Set(IpNetwork::new(Ipv4Addr::new(..))), ..Default::default() } ``` ### Enhancements * Added `try_getable_postgres_array!(Vec)` (to support `bytea[]`) https://github.com/SeaQL/sea-orm/pull/2503 ### Bug fixes * [sea-orm-codegen] Support postgres array in expanded format https://github.com/SeaQL/sea-orm/pull/2545 ### House keeping * Replace `once_cell` crate with `std` equivalent https://github.com/SeaQL/sea-orm/pull/2524 (available since rust 1.80) ## 1.1.7 - 2025-03-02 ### New Features * Support nested entities in `FromQueryResult` https://github.com/SeaQL/sea-orm/pull/2508 ```rust #[derive(FromQueryResult)] struct Cake { id: i32, name: String, #[sea_orm(nested)] bakery: Option, } #[derive(FromQueryResult)] struct CakeBakery { #[sea_orm(from_alias = "bakery_id")] id: i32, #[sea_orm(from_alias = "bakery_name")] title: String, } let cake: Cake = cake::Entity::find() .select_only() .column(cake::Column::Id) .column(cake::Column::Name) .column_as(bakery::Column::Id, "bakery_id") .column_as(bakery::Column::Name, "bakery_name") .left_join(bakery::Entity) .order_by_asc(cake::Column::Id) .into_model() .one(&ctx.db) .await? .unwrap(); assert_eq!( cake, Cake { id: 1, name: "Cake".to_string(), bakery: Some(CakeBakery { id: 20, title: "Bakery".to_string(), }) } ); ``` * Support nested entities in `DerivePartialModel` https://github.com/SeaQL/sea-orm/pull/2508 ```rust #[derive(DerivePartialModel)] // FromQueryResult is no longer needed #[sea_orm(entity = "cake::Entity", from_query_result)] struct Cake { id: i32, name: String, #[sea_orm(nested)] bakery: Option, } #[derive(DerivePartialModel)] #[sea_orm(entity = "bakery::Entity", from_query_result)] struct Bakery { id: i32, #[sea_orm(from_col = "Name")] title: String, } // same as previous example, but without the custom selects let cake: Cake = cake::Entity::find() .left_join(bakery::Entity) .order_by_asc(cake::Column::Id) .into_partial_model() .one(&ctx.db) .await? .unwrap(); assert_eq!( cake, Cake { id: 1, name: "Cake".to_string(), bakery: Some(CakeBakery { id: 20, title: "Bakery".to_string(), }) } ); ``` * Derive also `IntoActiveModel` with `DerivePartialModel` https://github.com/SeaQL/sea-orm/pull/2517 ```rust #[derive(DerivePartialModel)] #[sea_orm(entity = "cake::Entity", into_active_model)] struct Cake { id: i32, name: String, } assert_eq!( Cake { id: 12, name: "Lemon Drizzle".to_owned(), } .into_active_model(), cake::ActiveModel { id: Set(12), name: Set("Lemon Drizzle".to_owned()), ..Default::default() } ); ``` * Added `SelectThree` https://github.com/SeaQL/sea-orm/pull/2518 ```rust // Order -> (many) Lineitem -> Cake let items: Vec<(order::Model, Option, Option)> = order::Entity::find() .find_also_related(lineitem::Entity) .and_also_related(cake::Entity) .order_by_asc(order::Column::Id) .order_by_asc(lineitem::Column::Id) .all(&ctx.db) .await?; ``` ### Enhancements * Support complex type path in `DeriveIntoActiveModel` https://github.com/SeaQL/sea-orm/pull/2517 ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "::ActiveModel")] struct Fruit { cake_id: Option>, } ``` * Added `DatabaseConnection::close_by_ref` https://github.com/SeaQL/sea-orm/pull/2511 ```rust pub async fn close(self) -> Result<(), DbErr> { .. } // existing pub async fn close_by_ref(&self) -> Result<(), DbErr> { .. } // new ``` ### House Keeping * Cleanup legacy `ActiveValue::Set` https://github.com/SeaQL/sea-orm/pull/2515 ## 1.1.6 - 2025-02-24 ### New Features * Support PgVector (under feature flag `postgres-vector`) https://github.com/SeaQL/sea-orm/pull/2500 ```rust // Model #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "image_model")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: i32, pub embedding: PgVector, } // Schema sea_query::Table::create() .table(image_model::Entity.table_ref()) .col(ColumnDef::new(Column::Id).integer().not_null().primary_key()) .col(ColumnDef::new(Column::Embedding).vector(None).not_null()) .. // Insert ActiveModel { id: NotSet, embedding: Set(PgVector::from(vec![1., 2., 3.])), } .insert(db) .await? ``` * Added `Insert::exec_with_returning_keys` & `Insert::exec_with_returning_many` (Postgres only) ```rust assert_eq!( Entity::insert_many([ ActiveModel { id: NotSet, name: Set("two".into()) }, ActiveModel { id: NotSet, name: Set("three".into()) }, ]) .exec_with_returning_many(db) .await .unwrap(), [ Model { id: 2, name: "two".into() }, Model { id: 3, name: "three".into() }, ] ); assert_eq!( cakes_bakers::Entity::insert_many([ cakes_bakers::ActiveModel { cake_id: Set(1), baker_id: Set(2), }, cakes_bakers::ActiveModel { cake_id: Set(2), baker_id: Set(1), }, ]) .exec_with_returning_keys(db) .await .unwrap(), [(1, 2), (2, 1)] ); ``` * Added `DeleteOne::exec_with_returning` & `DeleteMany::exec_with_returning` https://github.com/SeaQL/sea-orm/pull/2432 ### Enhancements * Expose underlying row types (e.g. `sqlx::postgres::PgRow`) https://github.com/SeaQL/sea-orm/pull/2265 * [sea-orm-cli] Added `acquire-timeout` option https://github.com/SeaQL/sea-orm/pull/2461 * [sea-orm-cli] Added `with-prelude` option https://github.com/SeaQL/sea-orm/pull/2322 * [sea-orm-cli] Added `impl-active-model-behavior` option https://github.com/SeaQL/sea-orm/pull/2487 ### Bug Fixes * Fixed `seaography::register_active_enums` macro https://github.com/SeaQL/sea-orm/pull/2475 ### House keeping * Remove `futures` crate, replace with `futures-util` https://github.com/SeaQL/sea-orm/pull/2466 ## 1.1.5 - 2025-02-14 ### New Features * Added `Schema::json_schema_from_entity` to construct a schema description in json for the given Entity ## 1.1.4 - 2025-01-10 ### Enhancements * Allow modifying the connection in migrations https://github.com/SeaQL/sea-orm/pull/2397 * `DeriveRelatedEntity` proc_macro use `async-graphql` re-exported by `seaography` https://github.com/SeaQL/sea-orm/pull/2469 ## 1.1.3 - 2024-12-24 ### New Features * [sea-orm-codegen] register seaography entity modules & active enums https://github.com/SeaQL/sea-orm/pull/2403 ```rust pub mod prelude; pub mod sea_orm_active_enums; pub mod baker; pub mod bakery; pub mod cake; pub mod cakes_bakers; pub mod customer; pub mod lineitem; pub mod order; seaography::register_entity_modules!([ baker, bakery, cake, cakes_bakers, customer, lineitem, order, ]); seaography::register_active_enums!([ sea_orm_active_enums::Tea, sea_orm_active_enums::Color, ]); ``` ### Enhancements * Insert many allow active models to have different column set https://github.com/SeaQL/sea-orm/pull/2433 ```rust // this previously panics let apple = cake_filling::ActiveModel { cake_id: ActiveValue::set(2), filling_id: ActiveValue::NotSet, }; let orange = cake_filling::ActiveModel { cake_id: ActiveValue::NotSet, filling_id: ActiveValue::set(3), }; assert_eq!( Insert::::new() .add_many([apple, orange]) .build(DbBackend::Postgres) .to_string(), r#"INSERT INTO "cake_filling" ("cake_id", "filling_id") VALUES (2, NULL), (NULL, 3)"#, ); ``` * [sea-orm-cli] Added `MIGRATION_DIR` environment variable https://github.com/SeaQL/sea-orm/pull/2419 * Added `ColumnDef::is_unique` https://github.com/SeaQL/sea-orm/pull/2401 * Postgres: quote schema in `search_path` https://github.com/SeaQL/sea-orm/pull/2436 ### Bug Fixes * MySQL: fix transaction isolation level not respected when used with access mode https://github.com/SeaQL/sea-orm/pull/2450 ## 1.1.2 - 2024-12-02 ### Enhancements * Added `ColumnTrait::enum_type_name()` to signify enum types https://github.com/SeaQL/sea-orm/pull/2415 * Added `DbBackend::boolean_value()` for database dependent boolean value https://github.com/SeaQL/sea-orm/pull/2415 ## 1.1.1 - 2024-11-04 ### Enhancements * [sea-orm-macros] `impl From for ActiveModel` instead of `impl From<::Model> for ActiveModel` https://github.com/SeaQL/sea-orm/pull/2349. Now the following can compile: ```rust use sea_orm::{tests_cfg::cake, Set}; struct Cake { id: i32, name: String, } impl From for cake::ActiveModel { fn from(value: Cake) -> Self { Self { id: Set(value.id), name: Set(value.name), } } } ``` ## 1.1.0 - 2024-10-15 ### Versions + `1.1.0-rc.1`: 2024-08-09 + `1.1.0-rc.2`: 2024-10-04 + `1.1.0-rc.3`: 2024-10-08 ### Enhancements * [sea-orm-macros] Call `EnumIter::get` using fully qualified syntax https://github.com/SeaQL/sea-orm/pull/2321 * Construct `DatabaseConnection` directly from `sqlx::PgPool`, `sqlx::SqlitePool` and `sqlx::MySqlPool` https://github.com/SeaQL/sea-orm/pull/2348 * [sea-orm-migration] Add `pk_uuid` schema helper https://github.com/SeaQL/sea-orm/pull/2329 * [sea-orm-migration] Allow `custom` and `custom_null` schema helper to take column name and alias of different `IntoIden` types https://github.com/SeaQL/sea-orm/pull/2326 * Add `ColumnDef::get_column_default` getter https://github.com/SeaQL/sea-orm/pull/2387 ### Upgrades * Upgrade `sqlx` to `0.8.2` https://github.com/SeaQL/sea-orm/pull/2305, https://github.com/SeaQL/sea-orm/pull/2371 * Upgrade `bigdecimal` to `0.4` https://github.com/SeaQL/sea-orm/pull/2305 * Upgrade `sea-query` to `0.32.0-rc` https://github.com/SeaQL/sea-orm/pull/2305 * Upgrade `sea-query-binder` to `0.7.0-rc` https://github.com/SeaQL/sea-orm/pull/2305 * Upgrade `sea-schema` to `0.16.0-rc` https://github.com/SeaQL/sea-orm/pull/2305 * Upgrade `ouroboros` to `0.18` https://github.com/SeaQL/sea-orm/pull/2353 ### House keeping * Fix typos https://github.com/SeaQL/sea-orm/pull/2360 * Update documentations https://github.com/SeaQL/sea-orm/pull/2345 ## 1.0.1 - 2024-08-26 ### New Features * Added `ConnectOptions::connect_lazy` for creating DB connection pools without establishing connections up front https://github.com/SeaQL/sea-orm/pull/2268 ### Breaking Changes * Changed `ProxyDatabaseTrait` methods to async. It's a breaking change, but it should have been part of the 1.0 release. The feature is behind the feature guard `proxy`, and we believe it shouldn't impact majority of users. https://github.com/SeaQL/sea-orm/pull/2278 ### Bug Fixes * [sea-orm-codegen] Fix `ColumnType` to Rust type resolution https://github.com/SeaQL/sea-orm/pull/2313 ## 1.0.0 - 2024-08-02 ### Versions + `1.0.0-rc.1`: 2024-02-06 + `1.0.0-rc.2`: 2024-03-15 + `1.0.0-rc.3`: 2024-03-26 + `1.0.0-rc.4`: 2024-05-13 + `1.0.0-rc.5`: 2024-05-29 + `1.0.0-rc.6`: 2024-06-19 + `1.0.0-rc.7`: 2024-06-25 ### New Features * Introduce `PrimaryKeyArity` with `ARITY` constant https://github.com/SeaQL/sea-orm/pull/2185 ```rust fn get_arity_of() -> usize { E::PrimaryKey::iter().count() // before; runtime <::ValueType as PrimaryKeyArity>::ARITY // now; compile-time } ``` * Associate `ActiveModel` to `EntityTrait` https://github.com/SeaQL/sea-orm/pull/2186 * [sea-orm-macros] Added `rename_all` attribute to `DeriveEntityModel` & `DeriveActiveEnum` https://github.com/SeaQL/sea-orm/pull/2170 ```rust #[derive(DeriveEntityModel)] #[sea_orm(table_name = "user", rename_all = "camelCase")] pub struct Model { #[sea_orm(primary_key)] id: i32, first_name: String, // firstName #[sea_orm(column_name = "lAsTnAmE")] last_name: String, // lAsTnAmE } #[derive(EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "camelCase")] pub enum TestEnum { DefaultVariant, // defaultVariant #[sea_orm(rename = "kebab-case")] VariantKebabCase, // variant-kebab-case #[sea_orm(rename = "snake_case")] VariantSnakeCase, // variant_snake_case #[sea_orm(string_value = "CuStOmStRiNgVaLuE")] CustomStringValue, // CuStOmStRiNgVaLuE } ``` * [sea-orm-migration] schema helper https://github.com/SeaQL/sea-orm/pull/2099 ```rust // Remember to import `sea_orm_migration::schema::*` use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table(Users::Table) .if_not_exists() .col(pk_auto(Users::Id)) // Primary key with auto-increment .col(uuid(Users::Pid)) // UUID column .col(string_uniq(Users::Email)) // String column with unique constraint .col(string(Users::Password)) // String column .col(string(Users::ApiKey).unique_key()) .col(string(Users::Name)) .col(string_null(Users::ResetToken)) // Nullable string column .col(timestamp_null(Users::ResetSentAt)) // Nullable timestamp column .col(string_null(Users::EmailVerificationToken)) .col(timestamp_null(Users::EmailVerificationSentAt)) .col(timestamp_null(Users::EmailVerifiedAt)) .to_owned(), ) .await } // ... } ``` ### Enhancements * Added non-TLS runtime https://github.com/SeaQL/sea-orm/pull/2256 * Added `QuerySelect::tbl_col_as` * Added `Insert::on_conflict_do_nothing` https://github.com/SeaQL/sea-orm/pull/2244 * Migration schema nullable column set NULL explicitly https://github.com/SeaQL/sea-orm/pull/2255 * Added `ActiveValue::set_if_not_equals()` https://github.com/SeaQL/sea-orm/pull/2194 * Added `ActiveValue::try_as_ref()` https://github.com/SeaQL/sea-orm/pull/2197 * Added `QuerySelect::order_by_with_nulls` https://github.com/SeaQL/sea-orm/pull/2228 * Expose `get_xxx_connection_pool` by default https://github.com/SeaQL/sea-orm/pull/2233 * Added `QueryResult::column_names` https://github.com/SeaQL/sea-orm/pull/2148 * [sea-orm-macro] Add `@generated` in generated code https://github.com/SeaQL/sea-orm/pull/2199 * [sea-orm-macro] Qualify traits in `DeriveActiveModel` macro https://github.com/SeaQL/sea-orm/pull/1665 * [sea-orm-cli] Fix `migrate generate` on empty `mod.rs` files https://github.com/SeaQL/sea-orm/pull/2064 * `DerivePartialModel` macro attribute `entity` now supports `syn::Type` https://github.com/SeaQL/sea-orm/pull/2137 ```rust #[derive(DerivePartialModel)] #[sea_orm(entity = "::Entity")] struct EntityNameNotAIdent { #[sea_orm(from_col = "foo2")] _foo: i32, #[sea_orm(from_col = "bar2")] _bar: String, } ``` * Added `RelationDef::from_alias()` https://github.com/SeaQL/sea-orm/pull/2146 ```rust let cf = Alias::new("cf"); assert_eq!( cake::Entity::find() .join_as( JoinType::LeftJoin, cake_filling::Relation::Cake.def().rev(), cf.clone() ) .join( JoinType::LeftJoin, cake_filling::Relation::Filling.def().from_alias(cf) ) .build(DbBackend::MySql) .to_string(), [ "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", "LEFT JOIN `cake_filling` AS `cf` ON `cake`.`id` = `cf`.`cake_id`", "LEFT JOIN `filling` ON `cf`.`filling_id` = `filling`.`id`", ] .join(" ") ); ``` ### Bug Fixes * Set schema search path in Postgres without enclosing single quote https://github.com/SeaQL/sea-orm/pull/2241 * [sea-orm-cli] Generate `has_one` relation for foreign key of unique index / constraint https://github.com/SeaQL/sea-orm/pull/2254 ### Breaking changes * Renamed `ConnectOptions::pool_options()` to `ConnectOptions::sqlx_pool_options()` https://github.com/SeaQL/sea-orm/pull/2145 * Made `sqlx_common` private, hiding `sqlx_error_to_xxx_err` https://github.com/SeaQL/sea-orm/pull/2145 * Rework SQLite type mappings https://github.com/SeaQL/sea-orm/pull/2077, https://github.com/SeaQL/sea-orm/pull/2078 ### Upgrades * Upgrade `time` to `0.3.36` https://github.com/SeaQL/sea-orm/pull/2267 * Upgrade `strum` to `0.26` https://github.com/SeaQL/sea-orm/pull/2088 * Upgrade `sea-schema` to `0.15.0` * Upgrade `sea-query-binder` to `0.6.0` * Upgrade `sea-query` to `0.31.0` ### House keeping * Reduce warnings in integration tests https://github.com/SeaQL/sea-orm/pull/2177 * Improved Actix example to return 404 not found on unexpected inputs https://github.com/SeaQL/sea-orm/pull/2140 * Re-enable `rocket_okapi` example https://github.com/SeaQL/sea-orm/pull/2136 ## 1.0.0-rc.7 - 2024-06-25 ### Upgrades * Upgrade `sea-query-binder` to `0.6.0-rc.4` https://github.com/SeaQL/sea-orm/pull/2267 * Upgrade `time` to `0.3.36` https://github.com/SeaQL/sea-orm/pull/2267 ## 1.0.0-rc.6 - 2024-06-19 ### Enhancements * Added non-TLS runtime https://github.com/SeaQL/sea-orm/pull/2256 * Added `QuerySelect::tbl_col_as` * Added `Insert::on_conflict_do_nothing` https://github.com/SeaQL/sea-orm/pull/2244 * Migration schema nullable column set NULL explicitly https://github.com/SeaQL/sea-orm/pull/2255 ### Bug Fixes * Set schema search path in Postgres without enclosing single quote https://github.com/SeaQL/sea-orm/pull/2241 * [sea-orm-cli] Generate `has_one` relation for foreign key of unique index / constraint https://github.com/SeaQL/sea-orm/pull/2254 ## 1.0.0-rc.5 - 2024-05-29 ### New Features * Introduce `PrimaryKeyArity` with `ARITY` constant https://github.com/SeaQL/sea-orm/pull/2185 ```rust fn get_arity_of() -> usize { E::PrimaryKey::iter().count() // before; runtime <::ValueType as PrimaryKeyArity>::ARITY // now; compile-time } ``` * Associate `ActiveModel` to `EntityTrait` https://github.com/SeaQL/sea-orm/pull/2186 * [sea-orm-macros] Added `rename_all` attribute to `DeriveEntityModel` & `DeriveActiveEnum` https://github.com/SeaQL/sea-orm/pull/2170 ```rust #[derive(DeriveEntityModel)] #[sea_orm(table_name = "user", rename_all = "camelCase")] pub struct Model { #[sea_orm(primary_key)] id: i32, first_name: String, // firstName #[sea_orm(column_name = "lAsTnAmE")] last_name: String, // lAsTnAmE } #[derive(EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "camelCase")] pub enum TestEnum { DefaultVariant, // defaultVariant #[sea_orm(rename = "kebab-case")] VariantKebabCase, // variant-kebab-case #[sea_orm(rename = "snake_case")] VariantSnakeCase, // variant_snake_case #[sea_orm(string_value = "CuStOmStRiNgVaLuE")] CustomStringValue, // CuStOmStRiNgVaLuE } ``` ### Enhancements * Added `ActiveValue::set_if_not_equals()` https://github.com/SeaQL/sea-orm/pull/2194 * Added `ActiveValue::try_as_ref()` https://github.com/SeaQL/sea-orm/pull/2197 * Added `QuerySelect::order_by_with_nulls` https://github.com/SeaQL/sea-orm/pull/2228 * Expose `get_xxx_connection_pool` by default https://github.com/SeaQL/sea-orm/pull/2233 ## 1.0.0-rc.4 - 2024-05-13 ### Enhancements * Added `QueryResult::column_names` https://github.com/SeaQL/sea-orm/pull/2148 * [sea-orm-macro] Add `@generated` in generated code https://github.com/SeaQL/sea-orm/pull/2199 ### Upgrades * Upgrade `sea-query` to `0.31.0-rc.6` * Upgrade `sea-schema` to `0.15.0-rc.6` ### House Keeping * Reduce warnings in integration tests https://github.com/SeaQL/sea-orm/pull/2177 ## 1.0.0-rc.3 - 2024-03-26 ### Enhancements * [sea-orm-macro] Qualify traits in `DeriveActiveModel` macro https://github.com/SeaQL/sea-orm/pull/1665 ## 1.0.0-rc.2 - 2024-03-15 ### Breaking Changes * Renamed `ConnectOptions::pool_options()` to `ConnectOptions::sqlx_pool_options()` https://github.com/SeaQL/sea-orm/pull/2145 * Made `sqlx_common` private, hiding `sqlx_error_to_xxx_err` https://github.com/SeaQL/sea-orm/pull/2145 ### Enhancements * [sea-orm-cli] Fix `migrate generate` on empty `mod.rs` files https://github.com/SeaQL/sea-orm/pull/2064 * `DerivePartialModel` macro attribute `entity` now supports `syn::Type` https://github.com/SeaQL/sea-orm/pull/2137 ```rust #[derive(DerivePartialModel)] #[sea_orm(entity = "::Entity")] struct EntityNameNotAIdent { #[sea_orm(from_col = "foo2")] _foo: i32, #[sea_orm(from_col = "bar2")] _bar: String, } ``` * Added `RelationDef::from_alias()` https://github.com/SeaQL/sea-orm/pull/2146 ```rust let cf = Alias::new("cf"); assert_eq!( cake::Entity::find() .join_as( JoinType::LeftJoin, cake_filling::Relation::Cake.def().rev(), cf.clone() ) .join( JoinType::LeftJoin, cake_filling::Relation::Filling.def().from_alias(cf) ) .build(DbBackend::MySql) .to_string(), [ "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", "LEFT JOIN `cake_filling` AS `cf` ON `cake`.`id` = `cf`.`cake_id`", "LEFT JOIN `filling` ON `cf`.`filling_id` = `filling`.`id`", ] .join(" ") ); ``` ### Upgrades * Upgrade `sea-schema` to `0.15.0-rc.3` * Upgrade `strum` to `0.26` https://github.com/SeaQL/sea-orm/pull/2088 ### House keeping * Improved Actix example to return 404 not found on unexpected inputs https://github.com/SeaQL/sea-orm/pull/2140 * Re-enable `rocket_okapi` example https://github.com/SeaQL/sea-orm/pull/2136 ## 1.0.0-rc.1 - 2024-02-06 ### New Features * [sea-orm-migration] schema helper https://github.com/SeaQL/sea-orm/pull/2099 ```rust // Remember to import `sea_orm_migration::schema::*` use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table(Users::Table) .if_not_exists() .col(pk_auto(Users::Id)) // Primary key with auto-increment .col(uuid(Users::Pid)) // UUID column .col(string_uniq(Users::Email)) // String column with unique constraint .col(string(Users::Password)) // String column .col(string(Users::ApiKey).unique_key()) .col(string(Users::Name)) .col(string_null(Users::ResetToken)) // Nullable string column .col(timestamp_null(Users::ResetSentAt)) // Nullable timestamp column .col(string_null(Users::EmailVerificationToken)) .col(timestamp_null(Users::EmailVerificationSentAt)) .col(timestamp_null(Users::EmailVerifiedAt)) .to_owned(), ) .await } // ... } ``` ### Breaking Changes * Rework SQLite type mappings https://github.com/SeaQL/sea-orm/pull/2077, https://github.com/SeaQL/sea-orm/pull/2078 * Updated `sea-query` to `0.31` ## 0.12.14 - 2024-02-05 * Added feature flag `sqlite-use-returning-for-3_35` to use SQLite's returning https://github.com/SeaQL/sea-orm/pull/2070 * Added Loco example https://github.com/SeaQL/sea-orm/pull/2092 ## 0.12.12 - 2024-01-22 ### Bug Fixes * [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants https://github.com/SeaQL/sea-orm/pull/1821 * [sea-orm-cli] Fix entity generation for relations with composite keys https://github.com/SeaQL/sea-orm/pull/2071 ### Enhancements * Added `ConnectOptions::test_before_acquire` ## 0.12.11 - 2024-01-14 ### New Features * Added `desc` to `Cursor` paginator https://github.com/SeaQL/sea-orm/pull/2037 ### Enhancements * Improve query performance of `Paginator`'s `COUNT` query https://github.com/SeaQL/sea-orm/pull/2030 * Added SQLx slow statements logging to `ConnectOptions` https://github.com/SeaQL/sea-orm/pull/2055 * Added `QuerySelect::lock_with_behavior` https://github.com/SeaQL/sea-orm/pull/1867 ### Bug Fixes * [sea-orm-macro] Qualify types in `DeriveValueType` macro https://github.com/SeaQL/sea-orm/pull/2054 ### House keeping * Fix clippy warnings on 1.75 https://github.com/SeaQL/sea-orm/pull/2057 ## 0.12.10 - 2023-12-14 ### New Features * [sea-orm-macro] Comment attribute for Entity (`#[sea_orm(comment = "action")]`); `create_table_from_entity` supports comment https://github.com/SeaQL/sea-orm/pull/2009 * Added "proxy" (feature flag `proxy`) to database backend https://github.com/SeaQL/sea-orm/pull/1881, https://github.com/SeaQL/sea-orm/pull/2000 ### Enhancements * Cast enums in `is_in` and `is_not_in` https://github.com/SeaQL/sea-orm/pull/2002 ### Upgrades * Updated `sea-query` to `0.30.5` https://github.com/SeaQL/sea-query/releases/tag/0.30.5 ## 0.12.9 - 2023-12-08 ### Enhancements * Add source annotations to errors https://github.com/SeaQL/sea-orm/pull/1999 ### Upgrades * Updated `sea-query` to `0.30.4` https://github.com/SeaQL/sea-query/releases/tag/0.30.4 ## 0.12.8 - 2023-12-04 ### Enhancements * Implement `StatementBuilder` for `sea_query::WithQuery` https://github.com/SeaQL/sea-orm/issues/1960 ### Upgrades * Upgrade `axum` example to `0.7` https://github.com/SeaQL/sea-orm/pull/1984 ## 0.12.7 - 2023-11-22 ### Enhancements * Added method `expr_as_` that accepts `self` https://github.com/SeaQL/sea-orm/pull/1979 ### Upgrades * Updated `sea-query` to `0.30.3` https://github.com/SeaQL/sea-query/releases/tag/0.30.3 ## 0.12.6 - 2023-11-13 ### New Features * Added `#[sea_orm(skip)]` for `FromQueryResult` derive macro https://github.com/SeaQL/sea-orm/pull/1954 ## 0.12.5 - 2023-11-12 ### Bug Fixes * [sea-orm-cli] Fix duplicated active enum use statements on generated entities https://github.com/SeaQL/sea-orm/pull/1953 * [sea-orm-cli] Added `--enum-extra-derives` https://github.com/SeaQL/sea-orm/pull/1934 * [sea-orm-cli] Added `--enum-extra-attributes` https://github.com/SeaQL/sea-orm/pull/1952 ## 0.12.4 - 2023-10-19 ### New Features * Add support for root JSON arrays https://github.com/SeaQL/sea-orm/pull/1898 Now the following works (requires the `json-array` / `postgres-array` feature)! ```rust #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "json_struct_vec")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Json")] pub struct_vec: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] pub struct JsonColumn { pub value: String, } ``` ### Enhancements * Loader: use `ValueTuple` as hash key https://github.com/SeaQL/sea-orm/pull/1868 ### Upgrades * Updated `sea-query` to `0.30.2` https://github.com/SeaQL/sea-query/releases/tag/0.30.2 ## 0.12.3 - 2023-09-22 ### New Features * [sea-orm-migration] Check if an index exists https://github.com/SeaQL/sea-orm/pull/1828 * Added `cursor_by` to `SelectTwo` https://github.com/SeaQL/sea-orm/pull/1826 ### Enhancements * [sea-orm-cli] Support generation of related entity with composite foreign key https://github.com/SeaQL/sea-orm/pull/1693 ### Bug Fixes * [sea-orm-macro] Fixed `DeriveValueType` by qualifying `QueryResult` https://github.com/SeaQL/sea-orm/pull/1855 * Fixed `Loader` panic on empty inputs ### Upgrades * Upgraded `salvo` to `0.50` * Upgraded `chrono` to `0.4.30` https://github.com/SeaQL/sea-orm/pull/1858 * Updated `sea-query` to `0.30.1` * Updated `sea-schema` to `0.14.1` ### House keeping * Added test cases for `find_xxx_related/linked` https://github.com/SeaQL/sea-orm/pull/1811 ## 0.12.2 - 2023-08-04 ### Enhancements * Added support for Postgres arrays in `FromQueryResult` impl of `JsonValue` https://github.com/SeaQL/sea-orm/pull/1598 ### Bug fixes * Fixed `find_with_related` consolidation logic https://github.com/SeaQL/sea-orm/issues/1800 ## 0.12.1 - 2023-07-27 + `0.12.0-rc.1`: Yanked + `0.12.0-rc.2`: 2023-05-19 + `0.12.0-rc.3`: 2023-06-22 + `0.12.0-rc.4`: 2023-07-08 + `0.12.0-rc.5`: 2023-07-22 ### New Features * Added `MigratorTrait::migration_table_name()` method to configure the name of migration table https://github.com/SeaQL/sea-orm/pull/1511 ```rust #[async_trait::async_trait] impl MigratorTrait for Migrator { // Override the name of migration table fn migration_table_name() -> sea_orm::DynIden { Alias::new("override_migration_table_name").into_iden() } ... } ``` * Added option to construct chained AND / OR join on condition https://github.com/SeaQL/sea-orm/pull/1433 ```rust #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { // By default, it's // `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE '%tropical%'` #[sea_orm( has_many = "super::fruit::Entity", on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# )] TropicalFruit, // Or specify `condition_type = "any"` to override it, // `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` OR `fruit`.`name` LIKE '%tropical%'` #[sea_orm( has_many = "super::fruit::Entity", on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# condition_type = "any", )] OrTropicalFruit, } ``` * Supports entity with composite primary key of arity 12 https://github.com/SeaQL/sea-orm/pull/1508 * `Identity` supports tuple of `DynIden` with arity up to 12 ```rust #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "primary_key_of_12")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id_1: String, ... #[sea_orm(primary_key, auto_increment = false)] pub id_12: bool, } ``` * Added macro `DerivePartialModel` https://github.com/SeaQL/sea-orm/pull/1597 ```rust #[derive(DerivePartialModel, FromQueryResult)] #[sea_orm(entity = "Cake")] struct PartialCake { name: String, #[sea_orm( from_expr = r#"SimpleExpr::FunctionCall(Func::upper(Expr::col((Cake, cake::Column::Name))))"# )] name_upper: String, } assert_eq!( cake::Entity::find() .into_partial_model::() .into_statement(DbBackend::Sqlite) .to_string(), r#"SELECT "cake"."name", UPPER("cake"."name") AS "name_upper" FROM "cake""# ); ``` * Added `DbErr::sql_err()` method to convert error into common database errors `SqlErr`, such as unique constraint or foreign key violation errors. https://github.com/SeaQL/sea-orm/pull/1707 ```rust assert!(matches!( cake.into_active_model().insert(db).await .expect_err("Insert a row with duplicated primary key") .sql_err(), Some(SqlErr::UniqueConstraintViolation(_)) )); assert!(matches!( fk_cake.insert(db).await .expect_err("Insert a row with invalid foreign key") .sql_err(), Some(SqlErr::ForeignKeyConstraintViolation(_)) )); ``` * Added `Select::find_with_linked`, similar to `find_with_related`: https://github.com/SeaQL/sea-orm/pull/1728, https://github.com/SeaQL/sea-orm/pull/1743 ```rust fn find_with_related(self, r: R) -> SelectTwoMany where R: EntityTrait, E: Related; fn find_with_linked(self, l: L) -> SelectTwoMany where L: Linked, T: EntityTrait; // boths yields `Vec<(E::Model, Vec)>` ``` * Added `DeriveValueType` derive macro for custom wrapper types, implementations of the required traits will be provided, you can customize the `column_type` and `array_type` if needed https://github.com/SeaQL/sea-orm/pull/1720 ```rust #[derive(DeriveValueType)] #[sea_orm(array_type = "Int")] pub struct Integer(i32); #[derive(DeriveValueType)] #[sea_orm(column_type = "Boolean", array_type = "Bool")] pub struct Boolbean(pub String); #[derive(DeriveValueType)] pub struct StringVec(pub Vec); ``` * Added `DeriveDisplay` derive macro to implements `std::fmt::Display` for enum https://github.com/SeaQL/sea-orm/pull/1726 ```rust #[derive(DeriveDisplay)] enum DisplayTea { EverydayTea, #[sea_orm(display_value = "Breakfast Tea")] BreakfastTea, } assert_eq!(format!("{}", DisplayTea::EverydayTea), "EverydayTea"); assert_eq!(format!("{}", DisplayTea::BreakfastTea), "Breakfast Tea"); ``` * Added `UpdateMany::exec_with_returning()` https://github.com/SeaQL/sea-orm/pull/1677 ```rust let models: Vec = Entity::update_many() .col_expr(Column::Values, Expr::expr(..)) .exec_with_returning(db) .await?; ``` * Supporting `default_expr` in `DeriveEntityModel` https://github.com/SeaQL/sea-orm/pull/1474 ```rust #[derive(DeriveEntityModel)] #[sea_orm(table_name = "hello")] pub struct Model { #[sea_orm(default_expr = "Expr::current_timestamp()")] pub timestamp: DateTimeUtc, } assert_eq!( Column::Timestamp.def(), ColumnType::TimestampWithTimeZone.def() .default(Expr::current_timestamp()) ); ``` * Introduced new `ConnAcquireErr` https://github.com/SeaQL/sea-orm/pull/1737 ```rust enum DbErr { ConnectionAcquire(ConnAcquireErr), .. } enum ConnAcquireErr { Timeout, ConnectionClosed, } ``` #### Seaography Added Seaography integration https://github.com/SeaQL/sea-orm/pull/1599 * Added `DeriveEntityRelated` macro which will implement `seaography::RelationBuilder` for `RelatedEntity` enumeration when the `seaography` feature is enabled * Added generation of `seaography` related information to `sea-orm-codegen`. The `RelatedEntity` enum is added in entities files by `sea-orm-cli` when flag `seaography` is set: ```rust /// SeaORM Entity #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity { #[sea_orm(entity = "super::bakery::Entity")] Bakery, #[sea_orm(entity = "super::cake_baker::Entity")] CakeBaker, #[sea_orm(entity = "super::cake::Entity")] Cake, } ``` * Added [`seaography_example`](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) ### Enhancements * Supports for partial select of `Option` model field. A `None` value will be filled when the select result does not contain the `Option` field without throwing an error. https://github.com/SeaQL/sea-orm/pull/1513 * [sea-orm-cli] the `migrate init` command will create a `.gitignore` file when the migration folder reside in a Git repository https://github.com/SeaQL/sea-orm/pull/1334 * [sea-orm-cli] Added support for generating migration of space separated name, for example executing `sea-orm-cli migrate generate "create accounts table"` command will create `m20230503_000000_create_accounts_table.rs` for you https://github.com/SeaQL/sea-orm/pull/1570 * Added `Migration::name()` and `Migration::status()` getters for the name and status of `sea_orm_migration::Migration` https://github.com/SeaQL/sea-orm/pull/1519 ```rust let migrations = Migrator::get_pending_migrations(db).await?; assert_eq!(migrations.len(), 5); let migration = migrations.get(0).unwrap(); assert_eq!(migration.name(), "m20220118_000002_create_fruit_table"); assert_eq!(migration.status(), MigrationStatus::Pending); ``` * The `postgres-array` feature will be enabled when `sqlx-postgres` backend is selected https://github.com/SeaQL/sea-orm/pull/1565 * Replace `String` parameters in API with `Into` https://github.com/SeaQL/sea-orm/pull/1439 * Implements `IntoMockRow` for any `BTreeMap` that is indexed by string `impl IntoMockRow for BTreeMap where T: Into` * Converts any string value into `ConnectOptions` - `impl From for ConnectOptions where T: Into` * Changed the parameter of method `ConnectOptions::new(T) where T: Into` to takes any string SQL * Changed the parameter of method `Statement::from_string(DbBackend, T) where T: Into` to takes any string SQL * Changed the parameter of method `Statement::from_sql_and_values(DbBackend, T, I) where I: IntoIterator, T: Into` to takes any string SQL * Changed the parameter of method `Transaction::from_sql_and_values(DbBackend, T, I) where I: IntoIterator, T: Into` to takes any string SQL * Changed the parameter of method `ConnectOptions::set_schema_search_path(T) where T: Into` to takes any string * Changed the parameter of method `ColumnTrait::like()`, `ColumnTrait::not_like()`, `ColumnTrait::starts_with()`, `ColumnTrait::ends_with()` and `ColumnTrait::contains()` to takes any string * Added `sea_query::{DynIden, RcOrArc, SeaRc}` to entity prelude https://github.com/SeaQL/sea-orm/pull/1661 * Added `expr`, `exprs` and `expr_as` methods to `QuerySelect` trait https://github.com/SeaQL/sea-orm/pull/1702 * Added `DatabaseConnection::ping` https://github.com/SeaQL/sea-orm/pull/1627 ```rust |db: DatabaseConnection| { assert!(db.ping().await.is_ok()); db.clone().close().await; assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire))); } ``` * Added `TryInsert` that does not panic on empty inserts https://github.com/SeaQL/sea-orm/pull/1708 ```rust // now, you can do: let res = Bakery::insert_many(std::iter::empty()) .on_empty_do_nothing() .exec(db) .await; assert!(matches!(res, Ok(TryInsertResult::Empty))); ``` * Insert on conflict do nothing to return Ok https://github.com/SeaQL/sea-orm/pull/1712 ```rust let on = OnConflict::column(Column::Id).do_nothing().to_owned(); // Existing behaviour let res = Entity::insert_many([..]).on_conflict(on).exec(db).await; assert!(matches!(res, Err(DbErr::RecordNotInserted))); // New API; now you can: let res = Entity::insert_many([..]).on_conflict(on).do_nothing().exec(db).await; assert!(matches!(res, Ok(TryInsertResult::Conflicted))); ``` ### Bug Fixes * Fixed `DeriveActiveEnum` throwing errors because `string_value` consists non-UAX#31 compliant characters https://github.com/SeaQL/sea-orm/pull/1374 ```rust #[derive(EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum StringValue { #[sea_orm(string_value = "")] Member1, #[sea_orm(string_value = "$$")] Member2, } // will now produce the following enum: pub enum StringValueVariant { __Empty, _0x240x24, } ``` * [sea-orm-cli] Fix Postgres enum arrays https://github.com/SeaQL/sea-orm/pull/1678 * [sea-orm-cli] The implementation of `Related` with `via` and `to` methods will not be generated if there exists multiple paths via an intermediate table https://github.com/SeaQL/sea-orm/pull/1435 * [sea-orm-cli] fixed entity generation includes partitioned tables https://github.com/SeaQL/sea-orm/issues/1582, https://github.com/SeaQL/sea-schema/pull/105 * Fixed `ActiveEnum::db_type()` return type does not implement `ColumnTypeTrait` https://github.com/SeaQL/sea-orm/pull/1576 * Resolved `insert_many` failing if the models iterator is empty https://github.com/SeaQL/sea-orm/issues/873 ### Breaking changes * Supports for partial select of `Option` model field. A `None` value will be filled when the select result does not contain the `Option` field instead of throwing an error. https://github.com/SeaQL/sea-orm/pull/1513 * Replaced `sea-strum` dependency with upstream `strum` in `sea-orm` https://github.com/SeaQL/sea-orm/pull/1535 * Added `derive` and `strum` features to `sea-orm-macros` * The derive macro `EnumIter` is now shipped by `sea-orm-macros` * Added a new variant `Many` to `Identity` https://github.com/SeaQL/sea-orm/pull/1508 * Enabled `hashable-value` feature in SeaQuery, thus `Value::Float(NaN) == Value::Float(NaN)` would be true https://github.com/SeaQL/sea-orm/pull/1728, https://github.com/SeaQL/sea-orm/pull/1743 * The `DeriveActiveEnum` derive macro no longer implement `std::fmt::Display`. You can use the new `DeriveDisplay` macro https://github.com/SeaQL/sea-orm/pull/1726 * `sea-query/derive` is no longer enabled by `sea-orm`, as such, `Iden` no longer works as a derive macro (it's still a trait). Instead, we are shipping a new macro `DeriveIden` https://github.com/SeaQL/sea-orm/pull/1740 https://github.com/SeaQL/sea-orm/pull/1755 ```rust // then: #[derive(Iden)] #[iden = "category"] pub struct CategoryEnum; #[derive(Iden)] pub enum Tea { Table, #[iden = "EverydayTea"] EverydayTea, } // now: #[derive(DeriveIden)] #[sea_orm(iden = "category")] pub struct CategoryEnum; #[derive(DeriveIden)] pub enum Tea { Table, #[sea_orm(iden = "EverydayTea")] EverydayTea, } ``` * Definition of `DbErr::ConnectionAcquire` changed to `ConnectionAcquire(ConnAcquireErr)` https://github.com/SeaQL/sea-orm/pull/1737 * `FromJsonQueryResult` removed from entity prelude ### Upgrades * Upgraded `sqlx` to `0.7` https://github.com/SeaQL/sea-orm/pull/1742 * Upgraded `sea-query` to `0.30` https://github.com/SeaQL/sea-orm/pull/1742 * Upgraded `sea-schema` to `0.14` https://github.com/SeaQL/sea-orm/pull/1742 * Upgraded `syn` to `2` https://github.com/SeaQL/sea-orm/pull/1713 * Upgraded `heck` to `0.4` https://github.com/SeaQL/sea-orm/pull/1520, https://github.com/SeaQL/sea-orm/pull/1544 * Upgraded `strum` to `0.25` https://github.com/SeaQL/sea-orm/pull/1752 * Upgraded `clap` to `4.3` https://github.com/SeaQL/sea-orm/pull/1468 * Upgraded `ouroboros` to `0.17` https://github.com/SeaQL/sea-orm/pull/1724 ### House keeping * Replaced `bae` with `sea-bae` https://github.com/SeaQL/sea-orm/pull/1739 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.11.1...0.12.1 ## 0.11.3 - 2023-04-24 ### Enhancements * Re-export `sea_orm::ConnectionTrait` in `sea_orm_migration::prelude` https://github.com/SeaQL/sea-orm/pull/1577 * Support generic structs in `FromQueryResult` derive macro https://github.com/SeaQL/sea-orm/pull/1464, https://github.com/SeaQL/sea-orm/pull/1603 ```rust #[derive(FromQueryResult)] struct GenericTest { foo: i32, bar: T, } ``` ```rust trait MyTrait { type Item: TryGetable; } #[derive(FromQueryResult)] struct TraitAssociateTypeTest where T: MyTrait, { foo: T::Item, } ``` ### Bug Fixes * Fixed https://github.com/SeaQL/sea-orm/issues/1608 by pinning the version of `tracing-subscriber` dependency to 0.3.17 https://github.com/SeaQL/sea-orm/pull/1609 ## 0.11.2 - 2023-03-25 ### Enhancements * Enable required `syn` features https://github.com/SeaQL/sea-orm/pull/1556 * Re-export `sea_query::BlobSize` in `sea_orm::entity::prelude` https://github.com/SeaQL/sea-orm/pull/1548 ## 0.11.1 - 2023-03-10 ### Bug Fixes * Fixes `DeriveActiveEnum` (by qualifying `ColumnTypeTrait::def`) https://github.com/SeaQL/sea-orm/issues/1478 * The CLI command `sea-orm-cli generate entity -u ''` will now generate the following code for each `Binary` or `VarBinary` columns in compact format https://github.com/SeaQL/sea-orm/pull/1529 ```rust #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "binary")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Binary(BlobSize::Blob(None))")] pub binary: Vec, #[sea_orm(column_type = "Binary(BlobSize::Blob(Some(10)))")] pub binary_10: Vec, #[sea_orm(column_type = "Binary(BlobSize::Tiny)")] pub binary_tiny: Vec, #[sea_orm(column_type = "Binary(BlobSize::Medium)")] pub binary_medium: Vec, #[sea_orm(column_type = "Binary(BlobSize::Long)")] pub binary_long: Vec, #[sea_orm(column_type = "VarBinary(10)")] pub var_binary: Vec, } ``` * The CLI command `sea-orm-cli generate entity -u '' --expanded-format` will now generate the following code for each `Binary` or `VarBinary` columns in expanded format https://github.com/SeaQL/sea-orm/pull/1529 ```rust impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Binary => ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None)).def(), Self::Binary10 => { ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(Some(10u32))).def() } Self::BinaryTiny => ColumnType::Binary(sea_orm::sea_query::BlobSize::Tiny).def(), Self::BinaryMedium => ColumnType::Binary(sea_orm::sea_query::BlobSize::Medium).def(), Self::BinaryLong => ColumnType::Binary(sea_orm::sea_query::BlobSize::Long).def(), Self::VarBinary => ColumnType::VarBinary(10u32).def(), } } } ``` * Fix missing documentation on type generated by derive macros https://github.com/SeaQL/sea-orm/pull/1522, https://github.com/SeaQL/sea-orm/pull/1531 ## 0.11.0 - 2023-02-07 + 2023-02-02: `0.11.0-rc.1` + 2023-02-04: `0.11.0-rc.2` ### New Features #### SeaORM Core * Simple data loader https://github.com/SeaQL/sea-orm/pull/1238, https://github.com/SeaQL/sea-orm/pull/1443 * Transactions Isolation level and Access mode https://github.com/SeaQL/sea-orm/pull/1230 * Support various UUID formats that are available in `uuid::fmt` module https://github.com/SeaQL/sea-orm/pull/1325 * Support Vector of enum for Postgres https://github.com/SeaQL/sea-orm/pull/1210 * Support `ActiveEnum` field as primary key https://github.com/SeaQL/sea-orm/pull/1414 * Casting columns as a different data type on select, insert and update https://github.com/SeaQL/sea-orm/pull/1304 * Methods of `ActiveModelBehavior` receive db connection as a parameter https://github.com/SeaQL/sea-orm/pull/1145, https://github.com/SeaQL/sea-orm/pull/1328 * Added `execute_unprepared` method to `DatabaseConnection` and `DatabaseTransaction` https://github.com/SeaQL/sea-orm/pull/1327 * Added `Select::into_tuple` to select rows as tuples (instead of defining a custom Model) https://github.com/SeaQL/sea-orm/pull/1311 #### SeaORM CLI * Generate `#[serde(skip_deserializing)]` for primary key columns https://github.com/SeaQL/sea-orm/pull/846, https://github.com/SeaQL/sea-orm/pull/1186, https://github.com/SeaQL/sea-orm/pull/1318 * Generate `#[serde(skip)]` for hidden columns https://github.com/SeaQL/sea-orm/pull/1171, https://github.com/SeaQL/sea-orm/pull/1320 * Generate entity with extra derives and attributes for model struct https://github.com/SeaQL/sea-orm/pull/1124, https://github.com/SeaQL/sea-orm/pull/1321 #### SeaORM Migration * Migrations are now performed inside a transaction for Postgres https://github.com/SeaQL/sea-orm/pull/1379 ### Enhancements * Refactor schema module to expose functions for database alteration https://github.com/SeaQL/sea-orm/pull/1256 * Generate compact entity with `#[sea_orm(column_type = "JsonBinary")]` macro attribute https://github.com/SeaQL/sea-orm/pull/1346 * `MockDatabase::append_exec_results()`, `MockDatabase::append_query_results()`, `MockDatabase::append_exec_errors()` and `MockDatabase::append_query_errors()` take any types implemented `IntoIterator` trait https://github.com/SeaQL/sea-orm/pull/1367 * `find_by_id` and `delete_by_id` take any `Into` primary key value https://github.com/SeaQL/sea-orm/pull/1362 * `QuerySelect::offset` and `QuerySelect::limit` takes in `Into>` where `None` would reset them https://github.com/SeaQL/sea-orm/pull/1410 * Added `DatabaseConnection::close` https://github.com/SeaQL/sea-orm/pull/1236 * Added `is_null` getter for `ColumnDef` https://github.com/SeaQL/sea-orm/pull/1381 * Added `ActiveValue::reset` to convert `Unchanged` into `Set` https://github.com/SeaQL/sea-orm/pull/1177 * Added `QueryTrait::apply_if` to optionally apply a filter https://github.com/SeaQL/sea-orm/pull/1415 * Added the `sea-orm-internal` feature flag to expose some SQLx types * Added `DatabaseConnection::get_*_connection_pool()` for accessing the inner SQLx connection pool https://github.com/SeaQL/sea-orm/pull/1297 * Re-exporting SQLx errors https://github.com/SeaQL/sea-orm/pull/1434 ### Upgrades * Upgrade `axum` to `0.6.1` https://github.com/SeaQL/sea-orm/pull/1285 * Upgrade `sea-query` to `0.28` https://github.com/SeaQL/sea-orm/pull/1366 * Upgrade `sea-query-binder` to `0.3` https://github.com/SeaQL/sea-orm/pull/1366 * Upgrade `sea-schema` to `0.11` https://github.com/SeaQL/sea-orm/pull/1366 ### House Keeping * Fixed all clippy warnings as of `1.67.0` https://github.com/SeaQL/sea-orm/pull/1426 * Removed dependency where not needed https://github.com/SeaQL/sea-orm/pull/1213 * Disabled default features and enabled only the needed ones https://github.com/SeaQL/sea-orm/pull/1300 * Cleanup panic and unwrap https://github.com/SeaQL/sea-orm/pull/1231 * Cleanup the use of `vec!` macro https://github.com/SeaQL/sea-orm/pull/1367 ### Bug Fixes * [sea-orm-cli] Propagate error on the spawned child processes https://github.com/SeaQL/sea-orm/pull/1402 * Fixes sea-orm-cli errors exit with error code 0 https://github.com/SeaQL/sea-orm/issues/1342 * Fixes `DeriveColumn` (by qualifying `IdenStatic::as_str`) https://github.com/SeaQL/sea-orm/pull/1280 * Prevent returning connections to pool with a positive transaction depth https://github.com/SeaQL/sea-orm/pull/1283 * Postgres insert many will throw `RecordNotInserted` error if non of them are being inserted https://github.com/SeaQL/sea-orm/pull/1021 * Fixes inserting active models by `insert_many` with `on_conflict` and `do_nothing` panics if no rows are inserted on Postgres https://github.com/SeaQL/sea-orm/issues/899 * Don't call `last_insert_id` if not needed https://github.com/SeaQL/sea-orm/pull/1403 * Fixes hitting 'negative last_insert_rowid' panic with Sqlite https://github.com/SeaQL/sea-orm/issues/1357 * Noop when update without providing any values https://github.com/SeaQL/sea-orm/pull/1384 * Fixes Syntax Error when saving active model that sets nothing https://github.com/SeaQL/sea-orm/pull/1376 ### Breaking Changes * [sea-orm-cli] Enable --universal-time by default https://github.com/SeaQL/sea-orm/pull/1420 * Added `RecordNotInserted` and `RecordNotUpdated` to `DbErr` * Added `ConnectionTrait::execute_unprepared` method https://github.com/SeaQL/sea-orm/pull/1327 * As part of https://github.com/SeaQL/sea-orm/pull/1311, the required method of `TryGetable` changed: ```rust // then fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result; // now; ColIdx can be `&str` or `usize` fn try_get_by(res: &QueryResult, index: I) -> Result; ``` So if you implemented it yourself: ```patch impl TryGetable for XXX { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + fn try_get_by(res: &QueryResult, idx: I) -> Result { - let value: YYY = res.try_get(pre, col).map_err(TryGetError::DbErr)?; + let value: YYY = res.try_get_by(idx).map_err(TryGetError::DbErr)?; .. } } ``` * The `ActiveModelBehavior` trait becomes async trait https://github.com/SeaQL/sea-orm/pull/1328. If you overridden the default `ActiveModelBehavior` implementation: ```rust #[async_trait::async_trait] impl ActiveModelBehavior for ActiveModel { async fn before_save(self, db: &C, insert: bool) -> Result where C: ConnectionTrait, { // ... } // ... } ``` * `DbErr::RecordNotFound("None of the database rows are affected")` is moved to a dedicated error variant `DbErr::RecordNotUpdated` https://github.com/SeaQL/sea-orm/pull/1425 ```rust let res = Update::one(cake::ActiveModel { name: Set("Cheese Cake".to_owned()), ..model.into_active_model() }) .exec(&db) .await; // then assert_eq!( res, Err(DbErr::RecordNotFound( "None of the database rows are affected".to_owned() )) ); // now assert_eq!(res, Err(DbErr::RecordNotUpdated)); ``` * `sea_orm::ColumnType` was replaced by `sea_query::ColumnType` https://github.com/SeaQL/sea-orm/pull/1395 * Method `ColumnType::def` was moved to `ColumnTypeTrait` * `ColumnType::Binary` becomes a tuple variant which takes in additional option `sea_query::BlobSize` * `ColumnType::Custom` takes a `sea_query::DynIden` instead of `String` and thus a new method `custom` is added (note the lowercase) ```diff // Compact Entity #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "fruit")] pub struct Model { - #[sea_orm(column_type = r#"Custom("citext".to_owned())"#)] + #[sea_orm(column_type = r#"custom("citext")"#)] pub column: String, } ``` ```diff // Expanded Entity impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { - Self::Column => ColumnType::Custom("citext".to_owned()).def(), + Self::Column => ColumnType::custom("citext").def(), } } } ``` ### Miscellaneous * Fixed a small typo https://github.com/SeaQL/sea-orm/pull/1391 * `axum` example should use tokio runtime https://github.com/SeaQL/sea-orm/pull/1428 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.10.0...0.11.0 ## 0.10.7 - 2023-01-19 ### Bug Fixes * Inserting active models by `insert_many` with `on_conflict` and `do_nothing` panics if no rows are inserted on Postgres https://github.com/SeaQL/sea-orm/issues/899 * Hitting 'negative last_insert_rowid' panic with Sqlite https://github.com/SeaQL/sea-orm/issues/1357 ## 0.10.6 - 2022-12-23 ### Enhancements * Cast enum values when constructing update many query https://github.com/SeaQL/sea-orm/pull/1178 ### Bug Fixes * Fixes `DeriveColumn` (by qualifying `IdenStatic::as_str`) https://github.com/SeaQL/sea-orm/pull/1280 * Prevent returning connections to pool with a positive transaction depth https://github.com/SeaQL/sea-orm/pull/1283 * [sea-orm-codegen] Skip implementing Related if the same related entity is being referenced by a conjunct relation https://github.com/SeaQL/sea-orm/pull/1298 * [sea-orm-cli] CLI depends on codegen of the same version https://github.com/SeaQL/sea-orm/pull/1299/ ## 0.10.5 - 2022-12-02 ### New Features * Add `QuerySelect::columns` method - select multiple columns https://github.com/SeaQL/sea-orm/pull/1264 * Transactions Isolation level and Access mode https://github.com/SeaQL/sea-orm/pull/1230 ### Bug Fixes * `DeriveEntityModel` derive macro: when parsing field type, always treat field with `Option` as nullable column https://github.com/SeaQL/sea-orm/pull/1257 ### Enhancements * [sea-orm-cli] Generate `Related` implementation for many-to-many relation with extra columns https://github.com/SeaQL/sea-orm/pull/1260 * Optimize the default implementation of `TryGetableFromJson::try_get_from_json()` - deserializing into `Self` directly without the need of a intermediate `serde_json::Value` https://github.com/SeaQL/sea-orm/pull/1249 ## 0.10.4 - 2022-11-24 ### Bug Fixes * Fix DeriveActiveEnum expand enum variant starts with number https://github.com/SeaQL/sea-orm/pull/1219 * [sea-orm-cli] Generate entity file for specified tables only https://github.com/SeaQL/sea-orm/pull/1245 * Support appending `DbErr` to `MockDatabase` https://github.com/SeaQL/sea-orm/pull/1241 ### Enhancements * Filter rows with `IS IN` enum values expression https://github.com/SeaQL/sea-orm/pull/1183 * [sea-orm-cli] Generate entity with relation variant order by name of reference table https://github.com/SeaQL/sea-orm/pull/1229 ## 0.10.3 - 2022-11-14 ### Bug Fixes * [sea-orm-cli] Set search path when initializing Postgres connection for CLI generate entity https://github.com/SeaQL/sea-orm/pull/1212 * [sea-orm-cli] Generate `_` prefix to enum variant starts with number https://github.com/SeaQL/sea-orm/pull/1211 * Fix composite key cursor pagination https://github.com/SeaQL/sea-orm/pull/1216 + The logic for single-column primary key was correct, but for composite keys the logic was incorrect ### Enhancements * Added `Insert::exec_without_returning` https://github.com/SeaQL/sea-orm/pull/1208 ### House Keeping * Remove dependency when not needed https://github.com/SeaQL/sea-orm/pull/1207 ## 0.10.2 - 2022-11-06 ### Enhancements * [sea-orm-rocket] added `sqlx_logging` to `Config` https://github.com/SeaQL/sea-orm/pull/1192 * Collecting metrics for `query_one/all` https://github.com/SeaQL/sea-orm/pull/1165 * Use GAT to elide `StreamTrait` lifetime https://github.com/SeaQL/sea-orm/pull/1161 ### Bug Fixes * corrected the error name `UpdateGetPrimaryKey` https://github.com/SeaQL/sea-orm/pull/1180 ### Upgrades * Update MSRV to 1.65 ## 0.10.1 - 2022-10-27 ### Enhancements * [sea-orm-cli] Escape module name defined with Rust keywords https://github.com/SeaQL/sea-orm/pull/1052 * [sea-orm-cli] Check to make sure migration name doesn't contain hyphen `-` in it https://github.com/SeaQL/sea-orm/pull/879, https://github.com/SeaQL/sea-orm/pull/1155 * Support `time` crate for SQLite https://github.com/SeaQL/sea-orm/pull/995 ### Bug Fixes * [sea-orm-cli] Generate `Related` for m-to-n relation https://github.com/SeaQL/sea-orm/pull/1075 * [sea-orm-cli] Generate model entity with Postgres Enum field https://github.com/SeaQL/sea-orm/pull/1153 * [sea-orm-cli] Migrate up command apply all pending migrations https://github.com/SeaQL/sea-orm/pull/1010 * [sea-orm-cli] Conflicting short flag `-u` when executing `migrate generate` command https://github.com/SeaQL/sea-orm/pull/1157 * Prefix the usage of types with `sea_orm::` inside `DeriveActiveEnum` derive macros https://github.com/SeaQL/sea-orm/pull/1146, https://github.com/SeaQL/sea-orm/pull/1154 * [sea-orm-cli] Generate model with `Vec` or `Vec` should not derive `Eq` on the model struct https://github.com/SeaQL/sea-orm/pull/1158 ### House Keeping * [sea-orm-cli] [sea-orm-migration] Add `cli` feature to optionally include dependencies that are required by the CLI https://github.com/SeaQL/sea-orm/pull/978 ### Upgrades * Upgrade `sea-schema` to 0.10.2 https://github.com/SeaQL/sea-orm/pull/1153 ## 0.10.0 - 2022-10-23 ### New Features * Better error types (carrying SQLx Error) https://github.com/SeaQL/sea-orm/pull/1002 * Support array datatype in PostgreSQL https://github.com/SeaQL/sea-orm/pull/1132 * [sea-orm-cli] Generate entity files as a library or module https://github.com/SeaQL/sea-orm/pull/953 * [sea-orm-cli] Generate a new migration template with name prefix of unix timestamp https://github.com/SeaQL/sea-orm/pull/947 * [sea-orm-cli] Generate migration in modules https://github.com/SeaQL/sea-orm/pull/933 * [sea-orm-cli] Generate `DeriveRelation` on empty `Relation` enum https://github.com/SeaQL/sea-orm/pull/1019 * [sea-orm-cli] Generate entity derive `Eq` if possible https://github.com/SeaQL/sea-orm/pull/988 * [sea-orm-cli] Run migration on any PostgreSQL schema https://github.com/SeaQL/sea-orm/pull/1056 ### Enhancements * Support `distinct` & `distinct_on` expression https://github.com/SeaQL/sea-orm/pull/902 * `fn column()` also handle enum type https://github.com/SeaQL/sea-orm/pull/973 * Added `acquire_timeout` on `ConnectOptions` https://github.com/SeaQL/sea-orm/pull/897 * [sea-orm-cli] `migrate fresh` command will drop all PostgreSQL types https://github.com/SeaQL/sea-orm/pull/864, https://github.com/SeaQL/sea-orm/pull/991 * Better compile error for entity without primary key https://github.com/SeaQL/sea-orm/pull/1020 * Added blanket implementations of `IntoActiveValue` for `Option` values https://github.com/SeaQL/sea-orm/pull/833 * Added `into_model` & `into_json` to `Cursor` https://github.com/SeaQL/sea-orm/pull/1112 * Added `set_schema_search_path` method to `ConnectOptions` for setting schema search path of PostgreSQL connection https://github.com/SeaQL/sea-orm/pull/1056 * Serialize `time` types as `serde_json::Value` https://github.com/SeaQL/sea-orm/pull/1042 * Implements `fmt::Display` for `ActiveEnum` https://github.com/SeaQL/sea-orm/pull/986 * Implements `TryFrom` for `Model` https://github.com/SeaQL/sea-orm/pull/990 ### Bug Fixes * Trim spaces when paginating raw SQL https://github.com/SeaQL/sea-orm/pull/1094 ### Breaking Changes * Replaced `usize` with `u64` in `PaginatorTrait` https://github.com/SeaQL/sea-orm/pull/789 * Type signature of `DbErr` changed as a result of https://github.com/SeaQL/sea-orm/pull/1002 * `ColumnType::Enum` structure changed: ```rust enum ColumnType { // then Enum(String, Vec) // now Enum { /// Name of enum name: DynIden, /// Variants of enum variants: Vec, } ... } // example #[derive(Iden)] enum TeaEnum { #[iden = "tea"] Enum, #[iden = "EverydayTea"] EverydayTea, #[iden = "BreakfastTea"] BreakfastTea, } // then ColumnDef::new(active_enum_child::Column::Tea) .enumeration("tea", vec!["EverydayTea", "BreakfastTea"]) // now ColumnDef::new(active_enum_child::Column::Tea) .enumeration(TeaEnum::Enum, [TeaEnum::EverydayTea, TeaEnum::BreakfastTea]) ``` * A new method `array_type` was added to `ValueType`: ```rust impl sea_orm::sea_query::ValueType for MyType { fn array_type() -> sea_orm::sea_query::ArrayType { sea_orm::sea_query::ArrayType::TypeName } ... } ``` * `ActiveEnum::name()` changed return type to `DynIden`: ```rust #[derive(Debug, Iden)] #[iden = "category"] pub struct CategoryEnum; impl ActiveEnum for Category { // then fn name() -> String { "category".to_owned() } // now fn name() -> DynIden { SeaRc::new(CategoryEnum) } ... } ``` ### House Keeping * Documentation grammar fixes https://github.com/SeaQL/sea-orm/pull/1050 * Replace `dotenv` with `dotenvy` in examples https://github.com/SeaQL/sea-orm/pull/1085 * Exclude test_cfg module from SeaORM https://github.com/SeaQL/sea-orm/pull/1077 ### Integration * Support `rocket_okapi` https://github.com/SeaQL/sea-orm/pull/1071 ### Upgrades * Upgrade `sea-query` to 0.26 https://github.com/SeaQL/sea-orm/pull/985 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.9.0...0.10.0 ## 0.9.3 - 2022-09-30 ### Enhancements * `fn column()` also handle enum type https://github.com/SeaQL/sea-orm/pull/973 * Generate migration in modules https://github.com/SeaQL/sea-orm/pull/933 * Generate `DeriveRelation` on empty `Relation` enum https://github.com/SeaQL/sea-orm/pull/1019 * Documentation grammar fixes https://github.com/SeaQL/sea-orm/pull/1050 ### Bug Fixes * Implement `IntoActiveValue` for `time` types https://github.com/SeaQL/sea-orm/pull/1041 * Fixed module import for `FromJsonQueryResult` derive macro https://github.com/SeaQL/sea-orm/pull/1081 ## 0.9.2 - 2022-08-20 ### Enhancements * [sea-orm-cli] Migrator CLI handles init and generate commands https://github.com/SeaQL/sea-orm/pull/931 * [sea-orm-cli] added `with-copy-enums` flag to conditional derive `Copy` on `ActiveEnum` https://github.com/SeaQL/sea-orm/pull/936 ### House Keeping * Exclude `chrono` default features https://github.com/SeaQL/sea-orm/pull/950 * Set minimal rustc version to `1.60` https://github.com/SeaQL/sea-orm/pull/938 * Update `sea-query` to `0.26.3` ### Notes In this minor release, we removed `time` v0.1 from the dependency graph ## 0.9.1 - 2022-07-22 ### Enhancements * [sea-orm-cli] Codegen support for `VarBinary` column type https://github.com/SeaQL/sea-orm/pull/746 * [sea-orm-cli] Generate entity for SYSTEM VERSIONED tables on MariaDB https://github.com/SeaQL/sea-orm/pull/876 ### Bug Fixes * `RelationDef` & `RelationBuilder` should be `Send` & `Sync` https://github.com/SeaQL/sea-orm/pull/898 ### House Keeping * Remove unnecessary `async_trait` https://github.com/SeaQL/sea-orm/pull/737 ## 0.9.0 - 2022-07-17 ### New Features * Cursor pagination https://github.com/SeaQL/sea-orm/pull/822 * Custom join on conditions https://github.com/SeaQL/sea-orm/pull/793 * `DeriveMigrationName` and `sea_orm_migration::util::get_file_stem` https://github.com/SeaQL/sea-orm/pull/736 * `FromJsonQueryResult` for deserializing `Json` from query result https://github.com/SeaQL/sea-orm/pull/794 ### Enhancements * Added `sqlx_logging_level` to `ConnectOptions` https://github.com/SeaQL/sea-orm/pull/800 * Added `num_items_and_pages` to `Paginator` https://github.com/SeaQL/sea-orm/pull/768 * Added `TryFromU64` for `time` https://github.com/SeaQL/sea-orm/pull/849 * Added `Insert::on_conflict` https://github.com/SeaQL/sea-orm/pull/791 * Added `QuerySelect::join_as` and `QuerySelect::join_as_rev` https://github.com/SeaQL/sea-orm/pull/852 * Include column name in `TryGetError::Null` https://github.com/SeaQL/sea-orm/pull/853 * [sea-orm-cli] Improve logging https://github.com/SeaQL/sea-orm/pull/735 * [sea-orm-cli] Generate enum with numeric like variants https://github.com/SeaQL/sea-orm/pull/588 * [sea-orm-cli] Allow old pending migration to be applied https://github.com/SeaQL/sea-orm/pull/755 * [sea-orm-cli] Skip generating entity for ignored tables https://github.com/SeaQL/sea-orm/pull/837 * [sea-orm-cli] Generate code for `time` crate https://github.com/SeaQL/sea-orm/pull/724 * [sea-orm-cli] Add various blob column types https://github.com/SeaQL/sea-orm/pull/850 * [sea-orm-cli] Generate entity files with Postgres's schema name https://github.com/SeaQL/sea-orm/pull/422 ### Upgrades * Upgrade `clap` to 3.2 https://github.com/SeaQL/sea-orm/pull/706 * Upgrade `time` to 0.3 https://github.com/SeaQL/sea-orm/pull/834 * Upgrade `sqlx` to 0.6 https://github.com/SeaQL/sea-orm/pull/834 * Upgrade `uuid` to 1.0 https://github.com/SeaQL/sea-orm/pull/834 * Upgrade `sea-query` to 0.26 https://github.com/SeaQL/sea-orm/pull/834 * Upgrade `sea-schema` to 0.9 https://github.com/SeaQL/sea-orm/pull/834 ### House Keeping * Refactor stream metrics https://github.com/SeaQL/sea-orm/pull/778 ### Bug Fixes * [sea-orm-cli] skip checking connection string for credentials https://github.com/SeaQL/sea-orm/pull/851 ### Breaking Changes * `SelectTwoMany::one()` has been dropped https://github.com/SeaQL/sea-orm/pull/813, you can get `(Entity, Vec)` by first querying a single model from Entity, then use [`ModelTrait::find_related`] on the model. * #### Feature flag revamp We now adopt the [weak dependency](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html#new-syntax-for-cargo-features) syntax in Cargo. That means the flags `["sqlx-json", "sqlx-chrono", "sqlx-decimal", "sqlx-uuid", "sqlx-time"]` are not needed and now removed. Instead, `with-time` will enable `sqlx?/time` only if `sqlx` is already enabled. As a consequence, now the features `with-json`, `with-chrono`, `with-rust_decimal`, `with-uuid`, `with-time` will not be enabled as a side-effect of enabling `sqlx`. **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.8.0...0.9.0 ## sea-orm-migration 0.8.3 * Removed `async-std` from dependency https://github.com/SeaQL/sea-orm/pull/758 ## 0.8.0 - 2022-05-10 ### New Features * [sea-orm-cli] `sea migrate generate` to generate a new, empty migration file https://github.com/SeaQL/sea-orm/pull/656 ### Enhancements * Add `max_connections` option to CLI https://github.com/SeaQL/sea-orm/pull/670 * Derive `Eq`, `Clone` for `DbErr` https://github.com/SeaQL/sea-orm/pull/677 * Add `is_changed` to `ActiveModelTrait` https://github.com/SeaQL/sea-orm/pull/683 ### Bug Fixes * Fix `DerivePrimaryKey` with custom primary key column name https://github.com/SeaQL/sea-orm/pull/694 * Fix `DeriveEntityModel` macros override column name https://github.com/SeaQL/sea-orm/pull/695 * Fix Insert with no value supplied using `DEFAULT` https://github.com/SeaQL/sea-orm/pull/589 ### Breaking Changes * Migration utilities are moved from sea-schema to sea-orm repo, under a new sub-crate `sea-orm-migration`. `sea_schema::migration::prelude` should be replaced by `sea_orm_migration::prelude` in all migration files ### Upgrades * Upgrade `sea-query` to 0.24.x, `sea-schema` to 0.8.x * Upgrade example to Actix Web 4, Actix Web 3 remains https://github.com/SeaQL/sea-orm/pull/638 * Added Tonic gRPC example https://github.com/SeaQL/sea-orm/pull/659 * Upgrade GraphQL example to use axum 0.5.x * Upgrade axum example to 0.5.x ### Fixed Issues * Failed to insert row with only default values https://github.com/SeaQL/sea-orm/issues/420 * Reduce database connections to 1 during codegen https://github.com/SeaQL/sea-orm/issues/511 * Column names with single letters separated by underscores are concatenated https://github.com/SeaQL/sea-orm/issues/630 * Update Actix Web examples https://github.com/SeaQL/sea-orm/issues/639 * Lower function missing https://github.com/SeaQL/sea-orm/issues/672 * is_changed on active_model https://github.com/SeaQL/sea-orm/issues/674 * Failing find_with_related with column_name attribute https://github.com/SeaQL/sea-orm/issues/693 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.7.1...0.8.0 ## 0.7.1 - 2022-03-26 * Fix sea-orm-cli error * Fix sea-orm cannot build without `with-json` ## 0.7.0 - 2022-03-26 ### New Features * Update ActiveModel by JSON by @billy1624 in https://github.com/SeaQL/sea-orm/pull/492 * Supports `time` crate by @billy1624 https://github.com/SeaQL/sea-orm/pull/602 * Allow for creation of indexes for PostgreSQL and SQLite @nickb937 https://github.com/SeaQL/sea-orm/pull/593 * Added `delete_by_id` @ShouvikGhosh2048 https://github.com/SeaQL/sea-orm/pull/590 * Implement `PaginatorTrait` for `SelectorRaw` @shinbunbun https://github.com/SeaQL/sea-orm/pull/617 ### Enhancements * Added axum graphql example by @aaronleopold in https://github.com/SeaQL/sea-orm/pull/587 * Add example for integrate with jsonrpsee by @hunjixin https://github.com/SeaQL/sea-orm/pull/632 * Codegen add serde derives to enums, if specified by @BenJeau https://github.com/SeaQL/sea-orm/pull/463 * Codegen Unsigned Integer by @billy1624 https://github.com/SeaQL/sea-orm/pull/397 * Add `Send` bound to `QueryStream` and `TransactionStream` by @sebpuetz https://github.com/SeaQL/sea-orm/pull/471 * Add `Send` to `StreamTrait` by @nappa85 https://github.com/SeaQL/sea-orm/pull/622 * `sea` as an alternative bin name to `sea-orm-cli` by @ZhangHanDong https://github.com/SeaQL/sea-orm/pull/558 ### Bug Fixes * Fix codegen with Enum in expanded format by @billy1624 https://github.com/SeaQL/sea-orm/pull/624 * Fixing and testing into_json of various field types by @billy1624 https://github.com/SeaQL/sea-orm/pull/539 ### Breaking Changes * Exclude `mock` from default features by @billy1624 https://github.com/SeaQL/sea-orm/pull/562 * `create_table_from_entity` will no longer create index for MySQL, please use the new method `create_index_from_entity` ### Documentations * Describe default value of ActiveValue on document by @Ken-Miura in https://github.com/SeaQL/sea-orm/pull/556 * community: add axum-book-management by @lz1998 in https://github.com/SeaQL/sea-orm/pull/564 * Add Backpack to project showcase by @JSH32 in https://github.com/SeaQL/sea-orm/pull/567 * Add mediarepo to showcase by @Trivernis in https://github.com/SeaQL/sea-orm/pull/569 * COMMUNITY: add a link to Svix to showcase by @tasn in https://github.com/SeaQL/sea-orm/pull/537 * Update COMMUNITY.md by @naryand in https://github.com/SeaQL/sea-orm/pull/570 * Update COMMUNITY.md by @BobAnkh in https://github.com/SeaQL/sea-orm/pull/568 * Update COMMUNITY.md by @KaniyaSimeji in https://github.com/SeaQL/sea-orm/pull/566 * Update COMMUNITY.md by @aaronleopold in https://github.com/SeaQL/sea-orm/pull/565 * Update COMMUNITY.md by @gudaoxuri in https://github.com/SeaQL/sea-orm/pull/572 * Update Wikijump's entry in COMMUNITY.md by @ammongit in https://github.com/SeaQL/sea-orm/pull/573 * Update COMMUNITY.md by @koopa1338 in https://github.com/SeaQL/sea-orm/pull/574 * Update COMMUNITY.md by @gengteng in https://github.com/SeaQL/sea-orm/pull/580 * Update COMMUNITY.md by @Yama-Tomo in https://github.com/SeaQL/sea-orm/pull/582 * add oura-postgres-sink to COMMUNITY.md by @rvcas in https://github.com/SeaQL/sea-orm/pull/594 * Add rust-example-caster-api to COMMUNITY.md by @bkonkle in https://github.com/SeaQL/sea-orm/pull/623 ### Fixed Issues * orm-cli generated incorrect type for #[sea_orm(primary_key)]. Should be u64. Was i64. https://github.com/SeaQL/sea-orm/issues/295 * how to update dynamically from json value https://github.com/SeaQL/sea-orm/issues/346 * Make `DatabaseConnection` `Clone` with the default features enabled https://github.com/SeaQL/sea-orm/issues/438 * Updating multiple fields in a Model by passing a reference https://github.com/SeaQL/sea-orm/issues/460 * SeaORM CLI not adding serde derives to Enums https://github.com/SeaQL/sea-orm/issues/461 * sea-orm-cli generates wrong data type for nullable blob https://github.com/SeaQL/sea-orm/issues/490 * Support the time crate in addition (instead of?) chrono https://github.com/SeaQL/sea-orm/issues/499 * PaginatorTrait for SelectorRaw https://github.com/SeaQL/sea-orm/issues/500 * sea_orm::DatabaseConnection should implement `Clone` by default https://github.com/SeaQL/sea-orm/issues/517 * How do you seed data in migrations using ActiveModels? https://github.com/SeaQL/sea-orm/issues/522 * Datetime fields are not serialized by `.into_json()` on queries https://github.com/SeaQL/sea-orm/issues/530 * Update / Delete by id https://github.com/SeaQL/sea-orm/issues/552 * `#[sea_orm(indexed)]` only works for MySQL https://github.com/SeaQL/sea-orm/issues/554 * `sea-orm-cli generate --with-serde` does not work on Postgresql custom type https://github.com/SeaQL/sea-orm/issues/581 * `sea-orm-cli generate --expanded-format` panic when postgres table contains enum type https://github.com/SeaQL/sea-orm/issues/614 * UUID fields are not serialized by `.into_json()` on queries https://github.com/SeaQL/sea-orm/issues/619 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.6.0...0.7.0 ## 0.6.0 - 2022-02-07 ### New Features * Migration Support by @billy1624 in https://github.com/SeaQL/sea-orm/pull/335 * Support `DateTime` & `DateTime` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/489 * Add `max_lifetime` connection option by @billy1624 in https://github.com/SeaQL/sea-orm/pull/493 ### Enhancements * Model with Generics by @billy1624 in https://github.com/SeaQL/sea-orm/pull/400 * Add Poem example by @sunli829 in https://github.com/SeaQL/sea-orm/pull/446 * Codegen `column_name` proc_macro attribute by @billy1624 in https://github.com/SeaQL/sea-orm/pull/433 * Easy joins with MockDatabase #447 by @cemoktra in https://github.com/SeaQL/sea-orm/pull/455 ### Bug Fixes * CLI allow generate entity with url without password by @billy1624 in https://github.com/SeaQL/sea-orm/pull/436 * Support up to 6-ary composite primary key by @billy1624 in https://github.com/SeaQL/sea-orm/pull/423 * Fix FromQueryResult when Result is redefined by @tasn in https://github.com/SeaQL/sea-orm/pull/495 * Remove `r#` prefix when deriving `FromQueryResult` by @smrtrfszm in https://github.com/SeaQL/sea-orm/pull/494 ### Breaking Changes * Name conflict of foreign key constraints when two entities have more than one foreign keys by @billy1624 in https://github.com/SeaQL/sea-orm/pull/417 ### Fixed Issues * Is it possible to have 4 values Composite Key? https://github.com/SeaQL/sea-orm/issues/352 * Support `DateTime` & `DateTime` https://github.com/SeaQL/sea-orm/issues/381 * Codegen `column_name` proc_macro attribute if column name isn't in snake case https://github.com/SeaQL/sea-orm/issues/395 * Model with Generics https://github.com/SeaQL/sea-orm/issues/402 * Foreign key constraint collision when multiple keys exist between the same two tables https://github.com/SeaQL/sea-orm/issues/405 * sea-orm-cli passwordless database user causes "No password was found in the database url" error https://github.com/SeaQL/sea-orm/issues/435 * Testing joins with MockDatabase https://github.com/SeaQL/sea-orm/issues/447 * Surface max_lifetime connection option https://github.com/SeaQL/sea-orm/issues/475 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.5.0...0.6.0 ## 0.5.0 - 2022-01-01 ### Fixed Issues * Why insert, update, etc return an ActiveModel instead of Model? https://github.com/SeaQL/sea-orm/issues/289 * Rework `ActiveValue` https://github.com/SeaQL/sea-orm/issues/321 * Some missing ActiveEnum utilities https://github.com/SeaQL/sea-orm/issues/338 ### Merged PRs * First metric and tracing implementation by @nappa85 in https://github.com/SeaQL/sea-orm/pull/373 * Update sea-orm to depends on SeaQL/sea-query#202 by @billy1624 in https://github.com/SeaQL/sea-orm/pull/370 * Codegen ActiveEnum & Create Enum From ActiveEnum by @billy1624 in https://github.com/SeaQL/sea-orm/pull/348 * Axum example: update to Axum v0.4.2 by @ttys3 in https://github.com/SeaQL/sea-orm/pull/383 * Fix rocket version by @Gabriel-Paulucci in https://github.com/SeaQL/sea-orm/pull/384 * Insert & Update Return `Model` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/339 * Rework `ActiveValue` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/340 * Add wrapper method `ModelTrait::delete` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/396 * Add docker create script for contributors to setup databases locally by @billy1624 in https://github.com/SeaQL/sea-orm/pull/378 * Log with tracing-subscriber by @billy1624 in https://github.com/SeaQL/sea-orm/pull/399 * Codegen SQLite by @billy1624 in https://github.com/SeaQL/sea-orm/pull/386 * PR without clippy warnings in file changed tab by @billy1624 in https://github.com/SeaQL/sea-orm/pull/401 * Rename `sea-strum` lib back to `strum` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/361 ### Breaking Changes * `ActiveModel::insert` and `ActiveModel::update` return `Model` instead of `ActiveModel` * Method `ActiveModelBehavior::after_save` takes `Model` as input instead of `ActiveModel` * Rename method `sea_orm::unchanged_active_value_not_intended_for_public_use` to `sea_orm::Unchanged` * Rename method `ActiveValue::unset` to `ActiveValue::not_set` * Rename method `ActiveValue::is_unset` to `ActiveValue::is_not_set` * `PartialEq` of `ActiveValue` will also check the equality of state instead of just checking the equality of value **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.4.2...0.5.0 ## 0.4.2 - 2021-12-12 ### Fixed Issues * Delete::many() doesn't work when schema_name is defined https://github.com/SeaQL/sea-orm/issues/362 * find_with_related panic https://github.com/SeaQL/sea-orm/issues/374 * How to define the rust type of TIMESTAMP? https://github.com/SeaQL/sea-orm/issues/344 * Add Table on the generated Column enum https://github.com/SeaQL/sea-orm/issues/356 ### Merged PRs * `Delete::many()` with `TableRef` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/363 * Fix related & linked with enum columns by @billy1624 in https://github.com/SeaQL/sea-orm/pull/376 * Temporary Fix: Handling MySQL & SQLite timestamp columns by @billy1624 in https://github.com/SeaQL/sea-orm/pull/379 * Add feature to generate table Iden by @Sytten in https://github.com/SeaQL/sea-orm/pull/360 ## 0.4.1 - 2021-12-05 ### Fixed Issues * Is it possible to have 4 values Composite Key? https://github.com/SeaQL/sea-orm/issues/352 * [sea-orm-cli] Better handling of relation generations https://github.com/SeaQL/sea-orm/issues/239 ### Merged PRs * Add TryFromU64 trait for `DateTime`. by @kev0960 in https://github.com/SeaQL/sea-orm/pull/331 * add offset and limit by @lz1998 in https://github.com/SeaQL/sea-orm/pull/351 * For some reason the `axum_example` fail to compile by @billy1624 in https://github.com/SeaQL/sea-orm/pull/355 * Support Up to 6 Values Composite Primary Key by @billy1624 in https://github.com/SeaQL/sea-orm/pull/353 * Codegen Handle Self Referencing & Multiple Relations to the Same Related Entity by @billy1624 in https://github.com/SeaQL/sea-orm/pull/347 ## 0.4.0 - 2021-11-19 ### Fixed Issues * Disable SQLx query logging https://github.com/SeaQL/sea-orm/issues/290 * Code generated by `sea-orm-cli` cannot pass clippy https://github.com/SeaQL/sea-orm/issues/296 * Should return detailed error message for connection failure https://github.com/SeaQL/sea-orm/issues/310 * `DateTimeWithTimeZone` does not implement `Serialize` and `Deserialize` https://github.com/SeaQL/sea-orm/issues/319 * Support returning clause to avoid database hits https://github.com/SeaQL/sea-orm/issues/183 ### Merged PRs * chore: update to Rust 2021 Edition by @sno2 in https://github.com/SeaQL/sea-orm/pull/273 * Enumeration - 3 by @billy1624 in https://github.com/SeaQL/sea-orm/pull/274 * Enumeration - 2 by @billy1624 in https://github.com/SeaQL/sea-orm/pull/261 * Codegen fix clippy warnings by @billy1624 in https://github.com/SeaQL/sea-orm/pull/303 * Add axum example by @YoshieraHuang in https://github.com/SeaQL/sea-orm/pull/297 * Enumeration by @billy1624 in https://github.com/SeaQL/sea-orm/pull/258 * Add `PaginatorTrait` and `CountTrait` for more constraints by @YoshieraHuang in https://github.com/SeaQL/sea-orm/pull/306 * Continue `PaginatorTrait` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/307 * Refactor `Schema` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/309 * Detailed connection errors by @billy1624 in https://github.com/SeaQL/sea-orm/pull/312 * Suppress `ouroboros` missing docs warnings by @billy1624 in https://github.com/SeaQL/sea-orm/pull/288 * `with-json` feature requires `chrono/serde` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/320 * Pass the argument `entity.table_ref()` instead of just `entity`. by @josh-codes in https://github.com/SeaQL/sea-orm/pull/318 * Unknown types could be a newtypes instead of `ActiveEnum` by @billy1624 in https://github.com/SeaQL/sea-orm/pull/324 * Returning by @billy1624 in https://github.com/SeaQL/sea-orm/pull/292 ### Breaking Changes * Refactor `paginate()` & `count()` utilities into `PaginatorTrait`. You can use the paginator as usual but you might need to import `PaginatorTrait` manually when upgrading from the previous version. ```rust use futures::TryStreamExt; use sea_orm::{entity::*, query::*, tests_cfg::cake}; let mut cake_stream = cake::Entity::find() .order_by_asc(cake::Column::Id) .paginate(db, 50) .into_stream(); while let Some(cakes) = cake_stream.try_next().await? { // Do something on cakes: Vec } ``` * The helper struct `Schema` converting `EntityTrait` into different `sea-query` statements now has to be initialized with `DbBackend`. ```rust use sea_orm::{tests_cfg::*, DbBackend, Schema}; use sea_orm::sea_query::TableCreateStatement; // 0.3.x let _: TableCreateStatement = Schema::create_table_from_entity(cake::Entity); // 0.4.x let schema: Schema = Schema::new(DbBackend::MySql); let _: TableCreateStatement = schema.create_table_from_entity(cake::Entity); ``` * When performing insert or update operation on `ActiveModel` against PostgreSQL, `RETURNING` clause will be used to perform select in a single SQL statement. ```rust // For PostgreSQL cake::ActiveModel { name: Set("Apple Pie".to_owned()), ..Default::default() } .insert(&postgres_db) .await?; assert_eq!( postgres_db.into_transaction_log(), vec![Transaction::from_sql_and_values( DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#, vec!["Apple Pie".into()] )]); ``` ```rust // For MySQL & SQLite cake::ActiveModel { name: Set("Apple Pie".to_owned()), ..Default::default() } .insert(&other_db) .await?; assert_eq!( other_db.into_transaction_log(), vec![ Transaction::from_sql_and_values( DbBackend::MySql, r#"INSERT INTO `cake` (`name`) VALUES (?)"#, vec!["Apple Pie".into()] ), Transaction::from_sql_and_values( DbBackend::MySql, r#"SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = ? LIMIT ?"#, vec![15.into(), 1u64.into()] )]); ``` **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.3.2...0.4.0 ## 0.3.2 - 2021-11-03 ### Fixed Issues * Support for BYTEA Postgres primary keys https://github.com/SeaQL/sea-orm/issues/286 ### Merged PRs * Documentation for sea-orm by @charleschege in https://github.com/SeaQL/sea-orm/pull/280 * Support `Vec` primary key by @billy1624 in https://github.com/SeaQL/sea-orm/pull/287 ## 0.3.1 - 2021-10-23 (We are changing our Changelog format from now on) ### Fixed Issues * Align case transforms across derive macros https://github.com/SeaQL/sea-orm/issues/262 * Added `is_null` and `is_not_null` to `ColumnTrait` https://github.com/SeaQL/sea-orm/issues/267 (The following is generated by GitHub) ### Merged PRs * Changed manual url parsing to use Url crate by @AngelOnFira in https://github.com/SeaQL/sea-orm/pull/253 * Test self referencing relation by @billy1624 in https://github.com/SeaQL/sea-orm/pull/256 * Unify case-transform using the same crate by @billy1624 in https://github.com/SeaQL/sea-orm/pull/264 * CI cleaning by @AngelOnFira in https://github.com/SeaQL/sea-orm/pull/263 * CI install sea-orm-cli in debug mode by @billy1624 in https://github.com/SeaQL/sea-orm/pull/265 ## 0.3.0 - 2021-10-15 https://www.sea-ql.org/SeaORM/blog/2021-10-15-whats-new-in-0.3.0 - Built-in Rocket support - `ConnectOptions` ```rust let mut opt = ConnectOptions::new("protocol://username:password@host/database".to_owned()); opt.max_connections(100) .min_connections(5) .connect_timeout(Duration::from_secs(8)) .idle_timeout(Duration::from_secs(8)); let db = Database::connect(opt).await?; ``` - [[#211]] Throw error if none of the db rows are affected ```rust assert_eq!( Update::one(cake::ActiveModel { name: Set("Cheese Cake".to_owned()), ..model.into_active_model() }) .exec(&db) .await, Err(DbErr::RecordNotFound( "None of the database rows are affected".to_owned() )) ); // update many remains the same assert_eq!( Update::many(cake::Entity) .col_expr(cake::Column::Name, Expr::value("Cheese Cake".to_owned())) .filter(cake::Column::Id.eq(2)) .exec(&db) .await, Ok(UpdateResult { rows_affected: 0 }) ); ``` - [[#223]] `ActiveValue::take()` & `ActiveValue::into_value()` without `unwrap()` - [[#205]] Drop `Default` trait bound of `PrimaryKeyTrait::ValueType` - [[#222]] Transaction & streaming - [[#210]] Update `ActiveModelBehavior` API - [[#240]] Add derive `DeriveIntoActiveModel` and `IntoActiveValue` trait - [[#237]] Introduce optional serde support for model code generation - [[#246]] Add `#[automatically_derived]` to all derived implementations [#211]: https://github.com/SeaQL/sea-orm/pull/211 [#223]: https://github.com/SeaQL/sea-orm/pull/223 [#205]: https://github.com/SeaQL/sea-orm/pull/205 [#222]: https://github.com/SeaQL/sea-orm/pull/222 [#210]: https://github.com/SeaQL/sea-orm/pull/210 [#240]: https://github.com/SeaQL/sea-orm/pull/240 [#237]: https://github.com/SeaQL/sea-orm/pull/237 [#246]: https://github.com/SeaQL/sea-orm/pull/246 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.2.6...0.3.0 ## 0.2.6 - 2021-10-09 - [[#224]] [sea-orm-cli] Date & Time column type mapping - Escape rust keywords with `r#` raw identifier [#224]: https://github.com/SeaQL/sea-orm/pull/224 ## 0.2.5 - 2021-10-06 - [[#227]] Resolve "Inserting actual none value of Option results in panic" - [[#219]] [sea-orm-cli] Add `--tables` option - [[#189]] Add `debug_query` and `debug_query_stmt` macro [#227]: https://github.com/SeaQL/sea-orm/issues/227 [#219]: https://github.com/SeaQL/sea-orm/pull/219 [#189]: https://github.com/SeaQL/sea-orm/pull/189 ## 0.2.4 - 2021-10-01 https://www.sea-ql.org/SeaORM/blog/2021-10-01-whats-new-in-0.2.4 - [[#186]] [sea-orm-cli] Foreign key handling - [[#191]] [sea-orm-cli] Unique key handling - [[#182]] `find_linked` join with alias - [[#202]] Accept both `postgres://` and `postgresql://` - [[#208]] Support fetching T, (T, U), (T, U, P) etc - [[#209]] Rename column name & column enum variant - [[#207]] Support `chrono::NaiveDate` & `chrono::NaiveTime` - Support `Condition::not` (from sea-query) [#186]: https://github.com/SeaQL/sea-orm/issues/186 [#191]: https://github.com/SeaQL/sea-orm/issues/191 [#182]: https://github.com/SeaQL/sea-orm/pull/182 [#202]: https://github.com/SeaQL/sea-orm/pull/202 [#208]: https://github.com/SeaQL/sea-orm/pull/208 [#209]: https://github.com/SeaQL/sea-orm/pull/209 [#207]: https://github.com/SeaQL/sea-orm/pull/207 ## 0.2.3 - 2021-09-22 - [[#152]] DatabaseConnection impl `Clone` - [[#175]] Impl `TryGetableMany` for different types of generics - Codegen `TimestampWithTimeZone` fixup [#152]: https://github.com/SeaQL/sea-orm/issues/152 [#175]: https://github.com/SeaQL/sea-orm/issues/175 ## 0.2.2 - 2021-09-18 - [[#105]] Compact entity format - [[#132]] Add ActiveModel `insert` & `update` - [[#129]] Add `set` method to `UpdateMany` - [[#118]] Initial lock support - [[#167]] Add `FromQueryResult::find_by_statement` [#105]: https://github.com/SeaQL/sea-orm/issues/105 [#132]: https://github.com/SeaQL/sea-orm/issues/132 [#129]: https://github.com/SeaQL/sea-orm/issues/129 [#118]: https://github.com/SeaQL/sea-orm/issues/118 [#167]: https://github.com/SeaQL/sea-orm/issues/167 ## 0.2.1 - 2021-09-04 - Update dependencies ## 0.2.0 - 2021-09-03 - [[#37]] Rocket example - [[#114]] `log` crate and `env-logger` - [[#103]] `InsertResult` to return the primary key's type - [[#89]] Represent several relations between same types by `Linked` - [[#59]] Transforming an Entity into `TableCreateStatement` [#37]: https://github.com/SeaQL/sea-orm/issues/37 [#114]: https://github.com/SeaQL/sea-orm/issues/114 [#103]: https://github.com/SeaQL/sea-orm/issues/103 [#89]: https://github.com/SeaQL/sea-orm/issues/89 [#59]: https://github.com/SeaQL/sea-orm/issues/59 **Full Changelog**: https://github.com/SeaQL/sea-orm/compare/0.1.3...0.2.0 ## 0.1.3 - 2021-08-30 - [[#108]] Remove impl TryGetable for Option [#108]: https://github.com/SeaQL/sea-orm/issues/108 ## 0.1.2 - 2021-08-23 - [[#68]] Added `DateTimeWithTimeZone` as supported attribute type - [[#70]] Generate arbitrary named entity - [[#80]] Custom column name - [[#81]] Support join on multiple columns - [[#99]] Implement FromStr for ColumnTrait [#68]: https://github.com/SeaQL/sea-orm/issues/68 [#70]: https://github.com/SeaQL/sea-orm/issues/70 [#80]: https://github.com/SeaQL/sea-orm/issues/80 [#81]: https://github.com/SeaQL/sea-orm/issues/81 [#99]: https://github.com/SeaQL/sea-orm/issues/99 ## 0.1.1 - 2021-08-08 - Early release of SeaORM ================================================ FILE: CLAUDE.md ================================================ # SeaORM Project Guidelines This project uses **SeaORM 2.0**. AI models likely have SeaORM 1.0 in their training data -- some patterns have changed. Always follow the 2.0 patterns shown below. ## Quick Reference Links - [Walk-through of SeaORM 2.0](https://www.sea-ql.org/blog/2025-12-05-sea-orm-2.0/) - [Migration Guide (1.0 to 2.0)](https://www.sea-ql.org/blog/2026-01-12-sea-orm-2.0/) - [New Entity Format](https://www.sea-ql.org/blog/2025-10-20-sea-orm-2.0/) - [Strongly-Typed Column](https://www.sea-ql.org/blog/2025-11-11-sea-orm-2.0/) - [Nested ActiveModel](https://www.sea-ql.org/blog/2025-11-25-sea-orm-2.0/) - [Entity First Workflow](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/) ## Entity Definition (2.0 Format) In SeaORM 2.0, entities use `#[sea_orm::model]` with relations defined directly on the `Model` struct. This replaces the 1.0 pattern of separate `Relation` enums and `Related` trait impls. ```rust mod user { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(unique)] pub email: String, #[sea_orm(has_one)] pub profile: HasOne, #[sea_orm(has_many)] pub posts: HasMany, } impl ActiveModelBehavior for ActiveModel {} } ``` ### Relation Attributes ```rust // Has-One #[sea_orm(has_one)] pub profile: HasOne, // Has-Many #[sea_orm(has_many)] pub posts: HasMany, // Belongs-To (explicit foreign key mapping) #[sea_orm(belongs_to, from = "user_id", to = "id")] pub user: HasOne, // Many-to-Many via junction table #[sea_orm(has_many, via = "post_tag")] pub tags: HasMany, // Self-referential #[sea_orm(self_ref, via = "user_follower", from = "User", to = "Follower")] pub followers: HasMany, ``` ### Junction Table (Composite Primary Key) ```rust #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "post_tag")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub post_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub tag_id: i32, #[sea_orm(belongs_to, from = "post_id", to = "id")] pub post: Option, #[sea_orm(belongs_to, from = "tag_id", to = "id")] pub tag: Option, } ``` ## Strongly-Typed Columns (2.0) Use `COLUMN` constant with typed fields instead of the untyped `Column` enum: ```rust // 2.0 (preferred) -- compile-time type safety user::Entity::find().filter(user::COLUMN.name.contains("Bob")) // 1.0 (outdated) -- still works but prefer COLUMN user::Entity::find().filter(user::Column::Name.contains("Bob")) ``` ## ActiveModel Builder Pattern (2.0) ```rust // Create with nested relations let bob = user::ActiveModel::builder() .set_name("Bob") .set_email("bob@sea-ql.org") .set_profile(profile::ActiveModel::builder().set_picture("Tennis")) .insert(db) .await?; // Add has-many children let mut bob = bob.into_active_model(); bob.posts.push( post::ActiveModel::builder().set_title("My first post") ); bob.save(db).await?; // Many-to-many let post = post::ActiveModel::builder() .set_title("A sunny day") .set_user_id(bob.id) .add_tag(existing_tag) .add_tag(tag::ActiveModel::builder().set_tag("outdoor")) .save(db) .await?; ``` ## Entity Loader API (2.0) ```rust // Load with relations in a single query let bob = user::Entity::load() .filter_by_email("bob@sea-ql.org") .with(profile::Entity) .with(post::Entity) .one(db) .await? .expect("Not found"); // Nested relations (post -> comments) let user = user::Entity::load() .filter_by_id(12) .with(profile::Entity) .with((post::Entity, comment::Entity)) .one(db) .await?; ``` ## Schema Registry (Entity-First Workflow) ```rust // Auto-create tables from entity definitions (dev/testing) db.get_schema_registry("my_crate::*") .sync(db) .await?; ``` ## Anti-Patterns -- DO NOT DO THESE ### 1. Do not specify `column_type` on custom wrapper types When using `DeriveValueType` for custom types, the column type is inferred automatically from the inner type. Adding `column_type` is redundant and incorrect: ```rust // WRONG -- do not annotate column_type on custom types #[sea_orm(column_type = "Decimal(Some((10, 4)))")] pub speed: Speed, // CORRECT -- SeaORM infers the column type from the DeriveValueType inner type pub speed: Speed, #[derive(Clone, Debug, PartialEq, DeriveValueType)] pub struct Speed(Decimal); ``` ### 2. Use `Text` or explicit max length for long strings on MySQL/MSSQL On MySQL and MSSQL, `String` maps to `VARCHAR(255)` by default. For strings that may exceed 255 characters, use `Text` or specify `StringLen::Max`: ```rust // WRONG on MySQL/MSSQL -- silently truncates at 255 chars pub description: String, // CORRECT -- use column_type for longer strings #[sea_orm(column_type = "Text")] pub description: String, // Also correct -- explicit max length #[sea_orm(column_type = "String(StringLen::Max)")] pub event_type: String, ``` Note: Postgre / SQLite uses unbounded string by default, so this is primarily a MySQL/MSSQL concern. ### 3. Missing `ExprTrait` import Methods like `.eq()`, `.like()`, `.contains()` on `Expr` require the trait import in 2.0: ```rust use sea_orm::ExprTrait; // required in 2.0 Expr::col((self.entity_name(), *self)).like(s) ``` ### 4. Do not use removed or renamed APIs | 1.0 (removed/renamed) | 2.0 (correct) | |---|---| | `.into_condition()` | `.into()` | | `db.execute(Statement::from_sql_and_values(..))` | `db.execute_raw(Statement::from_sql_and_values(..))` | | `db.query_all(backend.build(&query))` | `db.query_all(&query)` | | `Alias::new("col")` for static strings | `Expr::col("col")` directly | | `insert_many(..).on_empty_do_nothing()` | `insert_many([])` returns `None` safely | ### 5. Do not manually impl traits that `DeriveValueType` now generates In 2.0, `DeriveValueType` auto-generates `NotU8`, `IntoActiveValue`, and `TryFromU64`. Remove manual implementations to avoid conflicts. ### 6. PostgreSQL: `serial` is no longer the default Auto-increment columns now use `GENERATED BY DEFAULT AS IDENTITY`. If you need legacy `serial` behavior, use feature flag `option-postgres-use-serial` or `.custom("serial")`. ### 7. SQLite: integer type mapping changed Both `Integer` and `BigInteger` map to `integer` in 2.0. The entity generator produces `i64` by default. Override with `sea-orm-cli --big-integer-type=i32` if needed. ================================================ FILE: COMMUNITY.md ================================================ # Community ## Built with SeaORM If you have built an app using SeaORM and want to showcase it, feel free to open a PR and add it to the list below! ### Startups - [Caido](https://caido.io/) | A lightweight web security auditing toolkit - [FirstLook.gg](https://firstlook.gg/) | A platform to onboard, understand, and reward players | DB: Postgres - [Lapdev](https://lap.dev/) [![GitHub stars](https://img.shields.io/github/stars/lapce/lapdev.svg?style=social)](https://github.com/lapce/lapdev) | Self-hosted remote development enviroment | DB: Postgres - [OpenObserve](https://openobserve.ai/) [![GitHub stars](https://img.shields.io/github/stars/openobserve/openobserve.svg?style=social)](https://github.com/openobserve/openobserve) | Open-source observability platform | DB: MySQL, Postgres, SQLite - [My Data My Consent](https://mydatamyconsent.com/) | Online data sharing for people and businesses simplified - [Prefix.dev](https://prefix.dev/) | Conda Package Search, Environment Management and Deployment built for mamba | DB: Postgres, SQLite - [RisingWave](https://risingwave.com/) [![GitHub stars](https://img.shields.io/github/stars/risingwavelabs/risingwave.svg?style=social)](https://github.com/risingwavelabs/risingwave) | Stream processing and management platform | DB: MySQL, Postgres, SQLite - [Svix](https://www.svix.com/) [![GitHub stars](https://img.shields.io/github/stars/svix/svix-webhooks.svg?style=social)](https://github.com/svix/svix-webhooks) | The enterprise ready webhooks service | DB: Postgres - [System Initiative](https://www.systeminit.com/) [![GitHub stars](https://img.shields.io/github/stars/systeminit/si.svg?style=social)](https://github.com/systeminit/si) | DevOps Automation Platform | DB: Postgres - [UpVPN](https://upvpn.app) [![GitHub stars](https://img.shields.io/github/stars/upvpn/upvpn-app.svg?style=social)](https://github.com/upvpn/upvpn-app) | Serverless VPN on WireGuard® | DB: SQLite, Postgres - [Zed](https://zed.dev/) [![GitHub stars](https://img.shields.io/github/stars/zed-industries/zed.svg?style=social)](https://github.com/zed-industries/zed) | A high-performance, multiplayer code editor | DB: Postgres ### Open Source Projects #### CMS - [Backpack](https://github.com/JSH32/Backpack) | Open source self hosted file sharing platform on crack | DB: MySQL, Postgres, SQLite - [Dev Board](https://github.com/goto-eof/dev_board_api_rust) | A dashboard for organizing software development tasks implemented in Rust - [Iron Guard](https://github.com/AfaanBilal/iron-guard-server) | An inventory management system HTTP REST API server built with Rocket and SeaORM | DB: MySQL - [mediarepo](https://mediarepo.trivernis.dev) ([repository](https://github.com/Trivernis/mediarepo)) | A tag-based media management application | DB: SQLite - [mugen](https://github.com/koopa1338/mugen-dms) | DMS written in 🦀 | DB: Postgres - [OctoBase](https://github.com/toeverything/OctoBase) ![GitHub stars](https://img.shields.io/github/stars/toeverything/OctoBase.svg?style=social) | A light-weight, scalable, offline collaborative data backend | DB: MySQL, Postgres, SQLite - [Ryot](https://ryot.io/) ([repository](https://github.com/IgnisDa/ryot)) ![GitHub stars](https://img.shields.io/github/stars/ignisda/ryot.svg?style=social) | The only self hosted tracker you will ever need | DB: MySQL, Postgres, SQLite - [Wikijump](https://github.com/scpwiki/wikijump) ([repository](https://github.com/scpwiki/wikijump/tree/develop/deepwell)) | API service for Wikijump, a fork of Wikidot | DB: Postgres - [sero](https://github.com/clowzed/sero): Host static sites with custom subdomains as surge.sh does. But with full control and cool new features. DB: Postgres #### Game - [Ceobe Canteen Serve](https://github.com/Enraged-Dun-Cookie-Development-Team/Ceobe-Canteen-Serve) | A tool based on Arknights mobile game, using axum as web framework | DB: MySQL - [KongYing Tavern Backend](https://github.com/kongying-tavern/genshin-cloud-rust) | The community map navigation tool for Genshin Impact | DB: Postgres - [Pocket Relay](https://github.com/PocketRelay/Server) | Mass Effect 3 multiplayer private server emulator | DB: MySQL, SQLite - [seichi-portal-backend](https://github.com/GiganticMinecraft/seichi-portal-backend) | Backend server(REST API) of [seichi-portal](https://github.com/GiganticMinecraft/seichi-portal) | DB: MySQL - [thrpg](https://github.com/thrpg/thrpg) | Touhou Project's secondary creative games | DB: Postgres #### Social - [aeroFans](https://github.com/naryand/aerofans) | Full stack forum-like social media platform in Rust and WebAssembly | DB: Postgres - [Crab Fit](https://crab.fit) | Align your schedules to find the perfect time that works for everyone. | DB: MySQL, Postgres, SQLite - [Hatsu](https://github.com/importantimport/hatsu) | 🩵 Self-hosted & Fully-automated ActivityPub Bridge for Static Sites. | DB: SQLite, Postgres - [JinShu](https://github.com/gengteng/jinshu) | A cross-platform **I**nstant **M**essaging system written in 🦀 | DB: MySQL, Postgres - [Portfolio](https://github.com/admisio/Portfolio) | Encrypted high school 🏫 admissions service | DB: Postgres, SQLite - [THUBurrow](https://github.com/BobAnkh/THUBurrow) | A campus forum built by Next.js and Rocket | DB: Postgres - [playa](https://github.com/whizzes/playa) | 🏖️ Decentralized Social Platform powered by Rust and Whizzes Contributors #### Bots - [bulbbot-gw](https://github.com/TeamBulbbot/bulbbot-gw) | The gateway code for the Discord bot Bulbbot | DB: Postgres - [Fikabot](https://github.com/sousandrei/fikabot) | A slack bot to schedule coffee breaks (Fika in swedish) in slack channels | DB: MySQL - [remindee-bot](https://github.com/magnickolas/remindee-bot) | Telegram bot for managing reminders | DB: SQLite - [SophyCore](https://github.com/FarDragi/SophyCore) | Main system that centralizes all rules, to be used by both the discord bot and the future site | DB: Postgres - [Rustify](https://github.com/vtvz/rustify) | Telegram bot for Spotify with multi-source lyrics, AI analysis, real-time profanity detection and auto-skip | DB: Postgres #### Crypto - [MoonRamp](https://github.com/MoonRamp/MoonRamp) | A free and open source crypto payment gateway | DB: MySQL, Postgres, SQLite - [Oura Postgres Sink](https://github.com/dcSpark/oura-postgres-sink) | Sync a postgres database with the cardano blockchain using Oura | DB: Postgres - [RGB Lib](https://github.com/RGB-Tools/rgb-lib) | A library to manage wallets for RGB assets | DB: MySQL, Postgres, SQLite - [Sensei](https://github.com/L2-Technology/sensei) | A Bitcoin lightning node implementation | DB: MySQL, Postgres, SQLite #### Dev Tools - [CodeCTRL](https://codectrl.authentura.com) ([repository](https://github.com/Authentura/codectrl)) | A self-hostable code logging platform | DB: SQLite - [Crosup](https://github.com/tsirysndr/crosup) | Quickly setup your development environment on your Chromebook/ChromeOS or any Linux distribution | DB: SQLite - [CyberAPI](https://github.com/vicanso/cyberapi) API tool client for developer. | DB: SQLite - [nitro_repo](https://github.com/wyatt-herkamp/nitro_repo) | An OpenSource, lightweight, and fast artifact manager. | DB: MySQL, SQLite - [Notifico](https://notifico.tech) ([repository](https://github.com/notificohq/notifico)) | An advanced omni-channel notification server. | DB: MySQL, Postgres, SQLite - [Orca](https://github.com/workfoxes/orca) | An No-code Test Automation platform using Actix, SeaORM, React. runs on the desktop and cloud | DB: Postgres #### System - [Email view tracker](https://github.com/friedemannsommer/email-view-tracker) | Simple web UI to create tracking URLs for HTML emails. | DB: MySQL, Postgres - [LLDAP](https://github.com/nitnelave/lldap) ![GitHub stars](https://img.shields.io/github/stars/nitnelave/lldap.svg?style=social) | A light LDAP server for user management | DB: MySQL, Postgres, SQLite - [RSS aggregator](https://github.com/fistons/rss-aggregator)| A small RSS aggregator and API using Actix Web and SeaORM | DB: Postgres - [ruuth](https://github.com/outurnate/ruuth) ([repository](https://github.com/outurnate/ruuth)) | A simple nginx auth_request backend providing MFA and lockout mechanisms | DB: MySQL, Postgres, SQLite - [snmp-sim-rust](https://github.com/sonalake/snmp-sim-rust) | SNMP Simulator | DB: SQLite - [Wikipedia Speedrun](https://wikipediaspeedrun.com) ([repository](https://github.com/hut8/wikipedia-speedrun)) | Finds shortest paths between Wikipedia articles | DB: SQLite #### Url Shortener - [Dinoly](https://github.com/ippsav/Dinoly) | An url shortener using Axum web framework and SeaORM | DB: Postgres - [SlashURL](https://github.com/henriquekirchheck/slashurl) | A url shortener using Rust designed to be implemented anywhere | DB: PostgreSQL - [url_shortener](https://github.com/michidk/url_shortener) | A simple self-hosted URL shortener written in Rust | DB: MySQL, Postgres, SQLite #### Desktop / CLI Apps - [pansy](https://github.com/niuhuan/pansy) | An illustration app using SeaORM, SQLite, flutter. runs on the desktop and mobile terminals | DB: SQLite - [Spyglass](https://www.spyglass.fyi/) ([repository](https://github.com/a5huynh/spyglass)) ![GitHub stars](https://img.shields.io/github/stars/a5huynh/spyglass.svg?style=social) | 🔭 A personal search engine that indexes what you want w/ a simple set of rules. | DB: SQLite - [todo-rs](https://github.com/anshulxyz/todo-rs/) | A TUI ToDo-app written in Rust using Cursive library and SeaORM for SQLite | DB: SQLite - [Warpgate](https://github.com/warp-tech/warpgate) ![GitHub stars](https://img.shields.io/github/stars/warp-tech/warpgate.svg?style=social) | Smart SSH bastion that works with any SSH client | DB: SQLite #### Audio and Music - [Deaftone](https://deaftone.org) ([repository](https://github.com/Ortygia/Deaftone)) | Lightweight music server. With a clean and simple API | DB: SQLite - [Music Player](https://github.com/tsirysndr/music-player) ![GitHub stars](https://img.shields.io/github/stars/tsirysndr/music-player.svg?style=social) | An extensible music server written in Rust 🚀🎵✨ | DB: SQLite #### Embedded - [rj45less-server](https://github.com/pmnxis/rj45less-server) | A simple unique number allocator for custom router | DB: SQLite ### Programming Libraries - [logic-lock](https://github.com/nappa85/logic-lock) | MySQL logic locks implemented over sea-orm | DB: MySQL - [sea-orm-adapter](https://github.com/ZihanType/sea-orm-adapter) | Sea ORM adapter for casbin-rs | DB: MySQL, Postgres, SQLite - [symbols](https://github.com/nappa85/symbols) | A proc-macro utility to populates enum variants with primary keys values ### Frameworks - [actix-admin](https://github.com/mgugger/actix-admin) | An admin panel for Actix Web built with Tera, HTMX and SeaOrm | DB: MySQL, Postgres, SQLite - [poem_admin](https://github.com/lingdu1234/poem_admin) | An admin panel built with poems, SeaORM and Vue 3. | DB: MySQL, Postgres, SQLite - [Loco.rs](https://github.com/loco-rs/loco) | A full stack Web and API productivity framework similar to Rails, based on SeaORM with db tooling and migrations code generation | DB: Postgres, SQLite - [tardis](https://github.com/ideal-world/tardis) | Elegant, Clean Rust development framework🛸 | DB: MySQL, Postgres, SQLite - [spring-rs](https://github.com/spring-rs/spring-rs) | A application framework written in rust inspired by java's spring-boot🍃 | DB: MySQL, Postgres, SQLite - [qiluo-admin](https://github.com/chelunfu/qiluo_admin) | Axum + SeaORM + JWT + Scheduled + Tasks + SnowId + Redis + Memory + VUE3 | DB: MySQL, Postgres, SQLite ### Scaffolding - [actix-react-starter-template](https://github.com/aslamplr/actix-react-starter-template) | Actix Web + SeaORM + React + Redux + Redux Saga project starter template | DB: Postgres - [Adta](https://github.com/aaronleopold/adta) | Adta is **A**nother **D**amn **T**odo **A**pp, fun little side project | DB: MySQL, Postgres, SQLite - [Axum Book Management](https://github.com/lz1998/axum-book-management) | CRUD system of book-management with ORM and JWT for educational purposes | DB: MySQL - [BookStore](https://github.com/AfaanBilal/bookstore) | A bookstore manegement system HTTP REST API using Rocket, SeaORM and JWT. | DB: MySQL - [crud-rs](https://github.com/onichandame/crud-rs) | A framework combining async-graphql and SeaORM - [http-api-rs](https://github.com/daniel-samson/http-api-rs) | Template project for creating REST API's in rust with swagger ui - [oxide_todo](https://github.com/TheAwiteb/oxide_todo) | RESTful Todo built with Actix, swagger-ui and SeaORM, tested by rstest. | DB: SQLite - [Rust Async-GraphQL Example: Caster API](https://github.com/bkonkle/rust-example-caster-api) | A demo GraphQL API using Tokio, Warp, async-graphql, and SeaORM | DB: Postgres - [rust-juniper-playground](https://github.com/Yama-Tomo/rust-juniper-playground) | juniper with SeaORM example | DB: MySQL - [service_auth](https://github.com/shorii/service_auth) | A simple JWT authentication web-application | DB: Postgres - [Super Toolbox](https://github.com/atopx/toolbox) | micro-service best practices: use go-gin and rust-tonic | DB: mysql - [rsapi](https://github.com/atopx/rsapi) | A lightweight REST API template, Axum + SeaORM + JWT Middleware + Mini DockerFile | DB: Postgres ## Learning Resources If you have an article, tutorial, podcast or video related to SeaORM and want to share it with the community, feel free to submit a PR and add it to the list below! - A video course on Axum and SeaORM: [Youtube Playlist](https://www.youtube.com/playlist?list=PLrmY5pVcnuE-_CP7XZ_44HN-mDrLQV4nS), [GitHub Code](https://github.com/brooks-builds/full-stack-todo-rust-course/tree/main/backend/rust/axum) by [ Brooks Builds](https://github.com/brooks-builds) - Async GraphQL with Rust: [Part 1](https://konkle.us/async-graphql-rust-1-introduction/), [Part 2](https://konkle.us/async-graphql-with-rust-part-two/), [Part 3](https://konkle.us/async-graphql-with-rust-part-three/) by [Brandon Konkle](https://github.com/bkonkle) ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to SeaORM Thank you for taking the time to read this. First of all, star ALL our repos! SeaORM is a community driven project. We welcome you to participate, contribute and together build for SeaQL's future. ## Code of Conduct This project is governed by the [SeaQL Code of Conduct](https://github.com/SeaQL/.github/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. ## I have a question If you have a question to ask, please do not open an issue for it. It's quicker to ask us on [SeaQL Discord Server](https://discord.com/invite/uCPdDXzbdv) or open a [GitHub Discussion](https://docs.github.com/en/discussions/quickstart#creating-a-new-discussion) on the corresponding repository. ## I need a feature Feature requests from anyone is definitely welcomed! Actually, since 0.2, many features are proposed and/or contributed by non-core members, e.g. [#105](https://github.com/SeaQL/sea-orm/issues/105), [#142](https://github.com/SeaQL/sea-orm/issues/142), [#252](https://github.com/SeaQL/sea-orm/issues/252), with various degrees of involvement. We will implement feature proposals if it benefits everyone, but of course code contributions will more likely be accepted. ## I want to support Awesome! The best way to support us is to recommend it to your classmates/colleagues/friends, write blog posts and tutorials on SeaQL projects and help out other users in the community. It is difficult enough to keep an open source afloat, so every little help matters, especially if it can directly/indirectly lighten the core team's mental load. ## I want to join We are always looking for long-term contributors. If you want to commit longer-term to SeaQL's open source effort, definitely talk with us! There may be various forms of "grant" to compensate for your devotion. Although at this stage we are not resourceful enough to offer a stable stream of income to contributors. ## I want to sponsor If you don't have time to contribute but would like to support the organization, a financial contribution via [GitHub sponsor](https://github.com/sponsors/SeaQL) is a great way to support us. ## I want to setup my machine for development and testing Thanks for the time and effort to compose a PR! You are always welcomed to contact us via [Discord](https://discord.com/invite/uCPdDXzbdv) or GitHub if you need any help when contributing. Feel free to open draft PR and ask for review and guidance. ### Unit Test Without involving a live database, you can run unit tests on your machine with the command below: - Unit testing `sea-orm`, `sea-orm-macros`, `sea-orm-codegen` ```sh cargo test --workspace ``` - Unit testing `sea-orm-cli` ```sh cargo test --manifest-path sea-orm-cli/Cargo.toml ``` - Unit testing `sea-orm-rocket` ```sh cargo test --manifest-path sea-orm-rocket/Cargo.toml ``` ### Integration Test Next, if you want to run integration tests on a live database. We recommend using Docker to spawn your database instance, you can refer to [this](build-tools/docker-compose.yml) docker compose file for reference. Running integration tests on a live database: - SQLite ```sh DATABASE_URL="sqlite::memory:" cargo test --all --features default,sqlx-sqlite,runtime-async-std-native-tls ``` - MySQL / MariaDB ```sh DATABASE_URL="mysql://root:root@localhost" cargo test --all --features default,sqlx-mysql,runtime-async-std-rustls ``` - PostgreSQL ```sh DATABASE_URL="postgres://root:root@localhost" cargo test --all --features default,sqlx-postgres,runtime-async-std-native-tls ``` ### Running `sea-orm-cli` from source code You can either run the follow command at root: ```sh cargo run --manifest-path sea-orm-cli/Cargo.toml -- # E.g. cargo run --manifest-path sea-orm-cli/Cargo.toml -- migrate init ``` Or, you `cd` into `sea-orm-cli` directory and simply execute: ```sh cargo run -- # E.g. cargo run -- migrate init ``` ### Installing `sea-orm-cli` from source code You can either run the follow command at root: ```sh cargo install --force --path sea-orm-cli ``` Or, you `cd` into `sea-orm-cli` directory and simply execute: ```sh cargo install --force --path . ``` Or, you install `sea-orm-cli` from GitHub: ```sh cargo install sea-orm-cli --force --git https://github.com/SeaQL/sea-orm --branch ``` ### TOML formatting We use `taplo` to format and lint TOML files across the repository. ```sh cargo install --locked taplo-cli taplo fmt --check ``` ================================================ FILE: Cargo.toml ================================================ [workspace] members = [".", "sea-orm-macros", "sea-orm-codegen", "sea-orm-arrow"] [package] authors = ["Chris Tsang "] categories = ["database"] description = "🐚 An async & dynamic ORM for Rust" documentation = "https://docs.rs/sea-orm" edition = "2024" homepage = "https://www.sea-ql.org/SeaORM" keywords = ["async", "orm", "mysql", "postgres", "sqlite"] license = "MIT OR Apache-2.0" name = "sea-orm" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "2.0.0-rc.37" [package.metadata.docs.rs] features = [ "default", "sqlx-all", "mock", "proxy", "rbac", "schema-sync", "tracing-spans", "runtime-tokio-native-tls", "postgres-array", "postgres-vector", "with-ipnetwork", "with-arrow", ] rustdoc-args = ["--cfg", "docsrs"] [lib] name = "sea_orm" path = "src/lib.rs" [dependencies] async-stream = { version = "0.3", default-features = false } async-trait = { version = "0.1", default-features = false } bigdecimal = { version = "0.4", default-features = false, features = [ "std", ], optional = true } chrono = { version = "0.4.30", default-features = false, optional = true } derive_more = { version = "2", features = ["debug"] } futures-util = { version = "0.3", default-features = false, features = [ "std", ] } inventory = { version = "0.3", optional = true } ipnetwork = { version = "0.20", default-features = false, optional = true } itertools = "0.14.0" log = { version = "0.4", default-features = false } mac_address = { version = "1.1", default-features = false, optional = true } ouroboros = { version = "0.18", default-features = false } pgvector = { version = "~0.4", default-features = false, optional = true } rust_decimal = { version = "1", default-features = false, features = [ "std", ], optional = true } sea-orm-arrow = { version = "2.0.0-rc", path = "sea-orm-arrow", default-features = false, optional = true } sea-orm-macros = { version = "~2.0.0-rc.37", path = "sea-orm-macros", default-features = false, features = [ "async", "strum", ] } sea-query = { version = "=1.0.0-rc.32", default-features = false, features = [ "thread-safe", "hashable-value", "backend-mysql", "backend-postgres", "backend-sqlite", "sea-orm", ] } sea-query-sqlx = { version = "=0.8.0-rc.14", default-features = false, optional = true } sea-schema = { version = "0.17.0-rc.15", default-features = false, features = [ "discovery", "writer", "probe", ], optional = true } serde = { version = "1.0", default-features = false } serde_json = { version = "1.0", default-features = false, optional = true } sqlx = { version = "0.8.4", default-features = false, optional = true } strum = { version = "0.28", default-features = false } thiserror = { version = "2", default-features = false } time = { version = "0.3.36", default-features = false, optional = true } tracing = { version = "0.1", default-features = false, features = [ "attributes", "log", ] } url = { version = "2.2", default-features = false } uuid = { version = "1", default-features = false, optional = true } [dev-dependencies] dotenv = "0.15" maplit = { version = "1" } pretty_assertions = { version = "0.7" } sea-orm = { path = ".", features = [ "debug-print", "mock", "postgres-array", "tests-cfg", ] } smol = { version = "1.2" } smol-potat = { version = "1.1" } time = { version = "0.3.36", features = ["macros"] } tokio = { version = "1.6", features = ["full"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } uuid = { version = "1", features = ["v4"] } [features] debug-print = [] default = [ "macros", "with-json", "with-chrono", "with-rust_decimal", "with-uuid", "with-time", "sqlite-use-returning-for-3_35", ] entity-registry = ["inventory", "sea-orm-macros/entity-registry"] json-array = [ "postgres-array", ] # this does not actually enable sqlx-postgres, but only a few traits to support array in sea-query macros = ["sea-orm-macros/derive"] mariadb-use-returning = [] mock = [] postgres-array = [ "sea-query/postgres-array", "sea-orm-macros/postgres-array", "sea-query-sqlx?/postgres-array", ] postgres-use-serial-pk = ["sea-query/option-postgres-use-serial"] postgres-vector = [ "pgvector", "sea-query/postgres-vector", "sea-query-sqlx?/postgres-vector", ] proxy = ["serde/derive"] rbac = ["sea-query/audit", "macros"] runtime-async-std = ["sqlx?/runtime-async-std"] runtime-async-std-native-tls = [ "sqlx?/runtime-async-std-native-tls", "runtime-async-std", ] runtime-async-std-rustls = [ "sqlx?/runtime-async-std-rustls", "runtime-async-std", ] runtime-tokio = ["sqlx?/runtime-tokio"] runtime-tokio-native-tls = ["sqlx?/runtime-tokio-native-tls", "runtime-tokio"] runtime-tokio-rustls = ["sqlx?/runtime-tokio-rustls", "runtime-tokio"] rusqlite = [] schema-sync = ["sea-schema"] sea-orm-internal = [] seaography = ["sea-orm-macros/seaography"] sqlite-no-row-value-before-3_15 = [] sqlite-use-returning-for-3_35 = [] sqlx-all = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite"] sqlx-dep = ["sqlx"] sqlx-mysql = [ "sqlx-dep", "sea-query-sqlx/sqlx-mysql", "sea-schema?/sqlx-mysql", ] sqlx-postgres = [ "sqlx-dep", "sea-query-sqlx/sqlx-postgres", "postgres-array", "sea-schema?/sqlx-postgres", ] sqlx-sqlite = [ "sqlx-dep", "sea-query-sqlx/sqlx-sqlite", "sea-schema?/sqlx-sqlite", ] sync = [] tests-cfg = ["serde/derive"] tests-features = [ "default", "postgres-array", "rbac", "schema-sync", "with-arrow", "with-bigdecimal", "with-ipnetwork", ] tracing-spans = [] with-arrow = ["sea-orm-arrow", "sea-orm-macros/with-arrow"] with-bigdecimal = [ "bigdecimal", "sea-query/with-bigdecimal", "sea-query-sqlx?/with-bigdecimal", "sea-orm-arrow?/with-bigdecimal", ] with-chrono = [ "chrono", "sea-query/with-chrono", "sea-query-sqlx?/with-chrono", "sea-orm-arrow?/with-chrono", ] with-ipnetwork = [ "ipnetwork", "sea-query/with-ipnetwork", "sea-query-sqlx?/with-ipnetwork", ] with-json = [ "serde_json", "sea-query/with-json", "sea-orm-macros/with-json", "chrono?/serde", "rust_decimal?/serde", "bigdecimal?/serde", "uuid?/serde", "time?/serde", "mac_address?/serde", "pgvector?/serde", "sea-query-sqlx?/with-json", ] with-mac_address = [ "mac_address", "sea-query/with-mac_address", "sea-query-sqlx?/with-mac_address", ] with-rust_decimal = [ "rust_decimal", "sea-query/with-rust_decimal", "sea-query-sqlx?/with-rust_decimal", "sea-orm-arrow?/with-rust_decimal", ] with-time = [ "time", "sea-query/with-time", "sea-query-sqlx?/with-time", "sea-orm-arrow?/with-time", ] with-uuid = ["uuid", "sea-query/with-uuid", "sea-query-sqlx?/with-uuid"] # This allows us to develop using a local version of sea-query [patch.crates-io] # sea-query = { path = "../sea-query" } # sea-query = { git = "https://github.com/SeaQL/sea-query", branch = "master" } ================================================ FILE: DEVELOPMENT.md ================================================ ## Use a local SeaORM version Add this to your `Cargo.toml` for you local project to test out your changes: ```toml [patch.crates-io] sea-orm = { path = "../sea-orm" } ``` ## Before submitting PR ### Run `clippy` and `fmt` We need nightly to do the formatting, ```sh cargo +nightly fmt ``` If you don't have nightly then at least run: ```sh cargo fmt && git checkout -- ./sea-orm-codegen/src/tests_cfg/ ``` We use latest stable clippy: ```sh cargo clippy --all ``` ### Running unit tests Just do: ```sh cargo test --lib cargo test --doc ``` ### Launch some databases There is a docker compose under `build-tools`, but usually I just pick the ones I need from `build-tools/docker-crete.sh`: ```sh docker run \ --name "postgres-14" \ --env POSTGRES_USER="sea" \ --env POSTGRES_PASSWORD="sea" \ -d -p 5432:5432 postgres:14 ``` ### Running integration tests You need to supply the right feature flags to run integration tests: ```sh DATABASE_URL="sqlite::memory:" cargo test --features sqlx-sqlite,runtime-tokio --test crud_tests DATABASE_URL="mysql://sea:sea@localhost" cargo test --features sqlx-mysql,runtime-tokio-native-tls --test crud_tests DATABASE_URL="postgres://sea:sea@localhost" cargo test --features sqlx-postgres,runtime-tokio-native-tls --test crud_tests ``` Or use `--tests` to run all tests, which can take a while. ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2023 Seafire Software Limited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-zh.md ================================================
SeaORM

一个强大且动态的 Rust ORM

[![crate](https://img.shields.io/crates/v/sea-orm.svg)](https://crates.io/crates/sea-orm) [![build status](https://github.com/SeaQL/sea-orm/actions/workflows/rust.yml/badge.svg)](https://github.com/SeaQL/sea-orm/actions/workflows/rust.yml) [![GitHub stars](https://img.shields.io/github/stars/SeaQL/sea-orm.svg?style=social&label=Star&maxAge=1)](https://github.com/SeaQL/sea-orm/stargazers/)
请给我们一个 ⭐ 以支持我们!
# 🐚 SeaORM SeaORM 是一个关系型 ORM,帮助你在 Rust 中构建 Web 服务,同时提供动态语言的使用体验。 ### 高级关系 以高层次、概念化的方式建模复杂关系:一对一、一对多、多对多,甚至自引用。 ### 熟悉的概念 受 Ruby、Python 和 Node.js 生态系统中流行 ORM 的启发,SeaORM 提供的开发体验让你感觉似曾相识。 ### 功能丰富 SeaORM 是一个功能齐全的 ORM,内置过滤、分页和嵌套查询,加速构建 REST、GraphQL 和 gRPC API。 ### 生产就绪 SeaORM 周下载量超过 25 万次,已被全球的初创企业和大型企业采用,适用于生产环境。 ## 快速开始 [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) 加入我们的 Discord 服务器,与其他成员交流! + [中文文档](https://www.sea-ql.org/SeaORM/zh-CN/docs/index/) 集成示例: + [Actix 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) + [Axum 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) + [GraphQL 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example) + [jsonrpsee 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example) + [Loco 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_example) / [Loco REST 入门](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_starter) + [Poem 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) + [Rocket 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) / [Rocket OpenAPI 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_okapi_example) + [Salvo 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example) + [Tonic 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example) + [Seaography 示例 (Bakery)](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) / [Seaography 示例 (Sakila)](https://github.com/SeaQL/seaography/tree/main/examples/sqlite) 如果你想要一个简洁的单文件示例来展示 SeaORM 的精华,可以试试: + [快速入门](https://github.com/SeaQL/sea-orm/blob/master/examples/quickstart/src/main.rs) 让我们快速了解一下 SeaORM 的独特功能。 ## 灵活的实体格式 你不需要手写这些!实体文件可以使用 `sea-orm-cli` 从现有数据库生成, 以下代码通过 `--entity-format dense` 生成 *(2.0 新增)*。 ```rust mod user { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(unique)] pub email: String, #[sea_orm(has_one)] pub profile: HasOne, #[sea_orm(has_many)] pub posts: HasMany, } } mod post { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: i32, pub title: String, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub author: HasOne, #[sea_orm(has_many, via = "post_tag")] // 多对多关系,使用中间表 pub tags: HasMany, } } ``` ## 智能实体加载器 实体加载器智能地对一对一关系使用 join,对一对多关系使用 data loader, 即使在执行嵌套查询时也能消除 N+1 问题。 ```rust // 加载路径: // user -> profile // user -> post // post -> post_tag -> tag let smart_user = user::Entity::load() .filter_by_id(42) // 等价于 .filter(user::COLUMN.id.eq(42)) .with(profile::Entity) // 一对一使用 join .with((post::Entity, tag::Entity)) // 一对多使用 data loader .one(db) .await? .unwrap(); // 底层执行 3 个查询: // 1. SELECT FROM user JOIN profile WHERE id = $ // 2. SELECT FROM post WHERE user_id IN (..) // 3. SELECT FROM tag JOIN post_tag WHERE post_id IN (..) smart_user == user::ModelEx { id: 42, name: "Bob".into(), email: "bob@sea-ql.org".into(), profile: HasOne::Loaded( profile::ModelEx { picture: "image.jpg".into(), } .into(), ), posts: HasMany::Loaded(vec![post::ModelEx { title: "Nice weather".into(), tags: HasMany::Loaded(vec![tag::ModelEx { tag: "sunny".into(), }]), }]), }; ``` ## ActiveModel:简化嵌套持久化 通过流畅的 builder API,在单次操作中持久化整个对象图:用户、个人资料(一对一)、 帖子(一对多)和标签(多对多)。SeaORM 自动确定依赖关系, 以正确的顺序插入或删除对象。 ```rust // 创建上面展示的嵌套对象: let user = user::ActiveModel::builder() .set_name("Bob") .set_email("bob@sea-ql.org") .set_profile(profile::ActiveModel::builder().set_picture("image.jpg")) .add_post( post::ActiveModel::builder() .set_title("Nice weather") .add_tag(tag::ActiveModel::builder().set_tag("sunny")), ) .save(db) .await?; ``` ## Schema 优先还是实体优先?你的选择 SeaORM 提供了强大的迁移系统,让你轻松创建表、修改 Schema 和填充数据。 SeaORM 2.0 还提供了一流的[实体优先工作流](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/): 只需定义新实体或向现有实体添加列, SeaORM 将自动检测变更并创建新的表、列、唯一键和外键。 ```rust // SeaORM 解析外键依赖,按拓扑顺序创建表。 // 需要 `entity-registry` 和 `schema-sync` feature flags。 db.get_schema_registry("my_crate::entity::*").sync(db).await; ``` ## 简洁的原生 SQL 让 SeaORM 处理 95% 的事务查询。 对于过于复杂而难以表达的剩余情况, SeaORM 仍然提供便捷的原生 SQL 支持。 ```rust let user = Item { name: "Bob" }; // 嵌套参数访问 let ids = [2, 3, 4]; // 通过 `..` 运算符展开 let user: Option = user::Entity::find() .from_raw_sql(raw_sql!( Sqlite, r#"SELECT "id", "name" FROM "user" WHERE "name" LIKE {user.name} AND "id" in ({..ids}) "# )) .one(db) .await?; ``` ## 同步支持 [`sea-orm-sync`](https://crates.io/crates/sea-orm-sync) 提供完整的 SeaORM API,无需异步运行时,非常适合使用 SQLite 的轻量级 CLI 程序。 参见[快速入门示例](https://github.com/SeaQL/sea-orm/blob/master/sea-orm-sync/examples/quickstart/src/main.rs)了解用法。 ## 基础操作 ### 查询 SeaORM 在实体层面建模一对多和多对多关系, 让你通过中间表在一次调用中遍历多对多链接。 ```rust // 查找所有模型 let cakes: Vec = Cake::find().all(db).await?; // 查找并过滤 let chocolate: Vec = Cake::find() .filter(Cake::COLUMN.name.contains("chocolate")) .all(db) .await?; // 查找单个模型 let cheese: Option = Cake::find_by_id(1).one(db).await?; let cheese: cake::Model = cheese.unwrap(); // 查找关联模型(惰性) let fruit: Option = cheese.find_related(Fruit).one(db).await?; // 查找关联模型(急切加载):用于一对一关系 let cake_with_fruit: Vec<(cake::Model, Option)> = Cake::find().find_also_related(Fruit).all(db).await?; // 查找关联模型(急切加载):同时适用于一对多和多对多关系 let cake_with_fillings: Vec<(cake::Model, Vec)> = Cake::find() .find_with_related(Filling) // 多对多关系会执行两次 join .all(db) // 行会自动按左侧实体合并 .await?; ``` ### 嵌套查询 Partial model 通过只查询所需字段来避免过度获取; 它还使编写深层嵌套的关系查询变得简单。 ```rust use sea_orm::DerivePartialModel; #[derive(DerivePartialModel)] #[sea_orm(entity = "cake::Entity")] struct CakeWithFruit { id: i32, name: String, #[sea_orm(nested)] fruit: Option, // 可以是普通模型或另一个 partial model } let cakes: Vec = Cake::find() .left_join(fruit::Entity) // 无需指定 join 条件 .into_partial_model() // 只会查询 partial model 中的列 .all(db) .await?; ``` ### 插入 SeaORM 的 ActiveModel 让你直接使用 Rust 数据结构, 通过简单的 API 进行持久化。 批量插入大量不同数据源的行也很方便。 ```rust let apple = fruit::ActiveModel { name: Set("Apple".to_owned()), ..Default::default() // 无需设置主键 }; let pear = fruit::ActiveModel { name: Set("Pear".to_owned()), ..Default::default() }; // 插入单个:Active Record 风格 let apple = apple.insert(db).await?; apple.id == 1; // 插入单个:Repository 风格 let result = Fruit::insert(apple).exec(db).await?; result.last_insert_id == 1; // 插入多个,返回最后插入的 id let result = Fruit::insert_many([apple, pear]).exec(db).await?; result.last_insert_id == Some(2); ``` ### 高级插入 你可以利用数据库特有的功能执行 upsert 和幂等插入。 ```rust // 插入多条并返回(需要数据库支持) let models: Vec = Fruit::insert_many([apple, pear]) .exec_with_returning(db) .await?; models[0] == fruit::Model { id: 1, // 数据库分配的值 name: "Apple".to_owned(), cake_id: None, }; // 使用 ON CONFLICT 在主键冲突时忽略,并提供 MySQL 特定的 polyfill let result = Fruit::insert_many([apple, pear]) .on_conflict_do_nothing() .exec(db) .await?; matches!(result, TryInsertResult::Conflicted); ``` ### 更新 ActiveModel 通过只更新你修改过的字段来避免竞态条件, 绝不会覆盖未改动的列。 你还可以使用流畅的查询构建 API 构造复杂的批量更新查询。 ```rust use sea_orm::sea_query::{Expr, Value}; let pear: Option = Fruit::find_by_id(1).one(db).await?; let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); // 更新单个字段的值 // 更新单个:只更新修改过的列 let pear: fruit::Model = pear.update(db).await?; // 更新多个:UPDATE "fruit" SET "cake_id" = "cake_id" + 2 // WHERE "fruit"."name" LIKE '%Apple%' Fruit::update_many() .col_expr(fruit::COLUMN.cake_id, fruit::COLUMN.cake_id.add(2)) .filter(fruit::COLUMN.name.contains("Apple")) .exec(db) .await?; ``` ### 保存 你可以使用 ActiveModel 执行"插入或更新"操作,轻松组合事务操作。 ```rust let banana = fruit::ActiveModel { id: NotSet, name: Set("Banana".to_owned()), ..Default::default() }; // 创建,因为主键 `id` 是 `NotSet` let mut banana = banana.save(db).await?; banana.id == Unchanged(2); banana.name = Set("Banana Mongo".to_owned()); // 更新,因为主键 `id` 已存在 let banana = banana.save(db).await?; ``` ### 删除 与插入和更新一致的 ActiveModel API。 ```rust // 删除单个:Active Record 风格 let orange: Option = Fruit::find_by_id(1).one(db).await?; let orange: fruit::Model = orange.unwrap(); orange.delete(db).await?; // 删除单个:Repository 风格 let orange = fruit::ActiveModel { id: Set(2), ..Default::default() }; fruit::Entity::delete(orange).exec(db).await?; // 删除多个:DELETE FROM "fruit" WHERE "fruit"."name" LIKE '%Orange%' fruit::Entity::delete_many() .filter(fruit::COLUMN.name.contains("Orange")) .exec(db) .await?; ``` ### 原生 SQL 查询 `raw_sql!` 宏类似 `format!` 宏,但没有 SQL 注入风险。 它支持嵌套参数插值、数组和元组展开,甚至重复组, 为构造复杂查询提供了极大的灵活性。 ```rust #[derive(FromQueryResult)] struct CakeWithBakery { name: String, #[sea_orm(nested)] bakery: Option, } #[derive(FromQueryResult)] struct Bakery { #[sea_orm(alias = "bakery_name")] name: String, } let cake_ids = [2, 3, 4]; // 通过 `..` 运算符展开 // 可以将原生 SQL 与多种 API 配合使用,包括嵌套查询 let cake: Option = CakeWithBakery::find_by_statement(raw_sql!( Sqlite, r#"SELECT "cake"."name", "bakery"."name" AS "bakery_name" FROM "cake" LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id" WHERE "cake"."id" IN ({..cake_ids})"# )) .one(db) .await?; ``` ## 🧭 Seaography:即时 GraphQL API [Seaography](https://github.com/SeaQL/seaography) 是一个构建在 SeaORM 之上的 GraphQL 框架。 Seaography 允许你快速构建 GraphQL 解析器。 只需几个命令,你就可以从 SeaORM 实体启动一个功能完备的 GraphQL 服务器, 包含过滤、分页、关系查询和变更操作! 查看 [Seaography 示例](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) 了解更多。 ## 🖥️ SeaORM Pro:专业管理面板 [SeaORM Pro](https://github.com/SeaQL/sea-orm-pro/) 是一个管理面板解决方案,让你可以快速轻松地为应用程序启动管理面板——不需要前端开发技能,但有当然更好! SeaORM Pro 已更新以支持 SeaORM 2.0 的最新功能。 特性: + 完整的 CRUD 操作 + 基于 React + GraphQL 构建 + 内置 GraphQL 解析器 + 使用 TOML 配置自定义 UI + 基于角色的访问控制 *(2.0 新增)* 阅读[快速入门](https://www.sea-ql.org/sea-orm-pro/docs/install-and-config/getting-started/)指南了解更多。 ![](https://raw.githubusercontent.com/SeaQL/sea-orm/refs/heads/master/docs/sea-orm-pro-dark.png#gh-dark-mode-only) ![](https://raw.githubusercontent.com/SeaQL/sea-orm/refs/heads/master/docs/sea-orm-pro-light.png#gh-light-mode-only) ## SQL Server 支持 [SQL Server for SeaORM](https://www.sea-ql.org/SeaORM-X/) 为 MSSQL 提供相同的 SeaORM API。我们移植了所有测试用例和示例,并配有 MSSQL 专属文档。如果你正在构建企业软件,可以[申请商业访问权限](https://forms.office.com/r/1MuRPJmYBR)。目前基于 SeaORM 1.0,但我们会在 SeaORM 2.0 最终发布时为现有用户提供免费升级。 ## 版本发布 SeaORM 2.0 已进入候选发布阶段。我们期待你的试用,并通过[分享反馈](https://github.com/SeaQL/sea-orm/discussions/)来帮助塑造最终版本。 + [变更日志](https://github.com/SeaQL/sea-orm/tree/master/CHANGELOG.md) SeaORM 2.0 将是我们迄今最重要的版本——包含一些破坏性变更、大量增强功能,以及对开发者体验的明确聚焦。 + [SeaORM 2.0 先睹为快](https://www.sea-ql.org/blog/2025-09-16-sea-orm-2.0/) + [深入了解 SeaORM 2.0](https://www.sea-ql.org/blog/2025-09-24-sea-orm-2.0/) + [SeaORM 2.0 中的基于角色的访问控制](https://www.sea-ql.org/blog/2025-09-30-sea-orm-rbac/) + [Seaography 2.0:强大且可扩展的 GraphQL 框架](https://www.sea-ql.org/blog/2025-10-08-seaography/) + [SeaORM 2.0:新实体格式](https://www.sea-ql.org/blog/2025-10-20-sea-orm-2.0/) + [SeaORM 2.0:实体优先工作流](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/) + [SeaORM 2.0:强类型列](https://www.sea-ql.org/blog/2025-11-11-sea-orm-2.0/) + [SeaORM Pro 2.0 新特性](https://www.sea-ql.org/blog/2025-11-21-whats-new-in-seaormpro-2.0/) + [SeaORM 2.0:嵌套 ActiveModel](https://www.sea-ql.org/blog/2025-11-25-sea-orm-2.0/) + [SeaORM 2.0 全面介绍](https://www.sea-ql.org/blog/2025-12-05-sea-orm-2.0/) + [我们如何让 SeaORM 支持同步](https://www.sea-ql.org/blog/2025-12-12-sea-orm-2.0/) + [SeaORM 2.0 迁移指南](https://www.sea-ql.org/blog/2026-01-12-sea-orm-2.0/) + [SeaORM 现已支持 Arrow 和 Parquet](https://www.sea-ql.org/blog/2026-02-22-sea-orm-arrow/) + [SeaORM 2.0 支持 SQL Server](https://www.sea-ql.org/blog/2026-02-25-sea-orm-x/) 如果你大量使用 SeaQuery,建议查看我们关于 SeaQuery 1.0 发布的博客文章: + [SeaQuery 1.0 之路](https://www.sea-ql.org/blog/2025-08-30-sea-query-1.0/) ## 许可证 根据以下任一许可证授权: - Apache 许可证 2.0 ([LICENSE-APACHE](LICENSE-APACHE) 或 ) - MIT 许可证 ([LICENSE-MIT](LICENSE-MIT) 或 ) 由你选择。 ## 贡献 除非你明确声明,否则你有意提交的任何贡献,根据 Apache-2.0 许可证的定义,都将按照上述许可证双重授权,且不附带任何附加条款或条件。 我们诚挚邀请你参与、贡献,并携手共建 Rust 的未来。 向我们的贡献者们致以最真诚的感谢! [![Contributors](https://opencollective.com/sea-orm/contributors.svg?width=1000&button=false)](https://github.com/SeaQL/sea-orm/graphs/contributors) ## 谁在使用 SeaORM? 以下是一些使用 SeaORM 构建的优秀开源软件的简短列表。欢迎[提交你的项目](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#built-with-seaorm)! | 项目 | GitHub | 标语 | |---------|--------|---------| | [Zed](https://github.com/zed-industries/zed) | ![GitHub stars](https://img.shields.io/github/stars/zed-industries/zed.svg?style=social) | 高性能、多人代码编辑器 | | [OpenObserve](https://github.com/openobserve/openobserve) | ![GitHub stars](https://img.shields.io/github/stars/openobserve/openobserve.svg?style=social) | 开源可观测性平台 | | [RisingWave](https://github.com/risingwavelabs/risingwave) | ![GitHub stars](https://img.shields.io/github/stars/risingwavelabs/risingwave.svg?style=social) | 流处理和管理平台 | | [LLDAP](https://github.com/nitnelave/lldap) | ![GitHub stars](https://img.shields.io/github/stars/nitnelave/lldap.svg?style=social) | 轻量级 LDAP 用户管理服务器 | | [Warpgate](https://github.com/warp-tech/warpgate) | ![GitHub stars](https://img.shields.io/github/stars/warp-tech/warpgate.svg?style=social) | 智能 SSH 堡垒,适用于任何 SSH 客户端 | | [Svix](https://github.com/svix/svix-webhooks) | ![GitHub stars](https://img.shields.io/github/stars/svix/svix-webhooks.svg?style=social) | 企业级 Webhooks 服务 | | [Ryot](https://github.com/IgnisDa/ryot) | ![GitHub stars](https://img.shields.io/github/stars/ignisda/ryot.svg?style=social) | 你唯一需要的自托管追踪器 | | [Lapdev](https://github.com/lapce/lapdev) | ![GitHub stars](https://img.shields.io/github/stars/lapce/lapdev.svg?style=social) | 自托管远程开发环境 | | [System Initiative](https://github.com/systeminit/si) | ![GitHub stars](https://img.shields.io/github/stars/systeminit/si.svg?style=social) | DevOps 自动化平台 | | [OctoBase](https://github.com/toeverything/OctoBase) | ![GitHub stars](https://img.shields.io/github/stars/toeverything/OctoBase.svg?style=social) | 轻量级、可扩展、离线协作数据后端 | ## 赞助 [SeaQL.org](https://www.sea-ql.org/) 是一个由热情的开发者运营的独立开源组织。如果你愿意,通过 [GitHub Sponsor](https://github.com/sponsors/SeaQL) 进行小额捐赠将不胜感激,并将大大有助于维持组织的运营。 ### 金牌赞助商
[QDX](https://qdx.co/) 开创了量子动力学驱动的药物发现,利用人工智能和超级计算加速分子建模。 我们非常感谢 QDX 赞助 SeaORM 的开发,这是为其数据密集型应用提供支持的 SQL 工具包。 ### 银牌赞助商 我们感谢银牌赞助商:Digital Ocean 赞助我们的服务器,以及 JetBrains 赞助我们的 IDE。
## 吉祥物 Ferris 的朋友,寄居蟹 Terres 是 SeaORM 的官方吉祥物。他的爱好是收集贝壳。 Terres ## 🦀 Rustacean 贴纸包 Rustacean 贴纸包是表达你对 Rust 热情的完美方式。我们的贴纸采用优质防水乙烯基制成,具有独特的哑光效果。 贴纸包内容: + SeaQL 项目的标志:SeaQL、SeaORM、SeaQuery、Seaography + 吉祥物:Ferris x 3、寄居蟹 Terres + Rustacean 文字标识 [支持 SeaQL 并获取贴纸包!](https://www.sea-ql.org/sticker-pack/) 所有收益直接用于 SeaQL 项目的持续开发。
Rustacean Sticker Pack by SeaQL ================================================ FILE: README.md ================================================
SeaORM

SeaORM is a powerful ORM for building web services in Rust

[![crate](https://img.shields.io/crates/v/sea-orm.svg)](https://crates.io/crates/sea-orm) [![build status](https://github.com/SeaQL/sea-orm/actions/workflows/rust.yml/badge.svg)](https://github.com/SeaQL/sea-orm/actions/workflows/rust.yml) [![GitHub stars](https://img.shields.io/github/stars/SeaQL/sea-orm.svg?style=social&label=Star&maxAge=1)](https://github.com/SeaQL/sea-orm/stargazers/)
Support us with a ⭐ !
# 🐚 SeaORM [中文文档](https://github.com/SeaQL/sea-orm/blob/master/README-zh.md) ### Advanced Relations Model complex relationships 1-1, 1-N, M-N, and even self-referential in a high-level, conceptual way. ### Familiar Concepts Inspired by popular ORMs in the Ruby, Python, and Node.js ecosystem, SeaORM offers a developer experience that feels instantly recognizable. ### Feature Rich SeaORM is a batteries-included ORM with filters, pagination, and nested queries to accelerate building REST, GraphQL, and gRPC APIs. ### Production Ready With 250k+ weekly downloads, SeaORM is production-ready, trusted by startups and enterprises worldwide. ## Getting Started [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) Join our Discord server to chat with others! + [Documentation](https://www.sea-ql.org/SeaORM) Integration examples: + [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) + [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) + [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example) + [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example) + [Loco Example](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_example) / [Loco REST Starter](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_starter) + [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) + [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) / [Rocket OpenAPI Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_okapi_example) + [Salvo Example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example) + [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example) + [Seaography Example (Bakery)](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) / [Seaography Example (Sakila)](https://github.com/SeaQL/seaography/tree/main/examples/sqlite) If you want a simple, clean example that fits in a single file that demonstrates the best of SeaORM, you can try: + [Quickstart](https://github.com/SeaQL/sea-orm/blob/master/examples/quickstart/src/main.rs) Let's have a quick walk through of the unique features of SeaORM. ## Expressive Entity format You don't have to write this by hand! Entity files can be generated from an existing database using `sea-orm-cli`, following is generated with `--entity-format dense` *(new in 2.0)*. ```rust mod user { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(unique)] pub email: String, #[sea_orm(has_one)] pub profile: HasOne, #[sea_orm(has_many)] pub posts: HasMany, } } mod post { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: i32, pub title: String, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub author: HasOne, #[sea_orm(has_many, via = "post_tag")] // M-N relation with junction pub tags: HasMany, } } ``` ## Smart Entity Loader The Entity Loader intelligently uses join for 1-1 and data loader for 1-N relations, eliminating the N+1 problem even when performing nested queries. ```rust // join paths: // user -> profile // user -> post // post -> post_tag -> tag let smart_user = user::Entity::load() .filter_by_id(42) // shorthand for .filter(user::COLUMN.id.eq(42)) .with(profile::Entity) // 1-1 uses join .with((post::Entity, tag::Entity)) // 1-N uses data loader .one(db) .await? .unwrap(); // 3 queries are executed under the hood: // 1. SELECT FROM user JOIN profile WHERE id = $ // 2. SELECT FROM post WHERE user_id IN (..) // 3. SELECT FROM tag JOIN post_tag WHERE post_id IN (..) smart_user == user::ModelEx { id: 42, name: "Bob".into(), email: "bob@sea-ql.org".into(), profile: HasOne::Loaded( profile::ModelEx { picture: "image.jpg".into(), } .into(), ), posts: HasMany::Loaded(vec![post::ModelEx { title: "Nice weather".into(), tags: HasMany::Loaded(vec![tag::ModelEx { tag: "sunny".into(), }]), }]), }; ``` ## ActiveModel: nested persistence made simple Persist an entire object graph: user, profile (1-1), posts (1-N), and tags (M-N) in a single operation using a fluent builder API. SeaORM automatically determines the dependencies and inserts or deletes objects in the correct order. ```rust // this creates the nested object as shown above: let user = user::ActiveModel::builder() .set_name("Bob") .set_email("bob@sea-ql.org") .set_profile(profile::ActiveModel::builder().set_picture("image.jpg")) .add_post( post::ActiveModel::builder() .set_title("Nice weather") .add_tag(tag::ActiveModel::builder().set_tag("sunny")), ) .save(db) .await?; ``` ## Schema first or Entity first? Your choice SeaORM provides a powerful migration system that lets you create tables, modify schemas, and seed data with ease. With SeaORM 2.0, you also get a first-class [Entity First Workflow](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/): simply define new entities or add columns to existing ones, and SeaORM will automatically detect the changes and create the new tables, columns, unique keys, and foreign keys. ```rust // SeaORM resolves foreign key dependencies and creates the tables in topological order. // Requires the `entity-registry` and `schema-sync` feature flags. db.get_schema_registry("my_crate::entity::*").sync(db).await; ``` ## Ergonomic Raw SQL Let SeaORM handle 95% of your transactional queries. For the remaining cases that are too complex to express, SeaORM still offers convenient support for writing raw SQL. ```rust let user = Item { name: "Bob" }; // nested parameter access let ids = [2, 3, 4]; // expanded by the `..` operator let user: Option = user::Entity::find() .from_raw_sql(raw_sql!( Sqlite, r#"SELECT "id", "name" FROM "user" WHERE "name" LIKE {user.name} AND "id" in ({..ids}) "# )) .one(db) .await?; ``` ## Synchronous Support [`sea-orm-sync`](https://crates.io/crates/sea-orm-sync) provides the full SeaORM API without requiring an async runtime, making it ideal for lightweight CLI programs with SQLite. See the [quickstart example](https://github.com/SeaQL/sea-orm/blob/master/sea-orm-sync/examples/quickstart/src/main.rs) for usage. ## Basics ### Select SeaORM models 1-N and M-N relationships at the Entity level, letting you traverse many-to-many links through a junction table in a single call. ```rust // find all models let cakes: Vec = Cake::find().all(db).await?; // find and filter let chocolate: Vec = Cake::find() .filter(Cake::COLUMN.name.contains("chocolate")) .all(db) .await?; // find one model let cheese: Option = Cake::find_by_id(1).one(db).await?; let cheese: cake::Model = cheese.unwrap(); // find related models (lazy) let fruit: Option = cheese.find_related(Fruit).one(db).await?; // find related models (eager): for 1-1 relations let cake_with_fruit: Vec<(cake::Model, Option)> = Cake::find().find_also_related(Fruit).all(db).await?; // find related models (eager): works for both 1-N and M-N relations let cake_with_fillings: Vec<(cake::Model, Vec)> = Cake::find() .find_with_related(Filling) // for M-N relations, two joins are performed .all(db) // rows are automatically consolidated by left entity .await?; ``` ### Nested Select Partial models prevent overfetching by letting you querying only the fields you need; it also makes writing deeply nested relational queries simple. ```rust use sea_orm::DerivePartialModel; #[derive(DerivePartialModel)] #[sea_orm(entity = "cake::Entity")] struct CakeWithFruit { id: i32, name: String, #[sea_orm(nested)] fruit: Option, // this can be a regular or another partial model } let cakes: Vec = Cake::find() .left_join(fruit::Entity) // no need to specify join condition .into_partial_model() // only the columns in the partial model will be selected .all(db) .await?; ``` ### Insert SeaORM's ActiveModel lets you work directly with Rust data structures and persist them through a simple API. It's easy to insert large batches of rows from different data sources. ```rust let apple = fruit::ActiveModel { name: Set("Apple".to_owned()), ..Default::default() // no need to set primary key }; let pear = fruit::ActiveModel { name: Set("Pear".to_owned()), ..Default::default() }; // insert one: Active Record style let apple = apple.insert(db).await?; apple.id == 1; // insert one: repository style let result = Fruit::insert(apple).exec(db).await?; result.last_insert_id == 1; // insert many returning last insert id let result = Fruit::insert_many([apple, pear]).exec(db).await?; result.last_insert_id == Some(2); ``` ### Insert (advanced) You can take advantage of database specific features to perform upsert and idempotent insert. ```rust // insert many with returning (if supported by database) let models: Vec = Fruit::insert_many([apple, pear]) .exec_with_returning(db) .await?; models[0] == fruit::Model { id: 1, // database assigned value name: "Apple".to_owned(), cake_id: None, }; // insert with ON CONFLICT on primary key do nothing, with MySQL specific polyfill let result = Fruit::insert_many([apple, pear]) .on_conflict_do_nothing() .exec(db) .await?; matches!(result, TryInsertResult::Conflicted); ``` ### Update ActiveModel avoids race conditions by updating only the fields you've changed, never overwriting untouched columns. You can also craft complex bulk update queries with a fluent query building API. ```rust use sea_orm::sea_query::{Expr, Value}; let pear: Option = Fruit::find_by_id(1).one(db).await?; let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); // update value of a single field // update one: only changed columns will be updated let pear: fruit::Model = pear.update(db).await?; // update many: UPDATE "fruit" SET "cake_id" = "cake_id" + 2 // WHERE "fruit"."name" LIKE '%Apple%' Fruit::update_many() .col_expr(fruit::COLUMN.cake_id, fruit::COLUMN.cake_id.add(2)) .filter(fruit::COLUMN.name.contains("Apple")) .exec(db) .await?; ``` ### Save You can perform "insert or update" operation with ActiveModel, making it easy to compose transactional operations. ```rust let banana = fruit::ActiveModel { id: NotSet, name: Set("Banana".to_owned()), ..Default::default() }; // create, because primary key `id` is `NotSet` let mut banana = banana.save(db).await?; banana.id == Unchanged(2); banana.name = Set("Banana Mongo".to_owned()); // update, because primary key `id` is present let banana = banana.save(db).await?; ``` ### Delete The same ActiveModel API consistent with insert and update. ```rust // delete one: Active Record style let orange: Option = Fruit::find_by_id(1).one(db).await?; let orange: fruit::Model = orange.unwrap(); orange.delete(db).await?; // delete one: repository style let orange = fruit::ActiveModel { id: Set(2), ..Default::default() }; fruit::Entity::delete(orange).exec(db).await?; // delete many: DELETE FROM "fruit" WHERE "fruit"."name" LIKE '%Orange%' fruit::Entity::delete_many() .filter(fruit::COLUMN.name.contains("Orange")) .exec(db) .await?; ``` ### Raw SQL Query The `raw_sql!` macro is like the `format!` macro but without the risk of SQL injection. It supports nested parameter interpolation, array and tuple expansion, and even repeating group, offering great flexibility in crafting complex queries. ```rust #[derive(FromQueryResult)] struct CakeWithBakery { name: String, #[sea_orm(nested)] bakery: Option, } #[derive(FromQueryResult)] struct Bakery { #[sea_orm(alias = "bakery_name")] name: String, } let cake_ids = [2, 3, 4]; // expanded by the `..` operator // can use many APIs with raw SQL, including nested select let cake: Option = CakeWithBakery::find_by_statement(raw_sql!( Sqlite, r#"SELECT "cake"."name", "bakery"."name" AS "bakery_name" FROM "cake" LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id" WHERE "cake"."id" IN ({..cake_ids})"# )) .one(db) .await?; ``` ## 🧭 Seaography: instant GraphQL API [Seaography](https://github.com/SeaQL/seaography) is a GraphQL framework built for SeaORM. Seaography allows you to build GraphQL resolvers quickly. With just a few commands, you can launch a fullly-featured GraphQL server from SeaORM entities, complete with filter, pagination, relational queries and mutations! Look at the [Seaography Example](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) to learn more. ## 🖥️ SeaORM Pro: Professional Admin Panel [SeaORM Pro](https://github.com/SeaQL/sea-orm-pro/) is an admin panel solution allowing you to quickly and easily launch an admin panel for your application - frontend development skills not required, but certainly nice to have! SeaORM Pro has been updated to support the latest features in SeaORM 2.0. Features: + Full CRUD + Built on React + GraphQL + Built-in GraphQL resolver + Customize the UI with TOML config + Role Based Access Control *(new in 2.0)* Read the [Getting Started](https://www.sea-ql.org/sea-orm-pro/docs/install-and-config/getting-started/) guide to learn more. ![](https://raw.githubusercontent.com/SeaQL/sea-orm/refs/heads/master/docs/sea-orm-pro-dark.png#gh-dark-mode-only) ![](https://raw.githubusercontent.com/SeaQL/sea-orm/refs/heads/master/docs/sea-orm-pro-light.png#gh-light-mode-only) ## SQL Server Support [SQL Server for SeaORM](https://www.sea-ql.org/SeaORM-X/) offers the same SeaORM API for MSSQL. We ported all test cases and examples, complemented by MSSQL specific documentation. If you are building enterprise software, you can [request commercial access](https://forms.office.com/r/1MuRPJmYBR). It is currently based on SeaORM 1.0, but we will offer free upgrade to existing users when SeaORM 2.0 is finalized. ## Releases SeaORM 2.0 has reached its release candidate phase. We'd love for you to try it out and help shape the final release by [sharing your feedback](https://github.com/SeaQL/sea-orm/discussions/). + [Change Log](https://github.com/SeaQL/sea-orm/tree/master/CHANGELOG.md) SeaORM 2.0 is shaping up to be our most significant release yet - with a few breaking changes, plenty of enhancements, and a clear focus on developer experience. + [A Sneak Peek at SeaORM 2.0](https://www.sea-ql.org/blog/2025-09-16-sea-orm-2.0/) + [SeaORM 2.0: A closer look](https://www.sea-ql.org/blog/2025-09-24-sea-orm-2.0/) + [Role Based Access Control in SeaORM 2.0](https://www.sea-ql.org/blog/2025-09-30-sea-orm-rbac/) + [Seaography 2.0: A Powerful and Extensible GraphQL Framework](https://www.sea-ql.org/blog/2025-10-08-seaography/) + [SeaORM 2.0: New Entity Format](https://www.sea-ql.org/blog/2025-10-20-sea-orm-2.0/) + [SeaORM 2.0: Entity First Workflow](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/) + [SeaORM 2.0: Strongly-Typed Column](https://www.sea-ql.org/blog/2025-11-11-sea-orm-2.0/) + [What's new in SeaORM Pro 2.0](https://www.sea-ql.org/blog/2025-11-21-whats-new-in-seaormpro-2.0/) + [SeaORM 2.0: Nested ActiveModel](https://www.sea-ql.org/blog/2025-11-25-sea-orm-2.0/) + [A walk-through of SeaORM 2.0](https://www.sea-ql.org/blog/2025-12-05-sea-orm-2.0/) + [How we made SeaORM synchronous](https://www.sea-ql.org/blog/2025-12-12-sea-orm-2.0/) + [SeaORM 2.0 Migration Guide](https://www.sea-ql.org/blog/2026-01-12-sea-orm-2.0/) + [SeaORM now supports Arrow & Parquet](https://www.sea-ql.org/blog/2026-02-22-sea-orm-arrow/) + [SeaORM 2.0 with SQL Server Support](https://www.sea-ql.org/blog/2026-02-25-sea-orm-x/) If you make extensive use of SeaQuery, we recommend checking out our blog post on SeaQuery 1.0 release: + [The road to SeaQuery 1.0](https://www.sea-ql.org/blog/2025-08-30-sea-query-1.0/) ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. We invite you to participate, contribute and together help build Rust's future. A big shout out to our contributors! [![Contributors](https://opencollective.com/sea-orm/contributors.svg?width=1000&button=false)](https://github.com/SeaQL/sea-orm/graphs/contributors) ## Who's using SeaORM? Here is a short list of awesome open source software built with SeaORM. Feel free to [submit yours](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#built-with-seaorm)! | Project | GitHub | Tagline | |---------|--------|---------| | [Zed](https://github.com/zed-industries/zed) | ![GitHub stars](https://img.shields.io/github/stars/zed-industries/zed.svg?style=social) | A high-performance, multiplayer code editor | | [OpenObserve](https://github.com/openobserve/openobserve) | ![GitHub stars](https://img.shields.io/github/stars/openobserve/openobserve.svg?style=social) | Open-source observability platform | | [RisingWave](https://github.com/risingwavelabs/risingwave) | ![GitHub stars](https://img.shields.io/github/stars/risingwavelabs/risingwave.svg?style=social) | Stream processing and management platform | | [LLDAP](https://github.com/nitnelave/lldap) | ![GitHub stars](https://img.shields.io/github/stars/nitnelave/lldap.svg?style=social) | A light LDAP server for user management | | [Warpgate](https://github.com/warp-tech/warpgate) | ![GitHub stars](https://img.shields.io/github/stars/warp-tech/warpgate.svg?style=social) | Smart SSH bastion that works with any SSH client | | [Svix](https://github.com/svix/svix-webhooks) | ![GitHub stars](https://img.shields.io/github/stars/svix/svix-webhooks.svg?style=social) | The enterprise ready webhooks service | | [Ryot](https://github.com/IgnisDa/ryot) | ![GitHub stars](https://img.shields.io/github/stars/ignisda/ryot.svg?style=social) | The only self hosted tracker you will ever need | | [Lapdev](https://github.com/lapce/lapdev) | ![GitHub stars](https://img.shields.io/github/stars/lapce/lapdev.svg?style=social) | Self-hosted remote development enviroment | | [System Initiative](https://github.com/systeminit/si) | ![GitHub stars](https://img.shields.io/github/stars/systeminit/si.svg?style=social) | DevOps Automation Platform | | [OctoBase](https://github.com/toeverything/OctoBase) | ![GitHub stars](https://img.shields.io/github/stars/toeverything/OctoBase.svg?style=social) | A light-weight, scalable, offline collaborative data backend | ## Sponsorship [SeaQL.org](https://www.sea-ql.org/) is an independent open-source organization run by passionate developers. If you feel generous, a small donation via [GitHub Sponsor](https://github.com/sponsors/SeaQL) will be greatly appreciated, and goes a long way towards sustaining the organization. ### Gold Sponsors
[QDX](https://qdx.co/) pioneers quantum dynamics-powered drug discovery, leveraging AI and supercomputing to accelerate molecular modeling. We're immensely grateful to QDX for sponsoring the development of SeaORM, the SQL toolkit that powers their data intensive applications. ### Silver Sponsors We're grateful to our silver sponsors: Digital Ocean, for sponsoring our servers. And JetBrains, for sponsoring our IDE.
## Mascot A friend of Ferris, Terres the hermit crab is the official mascot of SeaORM. His hobby is collecting shells. Terres ## 🦀 Rustacean Sticker Pack The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Sticker Pack Contents: + Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography + Mascots: Ferris the Crab x 3, Terres the Hermit Crab + The Rustacean wordmark [Support SeaQL and get a Sticker Pack!](https://www.sea-ql.org/sticker-pack/) All proceeds contributes directly to the ongoing development of SeaQL projects. Rustacean Sticker Pack by SeaQL ================================================ FILE: VERSIONS.md ================================================ # Known good version combo sea-orm sea-query sea-query-derive sea-query-sqlx sea-schema 2.0.0-rc.1 1.0.0-rc.6 1.0.0-rc.5 sea-query-binder 0.17.0-rc.3 2.0.0-rc.4 1.0.0-rc.12 1.0.0-rc.9 0.8.0-rc.8 0.17.0-rc.6 2.0.0-rc.9 1.0.0-rc.14 1.0.0-rc.9 0.8.0-rc.9 0.17.0-rc.8 2.0.0-rc.24 1.0.0-rc.27 1.0.0-rc.11 0.8.0-rc.10 0.17.0-rc.17 2.0.0-rc.27 1.0.0-rc.29 1.0.0-rc.11 0.8.0-rc.11 0.17.0-rc.17 ================================================ FILE: build-tools/back-async.rs ================================================ cp sea-orm-sync/src/driver/rusqlite.rs ./src/driver/ ================================================ FILE: build-tools/bump.sh ================================================ #!/bin/bash set -e # Bump `sea-orm-codegen` version cd sea-orm-codegen sed -i 's/^version.*$/version = "'$1'"/' Cargo.toml cd .. # Bump `sea-orm-cli` version cd sea-orm-cli sed -i 's/^version.*$/version = "'$1'"/' Cargo.toml sed -i 's/^sea-orm-codegen [^,]*,/sea-orm-codegen = { version = "\='$1'",/' Cargo.toml cd .. # Bump `sea-orm-macros` version cd sea-orm-macros sed -i 's/^version.*$/version = "'$1'"/' Cargo.toml cd .. # Bump `sea-orm` version sed -i 's/^version.*$/version = "'$1'"/' Cargo.toml sed -i 's/^sea-orm-macros [^,]*,/sea-orm-macros = { version = "'~$1'",/' Cargo.toml # Bump `sea-orm-migration` version cd sea-orm-migration sed -i 's/^version.*$/version = "'$1'"/' Cargo.toml sed -i 's/^sea-orm-cli [^,]*,/sea-orm-cli = { version = "'~$1'",/' Cargo.toml sed -i 's/^sea-orm [^,]*,/sea-orm = { version = "'~$1'",/' Cargo.toml cd .. # Bump `sea-orm-sync` version cd sea-orm-sync sed -i 's/^version.*$/version = "'$1'"/' Cargo.toml cd .. git commit -am "$1" # Bump examples' dependency version cd examples find . -depth -type f -name '*.toml' -exec sed -i 's/^version = ".*" # sea-orm version$/version = "'~$1'" # sea-orm version/' {} \; find . -depth -type f -name '*.toml' -exec sed -i 's/^version = ".*" # sea-orm-migration version$/version = "'~$1'" # sea-orm-migration version/' {} \; git add . git commit -m "update examples" ================================================ FILE: build-tools/clean.sh ================================================ #!/bin/bash set -x for dir in */; do cd "$dir"; cargo clean; cd ..; done ================================================ FILE: build-tools/del-rel-dep.sh ================================================ #!/bin/bash set -e find examples/ -depth -type f -name '*.toml' -exec sed -i '/^path = "..\/..\/..\/sea-orm-migration"/d' {} \; find examples/ -depth -type f -name '*.toml' -exec sed -i '/^path = "..\/..\/..\/"/d' {} \; ================================================ FILE: build-tools/docker-compose.yml ================================================ version: "3" services: # # MariaDB # mariadb_10_6: image: mariadb:10.6 ports: - 3306 environment: MYSQL_DB: mysql MYSQL_USER: sea MYSQL_PASSWORD: sea MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ROOT_PASSWORD: root mariadb_10_5: image: mariadb:10.5 ports: - 3306 environment: MYSQL_DB: mysql MYSQL_USER: sea MYSQL_PASSWORD: sea MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ROOT_PASSWORD: root mariadb_10_4: image: mariadb:10.4 ports: - 3306 environment: MYSQL_DB: mysql MYSQL_USER: sea MYSQL_PASSWORD: sea MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ROOT_PASSWORD: root # # MySQL # mysql_8_0: image: mysql:8.0 ports: - 3306 environment: MYSQL_DB: mysql MYSQL_USER: sea MYSQL_PASSWORD: sea MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ROOT_PASSWORD: root mysql_5_7: image: mysql:5.7 ports: - 3306 environment: MYSQL_DB: mysql MYSQL_USER: sea MYSQL_PASSWORD: sea MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ROOT_PASSWORD: root # # PostgreSQL # postgres_13: image: postgres:13 ports: - 5432 environment: POSTGRES_USER: root POSTGRES_PASSWORD: root postgres_12: image: postgres:12 ports: - 5432 environment: POSTGRES_USER: root POSTGRES_PASSWORD: root postgres_11: image: postgres:11 ports: - 5432 environment: POSTGRES_USER: root POSTGRES_PASSWORD: root ================================================ FILE: build-tools/docker-create.sh ================================================ # Some Common Docker Commands You Might Need (use with caution) # # Delete all containers # $ docker rm -f $(docker ps -a -q) # # Delete all volumes # $ docker volume rm $(docker volume ls -q) # # Delete all images # $ docker image rm $(docker image ls -q) # Setup MariaDB docker run \ --name "mariadb-10.6" \ --env MYSQL_DB="mysql" \ --env MYSQL_USER="sea" \ --env MYSQL_PASSWORD="sea" \ --env MYSQL_ALLOW_EMPTY_PASSWORD="yes" \ --env MYSQL_ROOT_PASSWORD="root" \ -d -p 3306:3306 mariadb:10.6 docker stop "mariadb-10.6" docker run \ --name "mariadb-10.5" \ --env MYSQL_DB="mysql" \ --env MYSQL_USER="sea" \ --env MYSQL_PASSWORD="sea" \ --env MYSQL_ALLOW_EMPTY_PASSWORD="yes" \ --env MYSQL_ROOT_PASSWORD="root" \ -d -p 3306:3306 mariadb:10.5 docker stop "mariadb-10.5" docker run \ --name "mariadb-10.4" \ --env MYSQL_DB="mysql" \ --env MYSQL_USER="sea" \ --env MYSQL_PASSWORD="sea" \ --env MYSQL_ALLOW_EMPTY_PASSWORD="yes" \ --env MYSQL_ROOT_PASSWORD="root" \ -d -p 3306:3306 mariadb:10.4 docker stop "mariadb-10.4" # Setup MySQL docker run \ --name "mysql-8.0" \ --env MYSQL_DB="mysql" \ --env MYSQL_USER="sea" \ --env MYSQL_PASSWORD="sea" \ --env MYSQL_ALLOW_EMPTY_PASSWORD="yes" \ --env MYSQL_ROOT_PASSWORD="root" \ -d -p 3306:3306 mysql:8.0 docker stop "mysql-8.0" docker run \ --name "mysql-5.7" \ --env MYSQL_DB="mysql" \ --env MYSQL_USER="sea" \ --env MYSQL_PASSWORD="sea" \ --env MYSQL_ALLOW_EMPTY_PASSWORD="yes" \ --env MYSQL_ROOT_PASSWORD="root" \ -d -p 3306:3306 mysql:5.7 docker stop "mysql-5.7" # Setup PostgreSQL docker run \ --name "postgres-vector-14" \ --env POSTGRES_USER="sea" \ --env POSTGRES_PASSWORD="sea" \ -d -p 5432:5432 pgvector/pgvector:pg14 docker stop "postgres-vector-14" docker run \ --name "postgres-14" \ --env POSTGRES_USER="sea" \ --env POSTGRES_PASSWORD="sea" \ -d -p 5432:5432 postgres:14 docker stop "postgres-14" docker run \ --name "postgres-13" \ --env POSTGRES_USER="sea" \ --env POSTGRES_PASSWORD="sea" \ -d -p 5432:5432 postgres:13 docker stop "postgres-13" docker run \ --name "postgres-12" \ --env POSTGRES_USER="sea" \ --env POSTGRES_PASSWORD="sea" \ -d -p 5432:5432 postgres:12 docker stop "postgres-12" docker run \ --name "postgres-11" \ --env POSTGRES_USER="sea" \ --env POSTGRES_PASSWORD="sea" \ -d -p 5432:5432 postgres:11 docker stop "postgres-11" ================================================ FILE: build-tools/make-sync.sh ================================================ rm -rf sea-orm-sync/src rm -rf sea-orm-sync/tests cp -r src sea-orm-sync cp -r tests sea-orm-sync cp -r examples/quickstart/src/main.rs sea-orm-sync/examples/quickstart/src/main.rs rm -rf sea-orm-sync/src/bin cd sea-orm-sync find src -type f -name '*.rs' -exec sed -i '' "s/Pin, DbErr>> + 'a + Send>>/Result, DbErr>/" {} + find src -type f -name '*.rs' -exec sed -i '' "s/Pin> + Send + 'b>>/Result/" {} + find src -type f -name '*.rs' -exec sed -i '' "s/Pin> + Send + 'c>>/Result/" {} + find src -type f -name '*.rs' -exec sed -i '' 's/Box::pin(async move {/({/' {} + find tests -type f -name '*.rs' -exec sed -i '' 's/Box::pin(async move {/({/' {} + find src -type f -name '*.rs' -exec sed -i '' 's/async //' {} + find tests -type f -name '*.rs' -exec sed -i '' 's/async //' {} + find examples -type f -name '*.rs' -exec sed -i '' 's/async //' {} + find src -type f -name '*.rs' -exec sed -i '' 's/\.await//' {} + find tests -type f -name '*.rs' -exec sed -i '' 's/\.await//' {} + find examples -type f -name '*.rs' -exec sed -i '' 's/\.await//' {} + find src -type f -name '*.rs' -exec sed -i '' '/#\[async_trait::async_trait\]/d' {} + find tests -type f -name '*.rs' -exec sed -i '' '/#\[async_trait::async_trait\]/d' {} + find src -type f -name '*.rs' -exec sed -i '' 's/#\[smol_potat::test\]/#\[test\]/' {} + find src -type f -name '*.rs' -exec sed -i '' '/#\[smol_potat::main\]/d' {} + find examples -type f -name '*.rs' -exec sed -i '' '/#\[tokio::main\]/d' {} + find src -type f -name '*.rs' -exec sed -i '' 's/#\[tokio::test\]/#\[test\]/' {} + find tests -type f -name '*.rs' -exec sed -i '' 's/#\[cfg(feature = "sqlx-sqlite")\]/#\[cfg(feature = "rusqlite")\]/' {} + find src -type f -name '*.rs' -exec sed -i '' "/[a-zA-Z]+: Send + Sync,/d" {} + find src -type f -name '*.rs' -exec sed -i '' "/[a-zA-Z]+: Send,/d" {} + find src -type f -name '*.rs' -exec sed -i '' "s/: Send + Sync {/ {/" {} + find src -type f -name '*.rs' -exec sed -i '' "s/type Stream<'a>: Stream/type Stream<'a>: Iterator/" {} + find src -type f -name '*.rs' -exec sed -i '' "s/: Send {/ {/" {} + find src -type f -name '*.rs' -exec sed -i '' "s/>: Send$/>/" {} + find src -type f -name '*.rs' -exec sed -i '' "s/: Sync {/ {/" {} + find src -type f -name '*.rs' -exec sed -i '' "s/Send + Sync + //" {} + find src -type f -name '*.rs' -exec sed -i '' "s/ + Sync//" {} + find src -type f -name '*.rs' -exec sed -i '' "s/ + Send//" {} + find src -type f -name '*.rs' -exec sed -i '' "s/Send + //" {} + find src -type f -name '*.rs' -exec sed -i '' 's/Arc/Arc/' {} + find src -type f -name '*.rs' -exec sed -i '' '/T: Send,/d' {} + find src -type f -name '*.rs' -exec sed -i '' '/R::Model: Send,/d' {} + find src -type f -name '*.rs' -exec sed -i '' '/S::Item: Send,/d' {} + find src -type f -name '*.rs' -exec sed -i '' "s/Box::pin/Box::new/" {} + find src -type f -name '*.rs' -exec sed -i '' 's/impl Stream README.md cd sea-orm-sync && cargo readme --no-badges --no-indent-headings --no-license --no-template --no-title > README.md ================================================ FILE: build-tools/rustclippy.sh ================================================ #!/bin/bash set -e if [ -d ./build-tools ]; then targets=( "Cargo.toml" "sea-orm-cli/Cargo.toml" "sea-orm-codegen/Cargo.toml" "sea-orm-macros/Cargo.toml" "sea-orm-migration/Cargo.toml" "sea-orm-rocket/Cargo.toml" ) for target in "${targets[@]}"; do echo "cargo clippy --manifest-path ${target} --fix --allow-dirty --allow-staged" cargo clippy --manifest-path "${target}" --fix --allow-dirty --allow-staged done examples=(`find examples -type f -name 'Cargo.toml'`) for example in "${examples[@]}"; do echo "cargo clippy --manifest-path ${example} --fix --allow-dirty --allow-staged" cargo clippy --manifest-path "${example}" --fix --allow-dirty --allow-staged done else echo "Please execute this script from the repository root." fi ================================================ FILE: build-tools/rustfmt.sh ================================================ #!/bin/bash set -e taplo fmt if [ -d ./build-tools ]; then targets=( "Cargo.toml" "sea-orm-cli/Cargo.toml" "sea-orm-codegen/Cargo.toml" "sea-orm-macros/Cargo.toml" "sea-orm-migration/Cargo.toml" "sea-orm-rocket/Cargo.toml" ) for target in "${targets[@]}"; do echo "cargo +nightly fmt --manifest-path ${target} --all" cargo +nightly fmt --manifest-path "${target}" --all done examples=(`find examples -type f -name 'Cargo.toml'`) for example in "${examples[@]}"; do echo "cargo +nightly fmt --manifest-path ${example} --all" cargo +nightly fmt --manifest-path "${example}" --all done slmd COMMUNITY.md -oi else echo "Please execute this script from the repository root." fi ================================================ FILE: build-tools/update-strum-macros.sh ================================================ rm -rf sea-orm-macros/src/strum/helpers rm -rf sea-orm-macros/src/strum/enum_iter.rs cp -r ../strum/strum_macros/src/helpers sea-orm-macros/src/strum/helpers cp -r ../strum/strum_macros/src/macros/enum_iter.rs sea-orm-macros/src/strum/enum_iter.rs sed -i 's/crate::helpers::{*/super::helpers::{/' sea-orm-macros/src/strum/enum_iter.rs sed -i 's/parse_quote!(::strum)*/parse_quote!(sea_orm::strum)/' sea-orm-macros/src/strum/helpers/type_props.rs ================================================ FILE: changelog/2.0.0-rc.20.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.20 *(since 2.0.0-rc.19)* ### New Features **Allow more stringy newtypes in `DeriveValueType`** (#2831) - Produce syntax-aware attribute errors in `DeriveValueType` macro - Allow `value_type` attr for named field structs in `DeriveValueType` - Assume no derive attributes means `DeriveValueTypeStruct` **Many-to-many self-referencing relation** (#2823) - Add primary_key check to `DeriveEntityModel` - Support `RelatedSelfVia` and `self_ref` in dense entity format - Support `self_ref` on compact_model and in reverse - Loader support for self via in reverse **ActiveModelEx: support `self_ref`** ActiveModelEx now supports self-referencing relations. **Add nullable column types** Support for nullable column types added. ### Bug Fixes **Fix missing casts when returning one** (#2825) Fixed missing type casts when using `returning_one` / `returning_all`. **Temp fix MySQL JSON equals** Workaround for MySQL JSON equality comparison issues. **Automatically convert `eq(None)` to `IS NULL`** (#2826) `eq(None)` is now correctly translated to `IS NULL` in SQL. **Fix compile error when postgres-vector is enabled** (#2821) Resolved compilation failure with the `postgres-vector` feature. ### Improvements - Entity Loader: move `EntityLoaderWithParam` trait to be owned by user for better blanket impl support - Built with SeaORM: Add FirstLook.gg (#2828) ================================================ FILE: changelog/2.0.0-rc.21.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.21 *(since 2.0.0-rc.20)* ### New Features **Rusqlite / sea-orm-sync** (#2847) - New `sea-orm-sync` crate for synchronous SQLite via rusqlite - Stream and transaction support - Integration with `sea-schema-sync` **Add `--banner-version` to CLI** (#2836) CLI now supports a `--banner-version` flag. **Add `exists` method to `PaginatorTrait`** (#2623) `PaginatorTrait` now includes an `exists` method for efficient existence checks. ### Bug Fixes **Respect `#[serde(rename)]` in `ActiveModel::from_json`** (#2842) `ActiveModel::from_json` now correctly respects `#[serde(rename)]` and `#[serde(rename_all)]` attributes when deserializing JSON. ### Improvements - Reduce Vec allocations (#2835) - Add taplo for TOML formatting (#2844) - SeaORM sync example and preparation for `sea-orm-sync` crate ================================================ FILE: changelog/2.0.0-rc.22.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.22 *(since 2.0.0-rc.21)* ### New Features **`DatabaseExecutor` unified executor type** (#2839) Added a unified `DatabaseExecutor` type that wraps either a `&DatabaseConnection` or `&DatabaseTransaction`. This moves the `SchemaManagerConnection` pattern from `sea-orm-migration` into sea-orm core, providing: - `DatabaseExecutor` enum for unified handling of connections and transactions - `IntoDatabaseExecutor` trait for ergonomic conversions - `SchemaManagerConnection` type alias for backward compatibility - `SchemaManagerConnectionExt` trait for migration-specific methods ### Bug Fixes *None in this release.* ### Improvements **Refactor Value array** (#2849) Refactored the internal `Value` array handling for improved consistency and maintainability. **Refactor CI** (#2859) - Parallelized test compilation for faster CI runs - Updated `actions/cache` to v5 - Fixed cache keys **sea-orm-sync example** Added and improved the `sea-orm-sync` example. **Update examples** Updated example code across the repository. ================================================ FILE: changelog/2.0.0-rc.23.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.23 *(since 2.0.0-rc.22)* ### New Features **`DeriveValueType` implements `IntoActiveValue`** (#2868) `DeriveValueType` now automatically implements `IntoActiveValue`, so custom value types can be used directly when building ActiveModels without additional boilerplate. **`IntoActiveValue` for more types** `IntoActiveValue` is now implemented for additional standard types, improving ergonomics when setting ActiveModel fields. ### Bug Fixes **Fix sea-orm-sync** Fixed issues in the `sea-orm-sync` crate. **Fix sync** Fixed sync-related bugs. **Fix tests** Resolved failing tests. **Insert many on conflict do nothing returning** (#2389) Added test coverage and fixes for `insert_many` with `on_conflict_do_nothing` and `returning` clauses. ### Improvements **Remove `NotU8` trait** (#2868) The `NotU8` trait has been removed. `DeriveValueType` now handles type constraints internally, simplifying the derive macro. **Remove `bigdecimal` from default features** The `bigdecimal` dependency is no longer included in default features, reducing the default dependency footprint. Enable it explicitly if needed. **CI improvements** Various CI fixes and improvements. ================================================ FILE: changelog/2.0.0-rc.24.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.24 *(since 2.0.0-rc.23)* ### Improvements - Bumped `sea-query` to 1.0.0-rc.27 - Updated example projects to use the new release ================================================ FILE: changelog/2.0.0-rc.25.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.25 *(since 2.0.0-rc.24)* ### Bug Fixes **Restore value system** Restored the value system across the core crate, drivers (MySQL, PostgreSQL, SQLite), and entity/column types. Refactored executor and query handling for consistency. **Fix sea-orm-sync** Aligned `sea-orm-sync` with the restored value system changes in the main crate. Updated drivers, entity types, and executor/query logic to match. ### Improvements - Bumped `sea-query` to 1.0.0-rc.28 - Updated example projects to use the new release ================================================ FILE: changelog/2.0.0-rc.26.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.26 *(since 2.0.0-rc.25)* ### New Features **PostgreSQL legacy serial primary key option** Added the `postgres-use-serial-pk` feature flag to use legacy `serial` / `bigserial` types for auto-increment primary keys instead of `GENERATED BY DEFAULT AS IDENTITY`. Enable with `postgres-use-serial-pk` in your `Cargo.toml` if you need compatibility with older PostgreSQL setups or tooling. ================================================ FILE: changelog/2.0.0-rc.27.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.27 *(since 2.0.0-rc.26)* ### Improvements **DeriveValueType implements NotU8** `DeriveValueType` now also implements `sea_query::postgres_array::NotU8`, enabling custom value types to work with PostgreSQL array columns. This is auto-generated when the `postgres-array` feature is enabled. Remove any manual `NotU8` implementations on your custom types to avoid conflicts. ================================================ FILE: changelog/2.0.0-rc.28.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.28 *(since 2.0.0-rc.27)* ### New Features **`sqlx-all` feature in sea-orm-migration** (#2887) Added `sqlx-all` feature flag to `sea-orm-migration` for compatibility with sqlx's all-database feature. **`set_if_not_equals_and` on ActiveValue** (#2888) Added `set_if_not_equals_and` method to `ActiveValue` for conditional updates based on value comparison. **Set auto_increment to false for String/Uuid primary keys by default** (#2881) Macros now set `auto_increment = false` by default when the primary key is `String` or `Uuid`, avoiding incorrect schema generation. ### Bug Fixes **Postgres: combine SET TRANSACTION statements** (#2893) Combined `SET TRANSACTION ISOLATION LEVEL` and `ACCESS MODE` into a single statement for PostgreSQL, fixing transaction setup. ### Improvements **Debug log for entity registry** (#2900) Added debug logging for entity registry operations to aid troubleshooting. **Deprecate do_nothing and on_empty_do_nothing** (#2883) Deprecated `do_nothing` and `on_empty_do_nothing` on insert operations. Added documentation and tests for the deprecation path. **Add Rustify to COMMUNITY.md** (#2898) Added Rustify bot to the community bots section, covering multi-source lyrics, AI analysis, real-time profanity detection, and auto-skip features. ================================================ FILE: changelog/2.0.0-rc.29.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.29 *(since 2.0.0-rc.28)* ### New Features **Tracing spans for database operations** (#2885) Added `ObservabilityHook` and `with_db_span!` macro for distributed tracing support. Database operations and transactions emit tracing spans when the `tracing-spans` feature is enabled. **Wrapper type for storing UUIDs as TEXT** (#2914) Added a wrapper type for storing `Uuid` values as TEXT columns. **`Insert::try_insert` method** Added `try_insert` to the `Insert` trait for fallible insert operations. **Filter relations outside generation set** (#2913) Codegen now filters relations that fall outside the generation set. ### Bug Fixes **Allow LEFT JOIN to produce None for nested models** (#2845) Fixed handling of nested `Option` in `FromQueryResult` when using LEFT JOINs, so optional nested models correctly produce `None` when no related row exists. **Fix DeriveIntoActiveModel** (#2926) Fixed `DeriveIntoActiveModel` derive macro; added test coverage. **Fix tracing spans visibility** (#2925) Corrected tracing span visibility for database operations. **Fix derive enums without per-case rename** (#2922) Fixed codegen for enums that do not use per-variant `rename` attributes. Addresses [#2921](https://github.com/SeaQL/sea-orm/issues/2921). **Fix renaming enum variants starting with digits** (#2905) Enum variants that start with digits are now correctly renamed during codegen. **Revert multi-argument extra attribute support** (#2915) Reverted the multi-argument extra attribute codegen change (#2560) due to incorrect test input parsing that caused `syn` to panic. The attribute format should split arguments by `,`. **Add missing lifetime hint to `EntityName::table_name`** (#2907) Added the missing lifetime hint to fix compilation in certain contexts. ### Improvements **Optimize exists** (#2909) Improved performance of the `exists` query. ================================================ FILE: changelog/2.0.0-rc.30.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.30 *(since 2.0.0-rc.29)* ### Improvements **Dependency updates** Bumped `sea-query` dependency. **Examples** Updated examples. ================================================ FILE: changelog/2.0.0-rc.31.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.31 *(since 2.0.0-rc.30)* ### New Features **`ne_all` for `Condition`** Added `ne_all` to complement `eq_any`, allowing negation of multiple values in conditions. **Typed column for `TextUuid`** (#2717) Added typed column support for `TextUuid` type. **Custom derives for `Model` and `ModelEx`** (#2933) Macros now support `model_attrs` and `model_ex_attrs` to pass custom derives and attributes to the generated `Model` and `ModelEx` structs. Enables customization such as TypeScript interface names when using `ts-rs`: ```rust #[sea_orm::model] #[derive(TS, ...)] #[sea_orm(model_attrs(ts(rename = "Fruit")))] #[sea_orm(model_ex_attrs(ts(rename = "FruitEx")))] struct Model { ... } ``` ### Bug Fixes **`COUNT(*)` overflow on MySQL and SQLite** (#2944) `COUNT(*)` now returns `i64` on all backends. MySQL's `COUNT(*)` returns `BIGINT` (64-bit); previously it was read as `i32`, which could overflow for large datasets. **Proxy error handling** (#2935) Fixed proxy error handling. ### Improvements **Relaxed trait bounds for `eq_any` / `ne_all`** Relaxed trait bounds on `eq_any` and `ne_all` for broader compatibility. Depends on latest `sea-query`. **Raw string hashes** (#2381) Refactored to remove raw string hashes for improved readability. ================================================ FILE: changelog/2.0.0-rc.32.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.32 *(since 2.0.0-rc.31)* ### New Features **`MigratorTrait` with `self`** (#2806) `MigratorTrait` now uses `MigratorTraitSelf` with a shim, enabling migrator implementations that take `self` by reference. **PostgreSQL `application_name`** Added support for setting `application_name` when connecting to PostgreSQL. ### Improvements - Updated examples ================================================ FILE: changelog/2.0.0-rc.34.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.34 *(since 2.0.0-rc.32)* ### New Features **Arrow / Parquet support** (#2957) - Extracted `sea-orm-arrow` into a dedicated crate - Added `ArrowSchema`, `DeriveArrowSchema` for entity-to-Arrow conversion - Support decimal with different formats and timestamp with different timezone/resolution - Added `ActiveModel::from_arrow` for creating ActiveModels from Arrow arrays - Parquet example added - Arrow support in `sea-orm-sync` **`DeriveValueType`: `try_from_u64`** (#2958) `DeriveValueType` now derives `TryFromU64` for custom value types. **Derive `Clone` for topologies** (#2954) Topology types can now derive `Clone`. **`preserve-user-modifications` experimental flag** (#2964) Added experimental `preserve-user-modifications` option for schema sync; default is `false`. **Optional `sea-schema`** `sea-schema` can be optional in some configurations. ### Bug Fixes **Don't create redundant unique indexes** (#2950) Fixed creation of redundant unique indexes on PostgreSQL and SQLite when a column is already unique. Fixes #2873. **Typo: "exsiting" → "existing"** (#2956) ### Improvements - Refactored `sea-orm-arrow`; upgraded to Arrow 57; removed direct Arrow dependency - Improved docs for `DeriveValueType` (#2958) - Split up test suite by creating only needed tables per test - Support SeaORM X in `sea-orm-sync` ================================================ FILE: changelog/2.0.0-rc.35.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.35 *(since 2.0.0-rc.34)* ### New Features **SQLite Transaction Modes** (#2932) Added `begin_with_options` to `TransactionTrait`, allowing you to specify SQLite transaction modes (`DEFERRED`, `IMMEDIATE`, `EXCLUSIVE`), along with isolation level and access mode for other backends. Works for both sqlx-sqlite and rusqlite. ```rust use sea_orm::{TransactionTrait, TransactionOptions, SqliteTransactionMode}; let txn = db.begin_with_options(TransactionOptions { sqlite_transaction_mode: Some(SqliteTransactionMode::Immediate), ..Default::default() }).await?; ``` Nested transactions correctly fall back to `SAVEPOINT` regardless of the mode. **Extend `DeriveIntoActiveModel`** (#2961) `DeriveIntoActiveModel` now supports `set`, `default`, `ignore`, `exhaustive`, and custom `active_model` path attributes for fine-grained control when converting "form" or "input" structs into ActiveModels. **`set`** — always set fields not present on the struct: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "fruit::ActiveModel", set(cake_id = "None"))] struct NewFruit { name: String, // cake_id is not on the struct, but will always be Set(None) } NewFruit { name: "Apple".into() }.into_active_model() // => ActiveModel { id: NotSet, name: Set("Apple"), cake_id: Set(None) } ``` Multiple `set` entries can be combined or split across attributes: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm( active_model = "fruit::ActiveModel", set(name = "String::from(\"cherry\")", cake_id = "None") )] struct IdOnlyFruit { id: i32, } IdOnlyFruit { id: 1 }.into_active_model() // => ActiveModel { id: Set(1), name: Set("cherry"), cake_id: Set(None) } ``` **`default`** — fallback value when an `Option` field is `None`: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "fruit::ActiveModel")] struct NewFruit { #[sea_orm(default = "String::from(\"Unnamed\")")] name: Option, } NewFruit { name: Some("Apple".into()) }.into_active_model() // => ActiveModel { id: NotSet, name: Set("Apple"), cake_id: NotSet } NewFruit { name: None }.into_active_model() // => ActiveModel { id: NotSet, name: Set("Unnamed"), cake_id: NotSet } ``` Bare `#[sea_orm(default)]` (without a value) uses `Default::default()` as the fallback. This also works with custom enum types that implement `Into>`. **`ignore`** — exclude struct fields from the ActiveModel: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "fruit::ActiveModel")] struct NewFruit { name: String, cake_id: i32, #[sea_orm(ignore)] _extra: String, // not mapped to ActiveModel } ``` **`exhaustive`** — require all ActiveModel fields to be either on the struct or in `set(...)`: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "fruit::ActiveModel", exhaustive, set(cake_id = "None"))] struct FullFruit { id: i32, name: String, // cake_id is covered by set(...), so all fields are accounted for } ``` **Combining everything** — `set` + `default` + `ignore` + `exhaustive`: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "fruit::ActiveModel", exhaustive, set(cake_id = "None"))] struct NewFruit { id: i32, #[sea_orm(default = "String::from(\"Unnamed\")")] name: Option, } NewFruit { id: 1, name: Some("Apple".into()) }.into_active_model() // => ActiveModel { id: Set(1), name: Set("Apple"), cake_id: Set(None) } NewFruit { id: 2, name: None }.into_active_model() // => ActiveModel { id: Set(2), name: Set("Unnamed"), cake_id: Set(None) } ``` **`IntoSimpleExpr` for `FunctionCall`** (#2822) `FunctionCall` now implements `IntoSimpleExpr`, so function calls can be used directly in select expressions and filters without wrapping in `SimpleExpr`. **Arrow: Support `Decimal64` and Fixed-Size Binary** (#2957) - Decimal columns with precision <= 18 now map to Arrow `Decimal64` (was always `Decimal128`) - Precision 19-38 maps to `Decimal128`, above 38 to `Decimal256` - Added `FixedSizeBinary(N)` support via `#[sea_orm(arrow_byte_width = N)]` - Added `BinaryArray` / `LargeBinaryArray` / `FixedSizeBinaryArray` to `Value::Bytes` conversion **Optional `time` crate for Migrations** (#2865) Migrations can now use the `time` crate instead of `std::time::SystemTime` for timestamps, enabling compilation to WASM targets. Activate with the `with-time` feature on `sea-orm-migration`. **OpenTelemetry `SpanKind::Client`** (#2937) The `db_span!` macro now emits `otel.kind = "client"`, ensuring database spans are properly recognized as client spans by APM tools (Datadog, Jaeger, etc.). ### Bug Fixes **Fix unique column in schema sync** (#2971) Columns marked with `#[sea_orm(unique)]` are now correctly handled by the schema sync/diff builder, generating proper unique constraints instead of being silently ignored. **Fix `DeriveArrowSchema` with split attributes** (#2973) Fixed a compilation error when `#[sea_orm(...)]` attributes were split across multiple lines on the same field (e.g. `#[sea_orm(primary_key)]` and `#[sea_orm(auto_increment = false)]` separately). The macro now properly consumes attributes it doesn't recognize. **Map internal error types properly** Internal errors from the schema builder are now mapped to the correct `DbErr` variants instead of being lost or mistyped. ### Improvements **Pi Spigot Example** The `sea-orm-sync` pi spigot example has been refactored into a tutorial-style example with: - OOP `PiSpigot` struct with state machine pattern (`new` / `step` / `finalize` / `to_state` / `from_state`) - `clap` CLI with `--digits`, `--checkpoint`, and `--db` flags - Comprehensive tests against 1000 known digits of pi, including three-phase checkpoint/resume - Tutorial README demonstrating how to add SQLite persistence to any program ================================================ FILE: changelog/2.0.0-rc.36.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.36 *(since 2.0.0-rc.35)* ### New Features **Per-Migration Transaction Control** (#2980) Previously, all Postgres migrations ran inside a single batch transaction, while MySQL and SQLite ran without one. This was an all-or-nothing approach with no way to opt out for individual migrations (e.g. `CREATE INDEX CONCURRENTLY` on Postgres requires running outside a transaction). `MigrationTrait` now has a `use_transaction()` method to control this per migration: ```rust impl MigrationTrait for Migration { fn use_transaction(&self) -> Option { Some(false) // opt out of automatic transaction } } ``` - `None` (default): follow backend convention — Postgres uses a transaction, MySQL/SQLite do not - `Some(true)`: force a transaction on any backend - `Some(false)`: disable automatic transaction wrapping For migrations that opt out, `SchemaManager::begin()` and `SchemaManager::commit()` allow manual transaction control within the migration body: ```rust async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // DDL in a transaction let m = manager.begin().await?; m.create_table( Table::create() .table("my_table") .col(pk_auto("id")) .col(string("name")) .to_owned(), ).await?; m.commit().await?; // Non-transactional DDL manager.get_connection() .execute_unprepared("CREATE INDEX CONCURRENTLY idx_name ON my_table (name)") .await?; Ok(()) } ``` Core changes: - Added `OwnedTransaction` variant to `DatabaseExecutor`, enabling `SchemaManager` to own a transaction - Added `DatabaseExecutor::is_transaction()` for runtime introspection - Each migration is now wrapped individually rather than in a batch ================================================ FILE: changelog/2.0.0-rc.37.md ================================================ ## Release Notes: SeaORM 2.0.0-rc.37 *(since 2.0.0-rc.36)* ### New Features **ER Diagram Generation** (`sea-orm-cli generate entity --er-diagram`) `sea-orm-cli` can now generate a [Mermaid](https://mermaid.js.org/) ER diagram alongside the entity files. Pass `--er-diagram` to write `entities.mermaid` into the output directory: ```sh sea-orm-cli generate entity -u postgres://... -o src/entity --er-diagram ``` The diagram annotates columns with `PK`, `FK`, and `UK` markers and renders all relations — including many-to-many via junction tables — as Mermaid `erDiagram` syntax. Example output: ``` erDiagram user { int id PK varchar name varchar email UK int parent_id FK } post { int id PK text title int user_id FK } user }o--|| user : "parent_id" post }o--|| user : "user_id" post }o--o{ tag : "[post_tag]" ``` **PostgreSQL Statement Timeout** (`ConnectOptions::statement_timeout`) `ConnectOptions` now accepts a `statement_timeout` for PostgreSQL connections. The timeout is set via the connection options at connect time (no extra round-trip) and causes the server to abort any statement that exceeds the duration: ```rust ConnectOptions::new(DATABASE_URL) .statement_timeout(Duration::from_secs(30)) .to_owned() ``` Has no effect on MySQL or SQLite connections. **SQLite `?mode=` URL Parameter Support** (#2987) The rusqlite driver now parses the `?mode=` query parameter from SQLite connection URLs, matching the behaviour of the sqlx SQLite driver: | Mode | Behaviour | |---|---| | `rwc` (default) | Read-write, create if not exists | | `rw` | Read-write, must exist | | `ro` | Read-only | | `memory` | In-memory database | ```rust // Open an existing database read-only let db = Database::connect("sqlite:./data.db?mode=ro").await?; ``` Unsupported parameters or unknown mode values return a `DbErr::Conn` error. ### Bug Fixes **`no-default-features` compile errors with `mac_address` and `proxy`** (#2992) - `with-mac_address` feature: added missing `TryGetable` impls, `try_from_u64` impl, postgres array support, and `with-json` serde flag - `proxy` feature: removed an accidental hard dependency on `serde_json` (now only activated via `with-json`) - Fixed `cfg` guards on JSON/JSONB proxy row handling to require `with-json` ================================================ FILE: examples/actix_example/Cargo.toml ================================================ [package] authors = ["Sam Samai "] edition = "2024" name = "sea-orm-actix-4-beta-example" publish = false rust-version = "1.85.0" version = "0.1.0" [workspace] members = [".", "api", "entity", "migration"] [dependencies] actix-example-api = { path = "api" } ================================================ FILE: examples/actix_example/README.md ================================================ ![screenshot](Screenshot.png) # Actix 4 with SeaORM example app 1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database 1. Turn on the appropriate database feature for your chosen db in `api/Cargo.toml` (the `"sqlx-mysql",` line) 1. Execute `cargo run` to start the server 1. Visit [localhost:8000](http://localhost:8000) in browser Run server with auto-reloading: ```bash cargo install systemfd cargo-watch systemfd --no-pid -s http::8000 -- cargo watch -x run ``` Run tests: ```bash cd api cargo test ``` Run migration: ```bash cargo run -p migration -- up ``` Regenerate entity: ```bash sea-orm-cli generate entity --output-dir ./entity/src --lib --entity-format dense --with-serde both ``` ================================================ FILE: examples/actix_example/api/Cargo.toml ================================================ [package] authors = ["Sam Samai "] edition = "2024" name = "actix-example-api" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] actix-files = "0.6" actix-http = "3" actix-rt = "2.8" actix-service = "2" actix-web = "4" dotenvy = "0.15" entity = { path = "../entity" } listenfd = "1" migration = { path = "../migration" } serde = "1" tera = "1.19.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-native-tls", "sqlx-mysql", # "sqlx-postgres", # "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [dev-dependencies] actix-example-api = { path = ".", features = ["sqlite"] } tokio = { version = "1.20.0", features = ["macros", "rt"] } [features] sqlite = ["sea-orm/sqlx-sqlite"] ================================================ FILE: examples/actix_example/api/src/lib.rs ================================================ pub mod service; use actix_files::Files as Fs; use actix_web::{ App, Error, HttpRequest, HttpResponse, HttpServer, Result, error, get, middleware, post, web, }; use entity::post; use listenfd::ListenFd; use migration::{Migrator, MigratorTrait}; use sea_orm::{Database, DatabaseConnection}; use serde::{Deserialize, Serialize}; use service::{Mutation, Query}; use std::env; use tera::Tera; const DEFAULT_POSTS_PER_PAGE: u64 = 5; #[derive(Debug, Clone)] struct AppState { templates: tera::Tera, conn: DatabaseConnection, } #[derive(Debug, Deserialize)] pub struct Params { page: Option, posts_per_page: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] struct FlashData { kind: String, message: String, } #[get("/")] async fn list(req: HttpRequest, data: web::Data) -> Result { let template = &data.templates; let conn = &data.conn; // get params let params = web::Query::::from_query(req.query_string()).unwrap(); let page = params.page.unwrap_or(1); let posts_per_page = params.posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); let (posts, num_pages) = Query::find_posts_in_page(conn, page, posts_per_page) .await .expect("Cannot find posts in page"); let mut ctx = tera::Context::new(); ctx.insert("posts", &posts); ctx.insert("page", &page); ctx.insert("posts_per_page", &posts_per_page); ctx.insert("num_pages", &num_pages); let body = template .render("index.html.tera", &ctx) .map_err(|_| error::ErrorInternalServerError("Template error"))?; Ok(HttpResponse::Ok().content_type("text/html").body(body)) } #[get("/new")] async fn new(data: web::Data) -> Result { let template = &data.templates; let ctx = tera::Context::new(); let body = template .render("new.html.tera", &ctx) .map_err(|_| error::ErrorInternalServerError("Template error"))?; Ok(HttpResponse::Ok().content_type("text/html").body(body)) } #[post("/")] async fn create( data: web::Data, post_form: web::Form, ) -> Result { let conn = &data.conn; let form = post_form.into_inner(); Mutation::create_post(conn, form) .await .expect("could not insert post"); Ok(HttpResponse::Found() .append_header(("location", "/")) .finish()) } #[get(r#"/{id:\d+}"#)] async fn edit(data: web::Data, id: web::Path) -> Result { let conn = &data.conn; let template = &data.templates; let id = id.into_inner(); let post: Option = Query::find_post_by_id(conn, id) .await .expect("could not find post"); let mut ctx = tera::Context::new(); let body = match post { Some(post) => { ctx.insert("post", &post); template .render("edit.html.tera", &ctx) .map_err(|_| error::ErrorInternalServerError("Template error")) } None => { ctx.insert("uri", &format!("/{}", id)); template .render("error/404.html.tera", &ctx) .map_err(|_| error::ErrorInternalServerError("Template error")) } }; Ok(HttpResponse::Ok().content_type("text/html").body(body?)) } #[post("/{id}")] async fn update( data: web::Data, id: web::Path, post_form: web::Form, ) -> Result { let conn = &data.conn; let form = post_form.into_inner(); let id = id.into_inner(); Mutation::update_post_by_id(conn, id, form) .await .expect("could not edit post"); Ok(HttpResponse::Found() .append_header(("location", "/")) .finish()) } #[post("/delete/{id}")] async fn delete(data: web::Data, id: web::Path) -> Result { let conn = &data.conn; let id = id.into_inner(); Mutation::delete_post(conn, id) .await .expect("could not delete post"); Ok(HttpResponse::Found() .append_header(("location", "/")) .finish()) } async fn not_found(data: web::Data, request: HttpRequest) -> Result { let mut ctx = tera::Context::new(); ctx.insert("uri", request.uri().path()); let template = &data.templates; let body = template .render("error/404.html.tera", &ctx) .map_err(|_| error::ErrorInternalServerError("Template error"))?; Ok(HttpResponse::Ok().content_type("text/html").body(body)) } #[actix_web::main] async fn start() -> std::io::Result<()> { unsafe { std::env::set_var("RUST_LOG", "debug"); } tracing_subscriber::fmt::init(); // get env vars dotenvy::dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let host = env::var("HOST").expect("HOST is not set in .env file"); let port = env::var("PORT").expect("PORT is not set in .env file"); let server_url = format!("{host}:{port}"); // establish connection to database and apply migrations // -> create post table if not exists let conn = Database::connect(&db_url).await.unwrap(); Migrator::up(&conn, None).await.unwrap(); // load tera templates and build app state let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap(); let state = AppState { templates, conn }; // create server and try to serve over socket if possible let mut listenfd = ListenFd::from_env(); let mut server = HttpServer::new(move || { App::new() .service(Fs::new("/static", "./api/static")) .app_data(web::Data::new(state.clone())) .wrap(middleware::Logger::default()) // enable logger .default_service(web::route().to(not_found)) .configure(init) }); server = match listenfd.take_tcp_listener(0)? { Some(listener) => server.listen(listener)?, None => server.bind(&server_url)?, }; println!("Starting server at {server_url}"); server.run().await?; Ok(()) } fn init(cfg: &mut web::ServiceConfig) { cfg.service(list); cfg.service(new); cfg.service(create); cfg.service(edit); cfg.service(update); cfg.service(delete); } pub fn main() { let result = start(); if let Some(err) = result.err() { println!("Error: {err}"); } } ================================================ FILE: examples/actix_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/actix_example/api/src/service/mutation.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/actix_example/api/src/service/query.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/actix_example/api/static/css/normalize.css ================================================ /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined for any HTML5 element in IE 8/9. * Correct `block` display not defined for `details` or `summary` in IE 10/11 * and Firefox. * Correct `block` display not defined for `main` in IE 11. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } /** * 1. Correct `inline-block` display not defined in IE 8/9. * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. */ audio, canvas, progress, video { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9/10. * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background-color: transparent; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* Text-level semantics ========================================================================== */ /** * Address styling not present in IE 8/9/10/11, Safari, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari and Chrome. */ dfn { font-style: italic; } /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9/10. */ img { border: 0; } /** * Correct overflow not hidden in IE 9/10/11. */ svg:not(:root) { overflow: hidden; } /* Grouping content ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari. */ figure { margin: 1em 40px; } /** * Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Contain overflow in all browsers. */ pre { overflow: auto; } /** * Address odd `em`-unit font size rendering in all browsers. */ code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } /* Forms ========================================================================== */ /** * Known limitation: by default, Chrome and Safari on OS X allow very limited * styling of `select`, unless a `border` property is set. */ /** * 1. Correct color not being inherited. * Known issue: affects color of disabled elements. * 2. Correct font properties not being inherited. * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. */ button, input, optgroup, select, textarea { color: inherit; /* 1 */ font: inherit; /* 2 */ margin: 0; /* 3 */ } /** * Address `overflow` set to `hidden` in IE 8/9/10/11. */ button { overflow: visible; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. * Correct `select` style inheritance in Firefox. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ input { line-height: normal; } /** * It's recommended that you don't attempt to style these elements. * Firefox's implementation doesn't respect box-sizing, padding, or width. * * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Fix the cursor style for Chrome's increment/decrement buttons. For certain * `font-size` values of the `input`, it causes the cursor style of the * decrement button to change from `default` to `text`. */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Address `appearance` set to `searchfield` in Safari and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari and Chrome on OS X. * Safari (but not Chrome) clips the cancel button when the search input has * padding (and `textfield` appearance). */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9/10/11. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * Remove default vertical scrollbar in IE 8/9/10/11. */ textarea { overflow: auto; } /** * Don't inherit the `font-weight` (applied by a rule above). * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. */ optgroup { font-weight: bold; } /* Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } ================================================ FILE: examples/actix_example/api/static/css/skeleton.css ================================================ /* * Skeleton V2.0.4 * Copyright 2014, Dave Gamache * www.getskeleton.com * Free to use under the MIT license. * https://opensource.org/licenses/mit-license.php * 12/29/2014 */ /* Table of contents –––––––––––––––––––––––––––––––––––––––––––––––––– - Grid - Base Styles - Typography - Links - Buttons - Forms - Lists - Code - Tables - Spacing - Utilities - Clearing - Media Queries */ /* Grid –––––––––––––––––––––––––––––––––––––––––––––––––– */ .container { position: relative; width: 100%; max-width: 960px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } .column, .columns { width: 100%; float: left; box-sizing: border-box; } /* For devices larger than 400px */ @media (min-width: 400px) { .container { width: 85%; padding: 0; } } /* For devices larger than 550px */ @media (min-width: 550px) { .container { width: 80%; } .column, .columns { margin-left: 4%; } .column:first-child, .columns:first-child { margin-left: 0; } .one.column, .one.columns { width: 4.66666666667%; } .two.columns { width: 13.3333333333%; } .three.columns { width: 22%; } .four.columns { width: 30.6666666667%; } .five.columns { width: 39.3333333333%; } .six.columns { width: 48%; } .seven.columns { width: 56.6666666667%; } .eight.columns { width: 65.3333333333%; } .nine.columns { width: 74.0%; } .ten.columns { width: 82.6666666667%; } .eleven.columns { width: 91.3333333333%; } .twelve.columns { width: 100%; margin-left: 0; } .one-third.column { width: 30.6666666667%; } .two-thirds.column { width: 65.3333333333%; } .one-half.column { width: 48%; } /* Offsets */ .offset-by-one.column, .offset-by-one.columns { margin-left: 8.66666666667%; } .offset-by-two.column, .offset-by-two.columns { margin-left: 17.3333333333%; } .offset-by-three.column, .offset-by-three.columns { margin-left: 26%; } .offset-by-four.column, .offset-by-four.columns { margin-left: 34.6666666667%; } .offset-by-five.column, .offset-by-five.columns { margin-left: 43.3333333333%; } .offset-by-six.column, .offset-by-six.columns { margin-left: 52%; } .offset-by-seven.column, .offset-by-seven.columns { margin-left: 60.6666666667%; } .offset-by-eight.column, .offset-by-eight.columns { margin-left: 69.3333333333%; } .offset-by-nine.column, .offset-by-nine.columns { margin-left: 78.0%; } .offset-by-ten.column, .offset-by-ten.columns { margin-left: 86.6666666667%; } .offset-by-eleven.column, .offset-by-eleven.columns { margin-left: 95.3333333333%; } .offset-by-one-third.column, .offset-by-one-third.columns { margin-left: 34.6666666667%; } .offset-by-two-thirds.column, .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } .offset-by-one-half.column, .offset-by-one-half.columns { margin-left: 52%; } } /* Base Styles –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* NOTE html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */ html { font-size: 62.5%; } body { font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ line-height: 1.6; font-weight: 400; font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #222; } /* Typography –––––––––––––––––––––––––––––––––––––––––––––––––– */ h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 2rem; font-weight: 300; } h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } /* Larger than phablet */ @media (min-width: 550px) { h1 { font-size: 5.0rem; } h2 { font-size: 4.2rem; } h3 { font-size: 3.6rem; } h4 { font-size: 3.0rem; } h5 { font-size: 2.4rem; } h6 { font-size: 1.5rem; } } p { margin-top: 0; } /* Links –––––––––––––––––––––––––––––––––––––––––––––––––– */ a { color: #1EAEDB; } a:hover { color: #0FA0CE; } /* Buttons –––––––––––––––––––––––––––––––––––––––––––––––––– */ .button, button, input[type="submit"], input[type="reset"], input[type="button"] { display: inline-block; height: 38px; padding: 0 30px; color: #555; text-align: center; font-size: 11px; font-weight: 600; line-height: 38px; letter-spacing: .1rem; text-transform: uppercase; text-decoration: none; white-space: nowrap; background-color: transparent; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; box-sizing: border-box; } .button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover, .button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus { color: #333; border-color: #888; outline: 0; } .button.button-primary, button.button-primary, button.primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary { color: #FFF; background-color: #33C3F0; border-color: #33C3F0; } .button.button-primary:hover, button.button-primary:hover, button.primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, button.primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus { color: #FFF; background-color: #1EAEDB; border-color: #1EAEDB; } /* Forms –––––––––––––––––––––––––––––––––––––––––––––––––– */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select { height: 38px; padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ background-color: #fff; border: 1px solid #D1D1D1; border-radius: 4px; box-shadow: none; box-sizing: border-box; } /* Removes awkward default styles on some inputs for iOS */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; } textarea { min-height: 65px; padding-top: 6px; padding-bottom: 6px; } input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus { border: 1px solid #33C3F0; outline: 0; } label, legend { display: block; margin-bottom: .5rem; font-weight: 600; } fieldset { padding: 0; border-width: 0; } input[type="checkbox"], input[type="radio"] { display: inline; } label > .label-body { display: inline-block; margin-left: .5rem; font-weight: normal; } /* Lists –––––––––––––––––––––––––––––––––––––––––––––––––– */ ul { list-style: circle inside; } ol { list-style: decimal inside; } ol, ul { padding-left: 0; margin-top: 0; } ul ul, ul ol, ol ol, ol ul { margin: 1.5rem 0 1.5rem 3rem; font-size: 90%; } li { margin-bottom: 1rem; } /* Code –––––––––––––––––––––––––––––––––––––––––––––––––– */ code { padding: .2rem .5rem; margin: 0 .2rem; font-size: 90%; white-space: nowrap; background: #F1F1F1; border: 1px solid #E1E1E1; border-radius: 4px; } pre > code { display: block; padding: 1rem 1.5rem; white-space: pre; } /* Tables –––––––––––––––––––––––––––––––––––––––––––––––––– */ th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #E1E1E1; } th:first-child, td:first-child { padding-left: 0; } th:last-child, td:last-child { padding-right: 0; } /* Spacing –––––––––––––––––––––––––––––––––––––––––––––––––– */ button, .button { margin-bottom: 1rem; } input, textarea, select, fieldset { margin-bottom: 1.5rem; } pre, blockquote, dl, figure, table, p, ul, ol, form { margin-bottom: 2.5rem; } /* Utilities –––––––––––––––––––––––––––––––––––––––––––––––––– */ .u-full-width { width: 100%; box-sizing: border-box; } .u-max-full-width { max-width: 100%; box-sizing: border-box; } .u-pull-right { float: right; } .u-pull-left { float: left; } /* Misc –––––––––––––––––––––––––––––––––––––––––––––––––– */ hr { margin-top: 3rem; margin-bottom: 3.5rem; border-width: 0; border-top: 1px solid #E1E1E1; } /* Clearing –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Self Clearing Goodness */ .container:after, .row:after, .u-cf { content: ""; display: table; clear: both; } /* Media Queries –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Note: The best way to structure the use of media queries is to create the queries near the relevant code. For example, if you wanted to change the styles for buttons on small devices, paste the mobile query code up in the buttons section and style it there. */ /* Larger than mobile */ @media (min-width: 400px) {} /* Larger than phablet (also point when grid becomes active) */ @media (min-width: 550px) {} /* Larger than tablet */ @media (min-width: 750px) {} /* Larger than desktop */ @media (min-width: 1000px) {} /* Larger than Desktop HD */ @media (min-width: 1200px) {} ================================================ FILE: examples/actix_example/api/static/css/style.css ================================================ .field-error { border: 1px solid #ff0000 !important; } .field-error-flash { color: #ff0000; display: block; margin: -10px 0 10px 0; } .field-success { border: 1px solid #5ab953 !important; } .field-success-flash { color: #5ab953; display: block; margin: -10px 0 10px 0; } span.completed { text-decoration: line-through; } form.inline { display: inline; } form.link, button.link { display: inline; color: #1eaedb; border: none; outline: none; background: none; cursor: pointer; padding: 0; margin: 0 0 0 0; height: inherit; text-decoration: underline; font-size: inherit; text-transform: none; font-weight: normal; line-height: inherit; letter-spacing: inherit; } form.link:hover, button.link:hover { color: #0fa0ce; } button.small { height: 20px; padding: 0 10px; font-size: 10px; line-height: 20px; margin: 0 2.5px; } .post:hover { background-color: #bce2ee; } .post td { padding: 5px; width: 150px; } #delete-button { color: red; border-color: red; } ================================================ FILE: examples/actix_example/api/templates/edit.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Edit Post

{% endblock content %} ================================================ FILE: examples/actix_example/api/templates/error/404.html.tera ================================================ 404 - tera

404: Hey! There's nothing here.

The page at {{ uri }} does not exist! ================================================ FILE: examples/actix_example/api/templates/index.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Posts

{% if flash %} {{ flash.message }} {% endif %} {% for post in posts %} {% endfor %}
ID Title Text
{{ post.id }} {{ post.title }} {{ post.text }}
{% if page == 1 %} Previous {% else %} Previous {% endif %} | {% if page == num_pages %} Next {% else %} Next {% endif %}
{% endblock content %} ================================================ FILE: examples/actix_example/api/templates/layout.html.tera ================================================ Actix Example

{% block content %}{% endblock content %}
================================================ FILE: examples/actix_example/api/templates/new.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

New Post

{% endblock content %} ================================================ FILE: examples/actix_example/api/tests/crud_tests.rs ================================================ use actix_example_api::service::{Mutation, Query}; use entity::post; use sea_orm::Database; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/actix_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/actix_example/entity/src/lib.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub mod prelude; pub mod post; ================================================ FILE: examples/actix_example/entity/src/post.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/actix_example/entity/src/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub use super::post::Entity as Post; ================================================ FILE: examples/actix_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-mysql", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/actix_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/actix_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; mod m20220120_000002_seed_posts; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } } ================================================ FILE: examples/actix_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/actix_example/migration/src/m20220120_000002_seed_posts.rs ================================================ use entity::post; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Posts table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Post", "Second Post"]; post::Entity::delete_many() .filter(post::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Posts seeded data removed."); Ok(()) } } ================================================ FILE: examples/actix_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/actix_example/src/main.rs ================================================ fn main() { actix_example_api::main(); } ================================================ FILE: examples/axum_example/Cargo.toml ================================================ [package] authors = ["Yoshiera Huang "] edition = "2024" name = "sea-orm-axum-example" publish = false rust-version = "1.85.0" version = "0.1.0" [workspace] members = [".", "api", "entity", "migration"] [dependencies] axum-example-api = { path = "api" } ================================================ FILE: examples/axum_example/README.md ================================================ ![screenshot](Screenshot.png) # Axum with SeaORM example app 1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database 1. Turn on the appropriate database feature for your chosen db in `api/Cargo.toml` (the `"sqlx-postgres",` line) 1. Execute `cargo run` to start the server 1. Visit [localhost:8000](http://localhost:8000) in browser Run tests: ```bash cd api cargo test ``` Run migration: ```bash cargo run -p migration -- up ``` Regenerate entity: ```bash sea-orm-cli generate entity --output-dir ./entity/src --lib --entity-format dense --with-serde both ``` ================================================ FILE: examples/axum_example/api/Cargo.toml ================================================ [package] authors = ["Yoshiera Huang "] edition = "2024" name = "axum-example-api" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] anyhow = "1.0.75" axum = "0.8" dotenvy = "0.15.7" entity = { path = "../entity" } migration = { path = "../migration" } serde = "1.0.193" serde_json = "1.0.108" tera = "1.19.1" tokio = { version = "1.34.0", features = ["full"] } tower = "0.5" tower-cookies = "0.11" tower-http = { version = "0.6", features = ["fs"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-native-tls", "sqlx-postgres", # "sqlx-mysql", # "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [dev-dependencies] axum-example-api = { path = ".", features = ["sqlite"] } [features] sqlite = ["sea-orm/sqlx-sqlite"] ================================================ FILE: examples/axum_example/api/src/flash.rs ================================================ use axum::http::{HeaderMap, HeaderValue, StatusCode, header}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use tower_cookies::{Cookie, Cookies}; #[derive(Deserialize)] struct ValuedMessage { #[serde(rename = "_")] value: T, } #[derive(Serialize)] struct ValuedMessageRef<'a, T> { #[serde(rename = "_")] value: &'a T, } const FLASH_COOKIE_NAME: &str = "_flash"; pub fn get_flash_cookie(cookies: &Cookies) -> Option where T: DeserializeOwned, { cookies.get(FLASH_COOKIE_NAME).and_then(|flash_cookie| { if let Ok(ValuedMessage:: { value }) = serde_json::from_str(flash_cookie.value()) { Some(value) } else { None } }) } pub type PostResponse = (StatusCode, HeaderMap); pub fn post_response(cookies: &mut Cookies, data: T) -> PostResponse where T: Serialize, { let valued_message_ref = ValuedMessageRef { value: &data }; let mut cookie = Cookie::new( FLASH_COOKIE_NAME, serde_json::to_string(&valued_message_ref).unwrap(), ); cookie.set_path("/"); cookies.add(cookie); let mut header = HeaderMap::new(); header.insert(header::LOCATION, HeaderValue::from_static("/")); (StatusCode::SEE_OTHER, header) } ================================================ FILE: examples/axum_example/api/src/lib.rs ================================================ mod flash; pub mod service; use axum::{ Router, extract::{Form, Path, Query, State}, http::StatusCode, response::Html, routing::{get, get_service, post}, }; use entity::post; use flash::{PostResponse, get_flash_cookie, post_response}; use migration::{Migrator, MigratorTrait}; use sea_orm::{Database, DatabaseConnection}; use serde::{Deserialize, Serialize}; use service::{Mutation, Query as QueryService}; use std::env; use tera::Tera; use tower_cookies::{CookieManagerLayer, Cookies}; use tower_http::services::ServeDir; #[tokio::main] async fn start() -> anyhow::Result<()> { unsafe { env::set_var("RUST_LOG", "debug"); } tracing_subscriber::fmt::init(); dotenvy::dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let host = env::var("HOST").expect("HOST is not set in .env file"); let port = env::var("PORT").expect("PORT is not set in .env file"); let server_url = format!("{host}:{port}"); let conn = Database::connect(db_url) .await .expect("Database connection failed"); Migrator::up(&conn, None).await.unwrap(); let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")) .expect("Tera initialization failed"); let state = AppState { templates, conn }; let app = Router::new() .route("/", get(list_posts).post(create_post)) .route("/{id}", get(edit_post).post(update_post)) .route("/new", get(new_post)) .route("/delete/{id}", post(delete_post)) .nest_service( "/static", get_service(ServeDir::new(concat!( env!("CARGO_MANIFEST_DIR"), "/static" ))) .handle_error(|error| async move { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled internal error: {error}"), ) }), ) .layer(CookieManagerLayer::new()) .with_state(state); let listener = tokio::net::TcpListener::bind(&server_url).await.unwrap(); axum::serve(listener, app).await?; Ok(()) } #[derive(Clone)] struct AppState { templates: Tera, conn: DatabaseConnection, } #[derive(Deserialize)] struct Params { page: Option, posts_per_page: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] struct FlashData { kind: String, message: String, } async fn list_posts( state: State, Query(params): Query, cookies: Cookies, ) -> Result, (StatusCode, &'static str)> { let page = params.page.unwrap_or(1); let posts_per_page = params.posts_per_page.unwrap_or(5); let (posts, num_pages) = QueryService::find_posts_in_page(&state.conn, page, posts_per_page) .await .expect("Cannot find posts in page"); let mut ctx = tera::Context::new(); ctx.insert("posts", &posts); ctx.insert("page", &page); ctx.insert("posts_per_page", &posts_per_page); ctx.insert("num_pages", &num_pages); if let Some(value) = get_flash_cookie::(&cookies) { ctx.insert("flash", &value); } let body = state .templates .render("index.html.tera", &ctx) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; Ok(Html(body)) } async fn new_post(state: State) -> Result, (StatusCode, &'static str)> { let ctx = tera::Context::new(); let body = state .templates .render("new.html.tera", &ctx) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; Ok(Html(body)) } async fn create_post( state: State, mut cookies: Cookies, form: Form, ) -> Result { let form = form.0; Mutation::create_post(&state.conn, form) .await .expect("could not insert post"); let data = FlashData { kind: "success".to_owned(), message: "Post successfully added".to_owned(), }; Ok(post_response(&mut cookies, data)) } async fn edit_post( state: State, Path(id): Path, ) -> Result, (StatusCode, &'static str)> { let post: post::Model = QueryService::find_post_by_id(&state.conn, id) .await .expect("could not find post") .unwrap_or_else(|| panic!("could not find post with id {id}")); let mut ctx = tera::Context::new(); ctx.insert("post", &post); let body = state .templates .render("edit.html.tera", &ctx) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; Ok(Html(body)) } async fn update_post( state: State, Path(id): Path, mut cookies: Cookies, form: Form, ) -> Result { let form = form.0; Mutation::update_post_by_id(&state.conn, id, form) .await .expect("could not edit post"); let data = FlashData { kind: "success".to_owned(), message: "Post successfully updated".to_owned(), }; Ok(post_response(&mut cookies, data)) } async fn delete_post( state: State, Path(id): Path, mut cookies: Cookies, ) -> Result { Mutation::delete_post(&state.conn, id) .await .expect("could not delete post"); let data = FlashData { kind: "success".to_owned(), message: "Post successfully deleted".to_owned(), }; Ok(post_response(&mut cookies, data)) } pub fn main() { let result = start(); if let Some(err) = result.err() { println!("Error: {err}"); } } ================================================ FILE: examples/axum_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/axum_example/api/src/service/mutation.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/axum_example/api/src/service/query.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/axum_example/api/static/css/normalize.css ================================================ /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined for any HTML5 element in IE 8/9. * Correct `block` display not defined for `details` or `summary` in IE 10/11 * and Firefox. * Correct `block` display not defined for `main` in IE 11. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } /** * 1. Correct `inline-block` display not defined in IE 8/9. * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. */ audio, canvas, progress, video { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9/10. * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background-color: transparent; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* Text-level semantics ========================================================================== */ /** * Address styling not present in IE 8/9/10/11, Safari, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari and Chrome. */ dfn { font-style: italic; } /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9/10. */ img { border: 0; } /** * Correct overflow not hidden in IE 9/10/11. */ svg:not(:root) { overflow: hidden; } /* Grouping content ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari. */ figure { margin: 1em 40px; } /** * Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Contain overflow in all browsers. */ pre { overflow: auto; } /** * Address odd `em`-unit font size rendering in all browsers. */ code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } /* Forms ========================================================================== */ /** * Known limitation: by default, Chrome and Safari on OS X allow very limited * styling of `select`, unless a `border` property is set. */ /** * 1. Correct color not being inherited. * Known issue: affects color of disabled elements. * 2. Correct font properties not being inherited. * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. */ button, input, optgroup, select, textarea { color: inherit; /* 1 */ font: inherit; /* 2 */ margin: 0; /* 3 */ } /** * Address `overflow` set to `hidden` in IE 8/9/10/11. */ button { overflow: visible; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. * Correct `select` style inheritance in Firefox. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ input { line-height: normal; } /** * It's recommended that you don't attempt to style these elements. * Firefox's implementation doesn't respect box-sizing, padding, or width. * * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Fix the cursor style for Chrome's increment/decrement buttons. For certain * `font-size` values of the `input`, it causes the cursor style of the * decrement button to change from `default` to `text`. */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Address `appearance` set to `searchfield` in Safari and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari and Chrome on OS X. * Safari (but not Chrome) clips the cancel button when the search input has * padding (and `textfield` appearance). */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9/10/11. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * Remove default vertical scrollbar in IE 8/9/10/11. */ textarea { overflow: auto; } /** * Don't inherit the `font-weight` (applied by a rule above). * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. */ optgroup { font-weight: bold; } /* Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } ================================================ FILE: examples/axum_example/api/static/css/skeleton.css ================================================ /* * Skeleton V2.0.4 * Copyright 2014, Dave Gamache * www.getskeleton.com * Free to use under the MIT license. * https://opensource.org/licenses/mit-license.php * 12/29/2014 */ /* Table of contents –––––––––––––––––––––––––––––––––––––––––––––––––– - Grid - Base Styles - Typography - Links - Buttons - Forms - Lists - Code - Tables - Spacing - Utilities - Clearing - Media Queries */ /* Grid –––––––––––––––––––––––––––––––––––––––––––––––––– */ .container { position: relative; width: 100%; max-width: 960px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } .column, .columns { width: 100%; float: left; box-sizing: border-box; } /* For devices larger than 400px */ @media (min-width: 400px) { .container { width: 85%; padding: 0; } } /* For devices larger than 550px */ @media (min-width: 550px) { .container { width: 80%; } .column, .columns { margin-left: 4%; } .column:first-child, .columns:first-child { margin-left: 0; } .one.column, .one.columns { width: 4.66666666667%; } .two.columns { width: 13.3333333333%; } .three.columns { width: 22%; } .four.columns { width: 30.6666666667%; } .five.columns { width: 39.3333333333%; } .six.columns { width: 48%; } .seven.columns { width: 56.6666666667%; } .eight.columns { width: 65.3333333333%; } .nine.columns { width: 74.0%; } .ten.columns { width: 82.6666666667%; } .eleven.columns { width: 91.3333333333%; } .twelve.columns { width: 100%; margin-left: 0; } .one-third.column { width: 30.6666666667%; } .two-thirds.column { width: 65.3333333333%; } .one-half.column { width: 48%; } /* Offsets */ .offset-by-one.column, .offset-by-one.columns { margin-left: 8.66666666667%; } .offset-by-two.column, .offset-by-two.columns { margin-left: 17.3333333333%; } .offset-by-three.column, .offset-by-three.columns { margin-left: 26%; } .offset-by-four.column, .offset-by-four.columns { margin-left: 34.6666666667%; } .offset-by-five.column, .offset-by-five.columns { margin-left: 43.3333333333%; } .offset-by-six.column, .offset-by-six.columns { margin-left: 52%; } .offset-by-seven.column, .offset-by-seven.columns { margin-left: 60.6666666667%; } .offset-by-eight.column, .offset-by-eight.columns { margin-left: 69.3333333333%; } .offset-by-nine.column, .offset-by-nine.columns { margin-left: 78.0%; } .offset-by-ten.column, .offset-by-ten.columns { margin-left: 86.6666666667%; } .offset-by-eleven.column, .offset-by-eleven.columns { margin-left: 95.3333333333%; } .offset-by-one-third.column, .offset-by-one-third.columns { margin-left: 34.6666666667%; } .offset-by-two-thirds.column, .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } .offset-by-one-half.column, .offset-by-one-half.columns { margin-left: 52%; } } /* Base Styles –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* NOTE html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */ html { font-size: 62.5%; } body { font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ line-height: 1.6; font-weight: 400; font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #222; } /* Typography –––––––––––––––––––––––––––––––––––––––––––––––––– */ h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 2rem; font-weight: 300; } h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } /* Larger than phablet */ @media (min-width: 550px) { h1 { font-size: 5.0rem; } h2 { font-size: 4.2rem; } h3 { font-size: 3.6rem; } h4 { font-size: 3.0rem; } h5 { font-size: 2.4rem; } h6 { font-size: 1.5rem; } } p { margin-top: 0; } /* Links –––––––––––––––––––––––––––––––––––––––––––––––––– */ a { color: #1EAEDB; } a:hover { color: #0FA0CE; } /* Buttons –––––––––––––––––––––––––––––––––––––––––––––––––– */ .button, button, input[type="submit"], input[type="reset"], input[type="button"] { display: inline-block; height: 38px; padding: 0 30px; color: #555; text-align: center; font-size: 11px; font-weight: 600; line-height: 38px; letter-spacing: .1rem; text-transform: uppercase; text-decoration: none; white-space: nowrap; background-color: transparent; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; box-sizing: border-box; } .button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover, .button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus { color: #333; border-color: #888; outline: 0; } .button.button-primary, button.button-primary, button.primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary { color: #FFF; background-color: #33C3F0; border-color: #33C3F0; } .button.button-primary:hover, button.button-primary:hover, button.primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, button.primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus { color: #FFF; background-color: #1EAEDB; border-color: #1EAEDB; } /* Forms –––––––––––––––––––––––––––––––––––––––––––––––––– */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select { height: 38px; padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ background-color: #fff; border: 1px solid #D1D1D1; border-radius: 4px; box-shadow: none; box-sizing: border-box; } /* Removes awkward default styles on some inputs for iOS */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; } textarea { min-height: 65px; padding-top: 6px; padding-bottom: 6px; } input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus { border: 1px solid #33C3F0; outline: 0; } label, legend { display: block; margin-bottom: .5rem; font-weight: 600; } fieldset { padding: 0; border-width: 0; } input[type="checkbox"], input[type="radio"] { display: inline; } label > .label-body { display: inline-block; margin-left: .5rem; font-weight: normal; } /* Lists –––––––––––––––––––––––––––––––––––––––––––––––––– */ ul { list-style: circle inside; } ol { list-style: decimal inside; } ol, ul { padding-left: 0; margin-top: 0; } ul ul, ul ol, ol ol, ol ul { margin: 1.5rem 0 1.5rem 3rem; font-size: 90%; } li { margin-bottom: 1rem; } /* Code –––––––––––––––––––––––––––––––––––––––––––––––––– */ code { padding: .2rem .5rem; margin: 0 .2rem; font-size: 90%; white-space: nowrap; background: #F1F1F1; border: 1px solid #E1E1E1; border-radius: 4px; } pre > code { display: block; padding: 1rem 1.5rem; white-space: pre; } /* Tables –––––––––––––––––––––––––––––––––––––––––––––––––– */ th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #E1E1E1; } th:first-child, td:first-child { padding-left: 0; } th:last-child, td:last-child { padding-right: 0; } /* Spacing –––––––––––––––––––––––––––––––––––––––––––––––––– */ button, .button { margin-bottom: 1rem; } input, textarea, select, fieldset { margin-bottom: 1.5rem; } pre, blockquote, dl, figure, table, p, ul, ol, form { margin-bottom: 2.5rem; } /* Utilities –––––––––––––––––––––––––––––––––––––––––––––––––– */ .u-full-width { width: 100%; box-sizing: border-box; } .u-max-full-width { max-width: 100%; box-sizing: border-box; } .u-pull-right { float: right; } .u-pull-left { float: left; } /* Misc –––––––––––––––––––––––––––––––––––––––––––––––––– */ hr { margin-top: 3rem; margin-bottom: 3.5rem; border-width: 0; border-top: 1px solid #E1E1E1; } /* Clearing –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Self Clearing Goodness */ .container:after, .row:after, .u-cf { content: ""; display: table; clear: both; } /* Media Queries –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Note: The best way to structure the use of media queries is to create the queries near the relevant code. For example, if you wanted to change the styles for buttons on small devices, paste the mobile query code up in the buttons section and style it there. */ /* Larger than mobile */ @media (min-width: 400px) {} /* Larger than phablet (also point when grid becomes active) */ @media (min-width: 550px) {} /* Larger than tablet */ @media (min-width: 750px) {} /* Larger than desktop */ @media (min-width: 1000px) {} /* Larger than Desktop HD */ @media (min-width: 1200px) {} ================================================ FILE: examples/axum_example/api/static/css/style.css ================================================ .field-error { border: 1px solid #ff0000 !important; } .field-error-flash { color: #ff0000; display: block; margin: -10px 0 10px 0; } .field-success { border: 1px solid #5ab953 !important; } .field-success-flash { color: #5ab953; display: block; margin: -10px 0 10px 0; } span.completed { text-decoration: line-through; } form.inline { display: inline; } form.link, button.link { display: inline; color: #1eaedb; border: none; outline: none; background: none; cursor: pointer; padding: 0; margin: 0 0 0 0; height: inherit; text-decoration: underline; font-size: inherit; text-transform: none; font-weight: normal; line-height: inherit; letter-spacing: inherit; } form.link:hover, button.link:hover { color: #0fa0ce; } button.small { height: 20px; padding: 0 10px; font-size: 10px; line-height: 20px; margin: 0 2.5px; } .post:hover { background-color: #bce2ee; } .post td { padding: 5px; width: 150px; } #delete-button { color: red; border-color: red; } ================================================ FILE: examples/axum_example/api/templates/edit.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Edit Post

{% endblock content %} ================================================ FILE: examples/axum_example/api/templates/error/404.html.tera ================================================ 404 - tera

404: Hey! There's nothing here.

The page at {{ uri }} does not exist! ================================================ FILE: examples/axum_example/api/templates/index.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Posts

{% if flash %} {{ flash.message }} {% endif %} {% for post in posts %} {% endfor %}
ID Title Text
{{ post.id }} {{ post.title }} {{ post.text }}
{% if page == 1 %} Previous {% else %} Previous {% endif %} | {% if page == num_pages %} Next {% else %} Next {% endif %}
{% endblock content %} ================================================ FILE: examples/axum_example/api/templates/layout.html.tera ================================================ Axum Example

{% block content %}{% endblock content %}
================================================ FILE: examples/axum_example/api/templates/new.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

New Post

{% endblock content %} ================================================ FILE: examples/axum_example/api/tests/crud_tests.rs ================================================ use axum_example_api::service::{Mutation, Query}; use entity::post; use sea_orm::Database; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/axum_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/axum_example/entity/src/lib.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub mod prelude; pub mod post; ================================================ FILE: examples/axum_example/entity/src/post.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/axum_example/entity/src/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub use super::post::Entity as Post; ================================================ FILE: examples/axum_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-postgres", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/axum_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/axum_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; mod m20220120_000002_seed_posts; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } } ================================================ FILE: examples/axum_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/axum_example/migration/src/m20220120_000002_seed_posts.rs ================================================ use entity::post; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Posts table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Post", "Second Post"]; post::Entity::delete_many() .filter(post::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Posts seeded data removed."); Ok(()) } } ================================================ FILE: examples/axum_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/axum_example/src/main.rs ================================================ fn main() { axum_example_api::main(); } ================================================ FILE: examples/basic/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-example-basic" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] futures-util = { version = "0.3" } sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-tokio-native-tls", ] } serde_json = { version = "1" } tokio = { version = "1", features = ["full"] } ================================================ FILE: examples/basic/Readme.md ================================================ # SeaORM MySQL example Prepare: Setup a test database and configure the connection string in `main.rs`. Run `bakery.sql` to setup the test table and data. Running: `cargo run` ```sh find all cakes: SELECT `cake`.`id`, `cake`.`name` FROM `cake` Model { id: 1, name: "New York Cheese" } Model { id: 2, name: "Chocolate Forest" } find all fruits: SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` Model { id: 1, name: "Blueberry", cake_id: Some(1) } Model { id: 2, name: "Raspberry", cake_id: Some(1) } Model { id: 3, name: "Strawberry", cake_id: Some(2) } Model { id: 4, name: "Apple", cake_id: None } Model { id: 5, name: "Banana", cake_id: None } Model { id: 6, name: "Cherry", cake_id: None } Model { id: 7, name: "Lemon", cake_id: None } Model { id: 8, name: "Orange", cake_id: None } Model { id: 9, name: "Pineapple", cake_id: None } ===== ===== find one by primary key: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 1 LIMIT 1 Model { id: 1, name: "New York Cheese" } find one by name: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%chocolate%' LIMIT 1 Some(Model { id: 2, name: "Chocolate Forest" }) find models belong to: SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` INNER JOIN `cake` ON `cake`.`id` = `fruit`.`cake_id` WHERE `cake`.`id` = 1 Model { id: 1, name: "Blueberry", cake_id: Some(1) } Model { id: 2, name: "Raspberry", cake_id: Some(1) } ===== ===== find fruits and cakes: SELECT `fruit`.`id` AS `A_id`, `fruit`.`name` AS `A_name`, `fruit`.`cake_id` AS `A_cake_id`, `cake`.`id` AS `B_id`, `cake`.`name` AS `B_name` FROM `fruit` LEFT JOIN `cake` ON `fruit`.`cake_id` = `cake`.`id` with loader: SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` IN (1, 1, 2, NULL, NULL, NULL, NULL, NULL, NULL) (Model { id: 1, name: "Blueberry", cake_id: Some(1) }, Some(Model { id: 1, name: "New York Cheese" })) (Model { id: 2, name: "Raspberry", cake_id: Some(1) }, Some(Model { id: 1, name: "New York Cheese" })) (Model { id: 3, name: "Strawberry", cake_id: Some(2) }, Some(Model { id: 2, name: "Chocolate Forest" })) (Model { id: 4, name: "Apple", cake_id: None }, None) (Model { id: 5, name: "Banana", cake_id: None }, None) (Model { id: 6, name: "Cherry", cake_id: None }, None) (Model { id: 7, name: "Lemon", cake_id: None }, None) (Model { id: 8, name: "Orange", cake_id: None }, None) (Model { id: 9, name: "Pineapple", cake_id: None }, None) ===== ===== find cakes with fruits: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id` FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` ORDER BY `cake`.`id` ASC with loader: SELECT `cake`.`id`, `cake`.`name` FROM `cake` SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`cake_id` IN (1, 2) (Model { id: 1, name: "New York Cheese" }, [Model { id: 1, name: "Blueberry", cake_id: Some(1) }, Model { id: 2, name: "Raspberry", cake_id: Some(1) }]) (Model { id: 2, name: "Chocolate Forest" }, [Model { id: 3, name: "Strawberry", cake_id: Some(2) }]) ===== ===== count fruits by cake: SELECT `cake`.`name`, COUNT(`fruit`.`id`) AS `num_of_fruits` FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` GROUP BY `cake`.`name` SelectResult { name: "New York Cheese", num_of_fruits: 2 } SelectResult { name: "Chocolate Forest", num_of_fruits: 1 } ===== ===== find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` ORDER BY `cake`.`id` ASC with loader: SELECT `cake`.`id`, `cake`.`name` FROM `cake` SELECT `cake_filling`.`cake_id`, `cake_filling`.`filling_id` FROM `cake_filling` WHERE `cake_filling`.`cake_id` IN (1, 2) SELECT `filling`.`id`, `filling`.`name` FROM `filling` WHERE `filling`.`id` IN (1, 2, 2, 3) (Model { id: 1, name: "New York Cheese" }, [Model { id: 1, name: "Vanilla" }, Model { id: 2, name: "Lemon" }]) (Model { id: 2, name: "Chocolate Forest" }, [Model { id: 2, name: "Lemon" }, Model { id: 3, name: "Mango" }]) find fillings for cheese cake: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 1 LIMIT 1 SELECT `filling`.`id`, `filling`.`name` FROM `filling` INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id` INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id` WHERE `cake`.`id` = 1 Model { id: 1, name: "Vanilla" } Model { id: 2, name: "Lemon" } find cakes for lemon: SELECT `filling`.`id`, `filling`.`name` FROM `filling` WHERE `filling`.`id` = 2 LIMIT 1 SELECT `cake`.`id`, `cake`.`name` FROM `cake` INNER JOIN `cake_filling` ON `cake_filling`.`cake_id` = `cake`.`id` INNER JOIN `filling` ON `filling`.`id` = `cake_filling`.`filling_id` WHERE `filling`.`id` = 2 Model { id: 1, name: "New York Cheese" } Model { id: 2, name: "Chocolate Forest" } ===== ===== find all cakes paginated: SELECT `cake`.`id`, `cake`.`name` FROM `cake` LIMIT 3 OFFSET 0 Model { id: 1, name: "New York Cheese" } Model { id: 2, name: "Chocolate Forest" } SELECT `cake`.`id`, `cake`.`name` FROM `cake` LIMIT 3 OFFSET 3 find all fruits paginated: SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 Model { id: 1, name: "Blueberry", cake_id: Some(1) } Model { id: 2, name: "Raspberry", cake_id: Some(1) } Model { id: 3, name: "Strawberry", cake_id: Some(2) } SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 3 Model { id: 4, name: "Apple", cake_id: None } Model { id: 5, name: "Banana", cake_id: None } Model { id: 6, name: "Cherry", cake_id: None } SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 6 Model { id: 7, name: "Lemon", cake_id: None } Model { id: 8, name: "Orange", cake_id: None } Model { id: 9, name: "Pineapple", cake_id: None } SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 9 find all fruits with stream: SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 Model { id: 1, name: "Blueberry", cake_id: Some(1) } Model { id: 2, name: "Raspberry", cake_id: Some(1) } Model { id: 3, name: "Strawberry", cake_id: Some(2) } SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 3 Model { id: 4, name: "Apple", cake_id: None } Model { id: 5, name: "Banana", cake_id: None } Model { id: 6, name: "Cherry", cake_id: None } SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 6 Model { id: 7, name: "Lemon", cake_id: None } Model { id: 8, name: "Orange", cake_id: None } Model { id: 9, name: "Pineapple", cake_id: None } SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 9 find all fruits in json with stream: SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 Object {"cake_id": Number(1), "id": Number(1), "name": String("Blueberry")} Object {"cake_id": Number(1), "id": Number(2), "name": String("Raspberry")} Object {"cake_id": Number(2), "id": Number(3), "name": String("Strawberry")} SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 3 Object {"cake_id": Null, "id": Number(4), "name": String("Apple")} Object {"cake_id": Null, "id": Number(5), "name": String("Banana")} Object {"cake_id": Null, "id": Number(6), "name": String("Cherry")} SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 6 Object {"cake_id": Null, "id": Number(7), "name": String("Lemon")} Object {"cake_id": Null, "id": Number(8), "name": String("Orange")} Object {"cake_id": Null, "id": Number(9), "name": String("Pineapple")} SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 9 ===== ===== fruits first page: SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 Model { id: 1, name: "Blueberry", cake_id: Some(1) } Model { id: 2, name: "Raspberry", cake_id: Some(1) } Model { id: 3, name: "Strawberry", cake_id: Some(2) } ===== ===== fruits number of page: SELECT COUNT(*) AS num_items FROM (SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit`) AS `sub_query` 3 ===== ===== INSERT INTO `fruit` (`name`) VALUES ('pear') Inserted: last_insert_id = 64 SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 64 LIMIT 1 Pear: Some(Model { id: 64, name: "pear", cake_id: None }) UPDATE `fruit` SET `name` = 'Sweet pear' WHERE `fruit`.`id` = 64 SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 64 LIMIT 1 Updated: Model { id: 64, name: "Sweet pear", cake_id: None } DELETE FROM `fruit` WHERE `fruit`.`id` = 64 Deleted: DeleteResult { rows_affected: 1 } ===== ===== INSERT INTO `fruit` (`name`) VALUES ('Banana') SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 65 LIMIT 1 Inserted: ActiveModel { id: Unchanged(65), name: Unchanged("Banana"), cake_id: Unchanged(None) } UPDATE `fruit` SET `name` = 'Banana Mongo' WHERE `fruit`.`id` = 65 SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 65 LIMIT 1 Updated: ActiveModel { id: Unchanged(65), name: Unchanged("Banana Mongo"), cake_id: Unchanged(None) } DELETE FROM `fruit` WHERE `fruit`.`id` = 65 Deleted: DeleteResult { rows_affected: 1 } ===== ===== INSERT INTO `fruit` (`name`) VALUES ('Pineapple') SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 66 LIMIT 1 Saved: ActiveModel { id: Unchanged(66), name: Unchanged("Pineapple"), cake_id: Unchanged(None) } DELETE FROM `fruit` WHERE `fruit`.`id` = 66 Deleted: DeleteResult { rows_affected: 1 } ``` ================================================ FILE: examples/basic/bakery.sql ================================================ DROP TABLE IF EXISTS `cake`; CREATE TABLE `cake` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ); INSERT INTO `cake` (`id`, `name`) VALUES (1, 'New York Cheese'), (2, 'Chocolate Forest'); DROP TABLE IF EXISTS `fruit`; CREATE TABLE `fruit` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `cake_id` int DEFAULT NULL, PRIMARY KEY (`id`), CONSTRAINT `fk-fruit-cake` FOREIGN KEY (`cake_id`) REFERENCES `cake` (`id`) ); INSERT INTO `fruit` (`id`, `name`, `cake_id`) VALUES (1, 'Blueberry', 1), (2, 'Raspberry', 1), (3, 'Strawberry', 2); INSERT INTO `fruit` (`name`, `cake_id`) VALUES ('Apple', NULL), ('Banana', NULL), ('Cherry', NULL), ('Lemon', NULL), ('Orange', NULL), ('Pineapple', NULL); DROP TABLE IF EXISTS `filling`; CREATE TABLE `filling` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ); INSERT INTO `filling` (`id`, `name`) VALUES (1, 'Vanilla'), (2, 'Lemon'), (3, 'Mango'); DROP TABLE IF EXISTS `cake_filling`; CREATE TABLE `cake_filling` ( `cake_id` int NOT NULL, `filling_id` int NOT NULL, PRIMARY KEY (`cake_id`, `filling_id`), CONSTRAINT `fk-cake_filling-cake` FOREIGN KEY (`cake_id`) REFERENCES `cake` (`id`), CONSTRAINT `fk-cake_filling-filling` FOREIGN KEY (`filling_id`) REFERENCES `filling` (`id`) ); INSERT INTO `cake_filling` (`cake_id`, `filling_id`) VALUES (1, 1), (1, 2), (2, 2), (2, 3); ================================================ FILE: examples/basic/src/entity/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option, #[sea_orm(has_many)] pub fruits: HasMany, #[sea_orm(has_many, via = "cake_filling")] pub fillings: HasMany, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/basic/src/entity/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake_filling")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub filling_id: i32, #[sea_orm( belongs_to, from = "cake_id", to = "id", on_update = "Cascade", on_delete = "Cascade" )] pub cake: HasOne, #[sea_orm( belongs_to, from = "filling_id", to = "id", on_update = "Cascade", on_delete = "Cascade" )] pub filling: HasOne, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/basic/src/entity/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "filling")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(has_many, via = "cake_filling")] pub cakes: HasMany, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/basic/src/entity/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "fruit")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub cake_id: Option, #[sea_orm(belongs_to, from = "cake_id", to = "id")] pub cake: HasOne, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/basic/src/entity/mod.rs ================================================ pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod sea_orm_active_enums; pub use cake::Entity as Cake; pub use cake_filling::Entity as CakeFilling; pub use filling::Entity as Filling; pub use fruit::Entity as Fruit; ================================================ FILE: examples/basic/src/entity/sea_orm_active_enums.rs ================================================ //! SeaORM's active enums. use sea_orm::entity::prelude::*; /// Tea active enum #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")] pub enum Tea { /// EverydayTea variant #[sea_orm(string_value = "EverydayTea")] EverydayTea, /// BreakfastTea variant #[sea_orm(string_value = "BreakfastTea")] BreakfastTea, } ================================================ FILE: examples/basic/src/main.rs ================================================ //! Basic sea-orm example. #![deny(missing_docs)] use sea_orm::Database; mod entity; mod mutation; mod query; use entity::*; use mutation::*; use query::*; #[tokio::main] async fn main() { let db = Database::connect( std::env::var("DATABASE_URL") .unwrap_or_else(|_| "mysql://sea:sea@localhost/bakery".to_owned()), ) .await .unwrap(); println!("{db:?}\n"); println!("===== =====\n"); all_about_query(&db).await.unwrap(); println!("===== =====\n"); all_about_mutation(&db).await.unwrap(); } ================================================ FILE: examples/basic/src/mutation.rs ================================================ use super::*; use sea_orm::{DbConn, entity::*, error::*}; pub async fn all_about_mutation(db: &DbConn) -> Result<(), DbErr> { insert_and_update(db).await?; println!("===== =====\n"); save_active_model(db).await?; println!("===== =====\n"); save_custom_active_model(db).await?; Ok(()) } pub async fn insert_and_update(db: &DbConn) -> Result<(), DbErr> { let pear = fruit::ActiveModel { name: Set("pear".to_owned()), ..Default::default() }; let res = Fruit::insert(pear).exec(db).await?; println!("Inserted: last_insert_id = {}", res.last_insert_id); let pear: Option = Fruit::find_by_id(res.last_insert_id).one(db).await?; println!("Pear: {pear:?}"); let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); let pear: fruit::Model = pear.update(db).await?; println!("Updated: {pear:?}"); let result = pear.delete(db).await?; println!("Deleted: {result:?}"); Ok(()) } pub async fn save_active_model(db: &DbConn) -> Result<(), DbErr> { let banana = fruit::ActiveModel { name: Set("Banana".to_owned()), ..Default::default() }; let mut banana: fruit::ActiveModel = banana.save(db).await?; println!("Inserted: {banana:?}"); banana.name = Set("Banana Mongo".to_owned()); let banana: fruit::ActiveModel = banana.save(db).await?; println!("Updated: {banana:?}"); let result = banana.delete(db).await?; println!("Deleted: {result:?}"); Ok(()) } mod form { use super::fruit::*; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveIntoActiveModel)] pub struct InputModel { pub name: String, } } async fn save_custom_active_model(db: &DbConn) -> Result<(), DbErr> { let pineapple = form::InputModel { name: "Pineapple".to_owned(), } .into_active_model(); let pineapple = pineapple.save(db).await?; println!("Saved: {pineapple:?}"); let result = pineapple.delete(db).await?; println!("Deleted: {result:?}"); Ok(()) } ================================================ FILE: examples/basic/src/query.rs ================================================ use super::*; use sea_orm::{DbConn, FromQueryResult, entity::*, error::*, query::*}; pub async fn all_about_query(db: &DbConn) -> Result<(), DbErr> { find_all(db).await?; println!("===== =====\n"); find_one(db).await?; println!("===== =====\n"); find_one_to_one(db).await?; println!("===== =====\n"); find_one_to_many(db).await?; println!("===== =====\n"); count_fruits_by_cake(db).await?; println!("===== =====\n"); find_many_to_many(db).await?; if false { println!("===== =====\n"); all_about_select_json(db).await?; } println!("===== =====\n"); find_all_stream(db).await.unwrap(); println!("===== =====\n"); find_first_page(db).await.unwrap(); println!("===== =====\n"); find_num_pages(db).await.unwrap(); Ok(()) } async fn find_all(db: &DbConn) -> Result<(), DbErr> { print!("find all cakes: "); let cakes: Vec = Cake::find().all(db).await?; println!(); for cc in cakes.iter() { println!("{cc:?}\n"); } print!("find all fruits: "); let fruits = Fruit::find().all(db).await?; println!(); for ff in fruits.iter() { println!("{ff:?}\n"); } Ok(()) } async fn find_one_to_one(db: &DbConn) -> Result<(), DbErr> { print!("find fruits and cakes: "); let fruits_and_cakes: Vec<(fruit::Model, Option)> = Fruit::find().find_also_related(Cake).all(db).await?; println!("with loader: "); let fruits: Vec = Fruit::find().all(db).await?; let cakes: Vec> = fruits.load_one(Cake, db).await?; println!(); for (left, right) in fruits_and_cakes .into_iter() .zip(fruits.into_iter().zip(cakes.into_iter())) { println!("{left:?}"); assert_eq!(left, right); } Ok(()) } async fn find_one_to_many(db: &DbConn) -> Result<(), DbErr> { print!("find cakes with fruits: "); let cakes_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() .find_with_related(fruit::Entity) .all(db) .await?; println!("with loader: "); let cakes: Vec = Cake::find().all(db).await?; let fruits: Vec> = cakes.load_many(Fruit, db).await?; println!(); for (left, right) in cakes_with_fruits .into_iter() .zip(cakes.into_iter().zip(fruits.into_iter())) { println!("{left:?}\n"); assert_eq!(left, right); } Ok(()) } impl Cake { fn find_by_name(name: &str) -> Select { Self::find().filter(cake::Column::Name.contains(name)) } } async fn find_one(db: &DbConn) -> Result<(), DbErr> { print!("find one by primary key: "); let cheese: Option = Cake::find_by_id(1).one(db).await?; let cheese = cheese.unwrap(); println!(); println!("{cheese:?}"); println!(); print!("find one by name: "); let chocolate = Cake::find_by_name("chocolate").one(db).await?; println!(); println!("{chocolate:?}"); println!(); print!("find models belong to: "); let fruits = cheese.find_related(Fruit).all(db).await?; println!(); for ff in fruits.iter() { println!("{ff:?}\n"); } Ok(()) } async fn count_fruits_by_cake(db: &DbConn) -> Result<(), DbErr> { #[derive(Debug, FromQueryResult)] struct SelectResult { name: String, num_of_fruits: i32, } print!("count fruits by cake: "); let select = Cake::find() .left_join(Fruit) .select_only() .column(cake::Column::Name) .column_as(fruit::Column::Id.count(), "num_of_fruits") .group_by(cake::Column::Name); let results = select.into_model::().all(db).await?; println!(); for rr in results.iter() { println!("{rr:?}\n"); } Ok(()) } async fn find_many_to_many(db: &DbConn) -> Result<(), DbErr> { print!("find cakes and fillings: "); let cakes_with_fillings: Vec<(cake::Model, Vec)> = Cake::find().find_with_related(Filling).all(db).await?; println!("with loader: "); let cakes: Vec = Cake::find().all(db).await?; let fillings: Vec> = cakes.load_many_to_many(Filling, CakeFilling, db).await?; println!(); for (left, right) in cakes_with_fillings .into_iter() .zip(cakes.into_iter().zip(fillings.into_iter())) { println!("{left:?}\n"); assert_eq!(left, right); } print!("find fillings for cheese cake: "); let cheese = Cake::find_by_id(1).one(db).await?; if let Some(cheese) = cheese { let fillings: Vec = cheese.find_related(Filling).all(db).await?; println!(); for ff in fillings.iter() { println!("{ff:?}\n"); } } print!("find cakes for lemon: "); let lemon = Filling::find_by_id(2).one(db).await?; if let Some(lemon) = lemon { let cakes: Vec = lemon.find_related(Cake).all(db).await?; println!(); for cc in cakes.iter() { println!("{cc:?}\n"); } } Ok(()) } async fn all_about_select_json(db: &DbConn) -> Result<(), DbErr> { find_all_json(db).await?; println!("===== =====\n"); find_together_json(db).await?; println!("===== =====\n"); count_fruits_by_cake_json(db).await?; Ok(()) } async fn find_all_json(db: &DbConn) -> Result<(), DbErr> { print!("find all cakes: "); let cakes = Cake::find().into_json().all(db).await?; println!("\n{}\n", serde_json::to_string_pretty(&cakes).unwrap()); print!("find all fruits: "); let fruits = Fruit::find().into_json().all(db).await?; println!("\n{}\n", serde_json::to_string_pretty(&fruits).unwrap()); Ok(()) } async fn find_together_json(db: &DbConn) -> Result<(), DbErr> { print!("find cakes and fruits: "); let cakes_fruits = Cake::find() .find_also_related(Fruit) .into_json() .all(db) .await?; println!( "\n{}\n", serde_json::to_string_pretty(&cakes_fruits).unwrap() ); Ok(()) } async fn count_fruits_by_cake_json(db: &DbConn) -> Result<(), DbErr> { print!("count fruits by cake: "); let count = Cake::find() .left_join(Fruit) .select_only() .column(cake::Column::Name) .column_as(fruit::Column::Id.count(), "num_of_fruits") .group_by(cake::Column::Name) .into_json() .all(db) .await?; println!("\n{}\n", serde_json::to_string_pretty(&count).unwrap()); Ok(()) } async fn find_all_stream(db: &DbConn) -> Result<(), DbErr> { use futures_util::TryStreamExt; use std::time::Duration; use tokio::time::sleep; println!("find all cakes paginated: "); let mut cake_paginator = cake::Entity::find().paginate(db, 3); while let Some(cake_res) = cake_paginator.fetch_and_next().await? { for cake in cake_res { println!("{cake:?}"); } } println!(); println!("find all fruits paginated: "); let mut fruit_paginator = fruit::Entity::find().paginate(db, 3); while let Some(fruit_res) = fruit_paginator.fetch_and_next().await? { for fruit in fruit_res { println!("{fruit:?}"); } } println!(); println!("find all fruits with stream: "); let mut fruit_stream = fruit::Entity::find().paginate(db, 3).into_stream(); while let Some(fruits) = fruit_stream.try_next().await? { for fruit in fruits { println!("{fruit:?}"); } sleep(Duration::from_millis(250)).await; } println!(); println!("find all fruits in json with stream: "); let mut json_stream = fruit::Entity::find() .into_json() .paginate(db, 3) .into_stream(); while let Some(jsons) = json_stream.try_next().await? { for json in jsons { println!("{json:?}"); } sleep(Duration::from_millis(250)).await; } Ok(()) } async fn find_first_page(db: &DbConn) -> Result<(), DbErr> { println!("fruits first page: "); let page = fruit::Entity::find().paginate(db, 3).fetch_page(0).await?; for fruit in page { println!("{fruit:?}"); } Ok(()) } async fn find_num_pages(db: &DbConn) -> Result<(), DbErr> { println!("fruits number of page: "); let num_pages = fruit::Entity::find().paginate(db, 3).num_pages().await?; println!("{num_pages:?}"); Ok(()) } ================================================ FILE: examples/graphql_example/.gitignore ================================================ db db-shm db-wal ================================================ FILE: examples/graphql_example/Cargo.toml ================================================ [package] authors = ["Aaron Leopold "] edition = "2024" name = "sea-orm-graphql-example" publish = false rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] members = [".", "api", "entity", "migration"] [dependencies] graphql-example-api = { path = "api" } ================================================ FILE: examples/graphql_example/README.md ================================================ ![screenshot](Screenshot1.png) ![screenshot](Screenshot2.png) # Axum-GraphQL with SeaORM example app 1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database 1. Turn on the appropriate database feature for your chosen db in `api/Cargo.toml` (the `"sqlx-sqlite",` line) 1. Execute `cargo run` to start the server 1. Visit [localhost:3000/api/graphql](http://localhost:3000/api/graphql) in browser Run tests: ```bash cd api cargo test ``` Run migration: ```bash cargo run -p migration -- up ``` ================================================ FILE: examples/graphql_example/api/Cargo.toml ================================================ [package] authors = ["Aaron Leopold "] edition = "2024" name = "graphql-example-api" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] async-graphql = { version = "7.0.17" } async-graphql-axum = "7.0" axum = "0.8" dotenvy = "0.15.7" entity = { path = "../entity" } migration = { path = "../migration" } tokio = { version = "1.29", features = ["full"] } [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-native-tls", # "sqlx-postgres", # "sqlx-mysql", "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/graphql_example/api/src/db.rs ================================================ use sea_orm::DatabaseConnection; pub struct Database { pub connection: DatabaseConnection, } impl Database { pub async fn new() -> Self { let connection = sea_orm::Database::connect(std::env::var("DATABASE_URL").unwrap()) .await .expect("Could not connect to database"); Database { connection } } pub fn get_connection(&self) -> &DatabaseConnection { &self.connection } } ================================================ FILE: examples/graphql_example/api/src/graphql/mod.rs ================================================ pub mod mutation; pub mod query; pub mod schema; ================================================ FILE: examples/graphql_example/api/src/graphql/mutation/mod.rs ================================================ pub mod note; pub use note::NoteMutation; // Add your other ones here to create a unified Mutation object // e.x. Mutation(NoteMutation, OtherMutation, OtherOtherMutation) #[derive(async_graphql::MergedObject, Default)] pub struct Mutation(NoteMutation); ================================================ FILE: examples/graphql_example/api/src/graphql/mutation/note.rs ================================================ use crate::service::Mutation; use async_graphql::{Context, InputObject, Object, Result, SimpleObject}; use entity::note; use crate::db::Database; // I normally separate the input types into separate files/modules, but this is just // a quick example. #[derive(InputObject)] pub struct CreateNoteInput { pub title: String, pub text: String, } impl CreateNoteInput { fn into_model_with_arbitrary_id(self) -> note::Model { note::Model { id: 0, title: self.title, text: self.text, } } } #[derive(SimpleObject)] pub struct DeleteResult { pub success: bool, pub rows_affected: u64, } #[derive(Default)] pub struct NoteMutation; #[Object] impl NoteMutation { pub async fn create_note( &self, ctx: &Context<'_>, input: CreateNoteInput, ) -> Result { let db = ctx.data::().unwrap(); let conn = db.get_connection(); Ok(Mutation::create_note(conn, input.into_model_with_arbitrary_id()).await?) } pub async fn delete_note(&self, ctx: &Context<'_>, id: i32) -> Result { let db = ctx.data::().unwrap(); let conn = db.get_connection(); let res = Mutation::delete_note(conn, id) .await .expect("Cannot delete note"); if res.rows_affected <= 1 { Ok(DeleteResult { success: true, rows_affected: res.rows_affected, }) } else { unimplemented!() } } } ================================================ FILE: examples/graphql_example/api/src/graphql/query/mod.rs ================================================ pub mod note; pub use note::NoteQuery; // Add your other ones here to create a unified Query object // e.x. Query(NoteQuery, OtherQuery, OtherOtherQuery) #[derive(async_graphql::MergedObject, Default)] pub struct Query(NoteQuery); ================================================ FILE: examples/graphql_example/api/src/graphql/query/note.rs ================================================ use crate::service::Query; use async_graphql::{Context, Object, Result}; use entity::note; use crate::db::Database; #[derive(Default)] pub struct NoteQuery; #[Object] impl NoteQuery { async fn get_notes(&self, ctx: &Context<'_>) -> Result> { let db = ctx.data::().unwrap(); let conn = db.get_connection(); Ok(Query::get_all_notes(conn) .await .map_err(|e| e.to_string())?) } async fn get_note_by_id(&self, ctx: &Context<'_>, id: i32) -> Result> { let db = ctx.data::().unwrap(); let conn = db.get_connection(); Ok(Query::find_note_by_id(conn, id) .await .map_err(|e| e.to_string())?) } } ================================================ FILE: examples/graphql_example/api/src/graphql/schema.rs ================================================ use async_graphql; use async_graphql::{EmptySubscription, Schema}; use migration::{Migrator, MigratorTrait}; use crate::{ db::Database, graphql::{mutation::Mutation, query::Query}, }; pub type AppSchema = Schema; /// Builds the GraphQL Schema, attaching the Database to the context pub async fn build_schema() -> AppSchema { let db = Database::new().await; Migrator::up(db.get_connection(), None).await.unwrap(); Schema::build(Query::default(), Mutation::default(), EmptySubscription) .data(db) .finish() } ================================================ FILE: examples/graphql_example/api/src/lib.rs ================================================ mod db; mod graphql; pub mod service; use async_graphql::http::{GraphQLPlaygroundConfig, playground_source}; use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; use axum::{ Router, extract::State, response::{Html, IntoResponse}, routing::get, }; use graphql::schema::{AppSchema, build_schema}; #[cfg(debug_assertions)] use dotenvy::dotenv; async fn graphql_handler(schema: State, req: GraphQLRequest) -> GraphQLResponse { schema.execute(req.into_inner()).await.into() } async fn graphql_playground() -> impl IntoResponse { Html(playground_source(GraphQLPlaygroundConfig::new( "/api/graphql", ))) } #[tokio::main] pub async fn main() { #[cfg(debug_assertions)] dotenv().ok(); let schema = build_schema().await; let app = Router::new() .route( "/api/graphql", get(graphql_playground).post(graphql_handler), ) .with_state(schema); println!("Playground: http://localhost:3000/api/graphql"); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } ================================================ FILE: examples/graphql_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/graphql_example/api/src/service/mutation.rs ================================================ use entity::{note, note::Entity as Note}; use sea_orm::{DbConn, DbErr, DeleteResult, entity::*}; pub struct Mutation; impl Mutation { pub async fn create_note(db: &DbConn, form_data: note::Model) -> Result { let active_model = note::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() }; let res = Note::insert(active_model).exec(db).await?; Ok(note::Model { id: res.last_insert_id, ..form_data }) } pub async fn update_note_by_id( db: &DbConn, id: i32, form_data: note::Model, ) -> Result { let note: note::ActiveModel = Note::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find note.".to_owned())) .map(Into::into)?; note::ActiveModel { id: note.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_note(db: &DbConn, id: i32) -> Result { let note: note::ActiveModel = Note::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find note.".to_owned())) .map(Into::into)?; note.delete(db).await } pub async fn delete_all_notes(db: &DbConn) -> Result { Note::delete_many().exec(db).await } } ================================================ FILE: examples/graphql_example/api/src/service/query.rs ================================================ use entity::{note, note::Entity as Note}; use sea_orm::{DbConn, DbErr, EntityTrait, PaginatorTrait, QueryOrder}; pub struct Query; impl Query { pub async fn find_note_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Note::find_by_id(id).one(db).await } pub async fn get_all_notes(db: &DbConn) -> Result, DbErr> { Note::find().all(db).await } /// If ok, returns (note models, num pages). pub async fn find_notes_in_page( db: &DbConn, page: u64, notes_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Note::find() .order_by_asc(note::Column::Id) .paginate(db, notes_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated notes paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/graphql_example/api/tests/crud_tests.rs ================================================ use entity::note; use graphql_example_api::service::{Mutation, Query}; use sea_orm::{ConnectionTrait, Database, Schema}; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.execute(&Schema::new(db.get_database_backend()).create_table_from_entity(note::Entity)) .await .unwrap(); { let note = Mutation::create_note( db, note::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( note, note::Model { id: 1, title: "Title A".to_owned(), text: "Text A".to_owned(), } ); } { let note = Mutation::create_note( db, note::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( note, note::Model { id: 2, title: "Title B".to_owned(), text: "Text B".to_owned(), } ); } { let note = Query::find_note_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(note.id, 1); assert_eq!(note.title, "Title A"); } { let note = Mutation::update_note_by_id( db, 1, note::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( note, note::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_note(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let note = Query::find_note_by_id(db, 2).await.unwrap(); assert!(note.is_none()); } { let result = Mutation::delete_all_notes(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/graphql_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] serde = { version = "1", features = ["derive"] } [dependencies.async-graphql] version = "7.0" [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/graphql_example/entity/src/lib.rs ================================================ pub mod note; ================================================ FILE: examples/graphql_example/entity/src/note.rs ================================================ use async_graphql::*; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)] #[sea_orm(table_name = "note")] #[graphql(concrete(name = "Note", params()))] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, pub text: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} impl Entity { pub fn find_by_title(title: &str) -> Select { Self::find().filter(Column::Title.eq(title)) } } ================================================ FILE: examples/graphql_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-sqlite", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/graphql_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/graphql_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_note_table; mod m20220120_000002_seed_notes; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_note_table::Migration), Box::new(m20220120_000002_seed_notes::Migration), ] } } ================================================ FILE: examples/graphql_example/migration/src/m20220120_000001_create_note_table.rs ================================================ use entity::note; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); db.get_schema_builder() .register(note::Entity) .sync(db) .await } } ================================================ FILE: examples/graphql_example/migration/src/m20220120_000002_seed_notes.rs ================================================ use entity::note; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Note", "This is the first note."), ("Second Note", "This is another note."), ]; for (title, text) in seed_data { let model = note::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Notes table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Note", "Second Note"]; note::Entity::delete_many() .filter(note::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Notes seeded data removed."); Ok(()) } } ================================================ FILE: examples/graphql_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/graphql_example/src/main.rs ================================================ fn main() { graphql_example_api::main(); } ================================================ FILE: examples/jsonrpsee_example/Cargo.toml ================================================ [package] edition = "2024" name = "sea-orm-jsonrpsee-example" publish = false rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] members = [".", "api", "entity", "migration"] [dependencies] jsonrpsee-example-api = { path = "api" } ================================================ FILE: examples/jsonrpsee_example/README.md ================================================ # jsonrpsee with SeaORM example app 1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database 1. Turn on the appropriate database feature for your chosen db in `api/Cargo.toml` (the `"sqlx-sqlite",` line) 1. Execute `cargo run` to start the server 1. Send jsonrpc request to server #### Insert ```shell curl --location --request POST 'http://127.0.0.1:8000' \ --header 'Content-Type: application/json' \ --data-raw '{"jsonrpc": "2.0", "method": "Post.Insert", "params": [ { "id":0, "title":"aaaaaaa", "text":"aaaaaaa" } ], "id": 2}' ``` #### List ```shell curl --location --request POST 'http://127.0.0.1:8000' \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc": "2.0", "method": "Post.List", "params": [ 1, 100 ], "id": 2 }' ``` #### Delete ```shell curl --location --request POST 'http://127.0.0.1:8000' \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc": "2.0", "method": "Post.Delete", "params": [ 10 ], "id": 2 }' ``` #### Update ```shell curl --location --request POST 'http://127.0.0.1:8000' \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc": "2.0", "method": "Post.Update", "params": [ { "id": 1, "title": "1111", "text": "11111" } ], "id": 2 }' ``` Run tests: ```bash cd api cargo test ``` Run migration: ```bash cargo run -p migration -- up ``` Regenerate entity: ```bash sea-orm-cli generate entity --output-dir ./entity/src --lib --entity-format dense --with-serde both ``` ================================================ FILE: examples/jsonrpsee_example/api/Cargo.toml ================================================ [package] edition = "2024" name = "jsonrpsee-example-api" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] anyhow = "1.0.71" async-trait = "0.1.71" dotenvy = "0.15" entity = { path = "../entity" } jsonrpsee = { version = "0.18.2", features = ["full"] } jsonrpsee-core = "0.18.2" log = { version = "0.4", features = ["std"] } migration = { path = "../migration" } serde = { version = "1", features = ["derive"] } simplelog = "0.12" tokio = { version = "1.29.1", features = ["full"] } [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-native-tls", "sqlx-sqlite", # "sqlx-postgres", # "sqlx-mysql", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/jsonrpsee_example/api/src/lib.rs ================================================ pub mod service; use std::env; use entity::post; use jsonrpsee::core::{RpcResult, async_trait}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::ServerBuilder; use jsonrpsee::types::error::ErrorObjectOwned; use log::info; use migration::{Migrator, MigratorTrait}; use sea_orm::{Database, DatabaseConnection}; use service::{Mutation, Query}; use simplelog::*; use std::fmt::Display; use std::net::SocketAddr; use tokio::signal::ctrl_c; // use tokio::signal::unix::{signal, SignalKind}; const DEFAULT_POSTS_PER_PAGE: u64 = 5; #[rpc(server, client)] trait PostRpc { #[method(name = "Post.List")] async fn list( &self, page: Option, posts_per_page: Option, ) -> RpcResult>; #[method(name = "Post.Insert")] async fn insert(&self, p: post::Model) -> RpcResult; #[method(name = "Post.Update")] async fn update(&self, p: post::Model) -> RpcResult; #[method(name = "Post.Delete")] async fn delete(&self, id: i32) -> RpcResult; } struct PpcImpl { conn: DatabaseConnection, } #[async_trait] impl PostRpcServer for PpcImpl { async fn list( &self, page: Option, posts_per_page: Option, ) -> RpcResult> { let page = page.unwrap_or(1); let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); Query::find_posts_in_page(&self.conn, page, posts_per_page) .await .map(|(p, _)| p) .internal_call_error() } async fn insert(&self, p: post::Model) -> RpcResult { let new_post = Mutation::create_post(&self.conn, p) .await .internal_call_error()?; Ok(new_post.id.unwrap()) } async fn update(&self, p: post::Model) -> RpcResult { Mutation::update_post_by_id(&self.conn, p.id, p) .await .map(|_| true) .internal_call_error() } async fn delete(&self, id: i32) -> RpcResult { Mutation::delete_post(&self.conn, id) .await .map(|res| res.rows_affected == 1) .internal_call_error() } } trait IntoJsonRpcResult { fn internal_call_error(self) -> RpcResult; } impl IntoJsonRpcResult for Result where E: Display, { fn internal_call_error(self) -> RpcResult { // Err(ErrorObjectOwned::owned(1, "c", None::<()>)) // self.map_err(|e| jsonrpsee::core::Error::Call(CallError::Failed(anyhow!("{}", e)))) self.map_err(|e| ErrorObjectOwned::owned(1, format!("{}", e), None::<()>)) } } #[tokio::main] async fn start() -> std::io::Result<()> { let _ = TermLogger::init( LevelFilter::Trace, Config::default(), TerminalMode::Mixed, ColorChoice::Auto, ); // get env vars dotenvy::dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let host = env::var("HOST").expect("HOST is not set in .env file"); let port = env::var("PORT").expect("PORT is not set in .env file"); let server_url = format!("{host}:{port}"); // create post table if not exists let conn = Database::connect(&db_url).await.unwrap(); Migrator::up(&conn, None).await.unwrap(); let server = ServerBuilder::default() .build(server_url.parse::().unwrap()) .await .unwrap(); let rpc_impl = PpcImpl { conn }; let server_addr = server.local_addr().unwrap(); let handle = server.start(rpc_impl.into_rpc()).unwrap(); info!("starting listening {}", server_addr); // let mut sig_int = signal(SignalKind::interrupt()).unwrap(); // let mut sig_term = signal(SignalKind::terminate()).unwrap(); tokio::select! { // _ = sig_int.recv() => info!("receive SIGINT"), // _ = sig_term.recv() => info!("receive SIGTERM"), _ = ctrl_c() => info!("receive Ctrl C"), } handle.stop().unwrap(); info!("Shutdown program"); Ok(()) } pub fn main() { let result = start(); if let Some(err) = result.err() { println!("Error: {err}"); } } ================================================ FILE: examples/jsonrpsee_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/jsonrpsee_example/api/src/service/mutation.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/jsonrpsee_example/api/src/service/query.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/jsonrpsee_example/api/tests/crud_tests.rs ================================================ use entity::post; use jsonrpsee_example_api::service::{Mutation, Query}; use sea_orm::Database; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/jsonrpsee_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/jsonrpsee_example/entity/src/lib.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub mod prelude; pub mod post; ================================================ FILE: examples/jsonrpsee_example/entity/src/post.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/jsonrpsee_example/entity/src/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub use super::post::Entity as Post; ================================================ FILE: examples/jsonrpsee_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-sqlite", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/jsonrpsee_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/jsonrpsee_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; mod m20220120_000002_seed_posts; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } } ================================================ FILE: examples/jsonrpsee_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/jsonrpsee_example/migration/src/m20220120_000002_seed_posts.rs ================================================ use entity::post; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Posts table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Post", "Second Post"]; post::Entity::delete_many() .filter(post::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Posts seeded data removed."); Ok(()) } } ================================================ FILE: examples/jsonrpsee_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/jsonrpsee_example/src/main.rs ================================================ fn main() { jsonrpsee_example_api::main(); } ================================================ FILE: examples/loco_example/.cargo/config.toml ================================================ [alias] loco = "run --" playground = "run --example playground" ================================================ FILE: examples/loco_example/.dockerignore ================================================ target dockerfile .dockerignore .git .gitignore ================================================ FILE: examples/loco_example/.gitignore ================================================ **/config/local.yaml **/config/*.local.yaml # Generated by Cargo # will have compiled files and executables debug/ target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html !Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb ================================================ FILE: examples/loco_example/.rustfmt.toml ================================================ comment_width = 80 format_strings = true group_imports = "StdExternalCrate" imports_granularity = "Crate" max_width = 100 use_small_heuristics = "Default" wrap_comments = true ================================================ FILE: examples/loco_example/Cargo.lock ================================================ # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.16", "once_cell", "version_check", ] [[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aliasable" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "alloc-no-stdlib" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.60.2", ] [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert-json-diff" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ "serde", "serde_json", ] [[package]] name = "async-compression" version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ "compression-codecs", "compression-core", "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "async-stream" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", "pin-project-lite", ] [[package]] name = "async-stream-impl" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "async-trait" version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auto-future" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" dependencies = [ "axum-core", "axum-macros", "bytes", "form_urlencoded", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "itoa", "matchit", "memchr", "mime", "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", "sync_wrapper", "tokio", "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-core" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-extra" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ "axum", "axum-core", "bytes", "cookie", "futures-util", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", "serde_core", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-macros" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "axum-test" version = "17.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" dependencies = [ "anyhow", "assert-json-diff", "auto-future", "axum", "bytes", "bytesize", "cookie", "http", "http-body-util", "hyper", "hyper-util", "mime", "pretty_assertions", "reserve-port", "rust-multipart-rfc7578_2", "serde", "serde_json", "serde_urlencoded", "smallvec", "tokio", "tower 0.5.2", "url", ] [[package]] name = "backon" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" dependencies = [ "fastrand", "gloo-timers", "tokio", ] [[package]] name = "backtrace" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-link 0.2.1", ] [[package]] name = "backtrace_printer" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d28de81c708c843640982b66573df0f0168d87e42854b563971f326745aab7" dependencies = [ "btparse-stable", "colored 2.2.0", "regex", "thiserror 1.0.69", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bigdecimal" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", "num-bigint", "num-integer", "num-traits", "serde", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borsh" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases", ] [[package]] name = "borsh-derive" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "brotli" version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] name = "bstr" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "serde", ] [[package]] name = "btparse-stable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d75b8252ed252f881d1dc4482ae3c3854df6ee8183c1906bac50ff358f4f89f" [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-unit" version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" dependencies = [ "serde", "utf8-width", ] [[package]] name = "bytecheck" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", "simdutf8", ] [[package]] name = "bytecheck_derive" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytesize" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" [[package]] name = "cc" version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-link 0.2.1", ] [[package]] name = "chrono-tz" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", "phf", "phf_codegen", ] [[package]] name = "chumsky" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ "hashbrown 0.14.5", "stacker", ] [[package]] name = "clap" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "clap_lex" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "colored" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "futures-core", "memchr", "pin-project-lite", "tokio", "tokio-util", ] [[package]] name = "compression-codecs" version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" dependencies = [ "brotli", "compression-core", "flate2", "memchr", "zstd", "zstd-safe", ] [[package]] name = "compression-core" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cookie" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time", "version_check", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "cron" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" dependencies = [ "chrono", "nom 7.1.3", "once_cell", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "cruet" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "113a9e83d8f614be76de8df1f25bf9d0ea6e85ea573710a3d3f3abe1438ae49c" dependencies = [ "once_cell", "regex", ] [[package]] name = "cruet" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6132609543972496bc97b1e01f1ce6586768870aeb4cabeb3385f4e05b5caead" dependencies = [ "once_cell", "regex", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "cssparser" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" dependencies = [ "cssparser-macros", "dtoa-short", "itoa", "phf", "smallvec", ] [[package]] name = "cssparser-macros" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", "syn 2.0.106", ] [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.106", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", "syn 2.0.106", ] [[package]] name = "dashmap" version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "deranged" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", "serde_core", ] [[package]] name = "derive_more" version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "unicode-xid", ] [[package]] name = "deunicode" version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dtoa-short" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" dependencies = [ "dtoa", ] [[package]] name = "duct" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7478638a31d1f1f3d6c9f5e57c76b906a04ac4879d6fd0fb6245bc88f73fd0b" dependencies = [ "libc", "os_pipe", "shared_child", "shared_thread", ] [[package]] name = "duct_sh" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8139179d1d133ab7153920ba3812915b17c61e2514a6f98b1fd03f2c07668d1" dependencies = [ "duct", ] [[package]] name = "ego-tree" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c6ba7d4eec39eaa9ab24d44a0e73a7949a1095a8b3f3abb11eddf27dbb56a53" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] [[package]] name = "email-encoding" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" dependencies = [ "base64", "memchr", ] [[package]] name = "email_address" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "english-to-cron" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e26fb7377cbec9a94f60428e6e6afbe10c699a14639b4d3d4b67b25c0bbe0806" dependencies = [ "regex", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "etcetera" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", "windows-sys 0.48.0", ] [[package]] name = "event-listener" version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flate2" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", "spin", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] [[package]] name = "fsevent-sys" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ "libc", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-intrusive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", "parking_lot", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "fxhash" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ "byteorder", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getopts" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", "regex-syntax", ] [[package]] name = "globwalk" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ "bitflags 2.9.4", "ignore", "walkdir", ] [[package]] name = "gloo-timers" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash 0.7.8", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.12", "allocator-api2", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown 0.15.5", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "hostname" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", "windows-link 0.1.3", ] [[package]] name = "html5ever" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", "match_token", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "http-range-header" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humansize" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ "libm", ] [[package]] name = "hyper" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "pin-utils", "smallvec", "tokio", "want", ] [[package]] name = "hyper-util" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2 0.6.0", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "ignore" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", "regex-automata", "same-file", "walkdir", "winapi-util", ] [[package]] name = "include_dir" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "indenter" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown 0.16.0", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "inherent" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "inotify" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ "bitflags 2.9.4", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] [[package]] name = "insta" version = "1.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" dependencies = [ "console", "once_cell", "pest", "pest_derive", "regex", "serde", "similar", ] [[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags 2.9.4", "cfg-if", "libc", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "ipnetwork" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" dependencies = [ "serde", ] [[package]] name = "iri-string" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "jsonwebtoken" version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ "base64", "js-sys", "pem", "ring", "serde", "serde_json", "simple_asn1", ] [[package]] name = "kqueue" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", ] [[package]] name = "kqueue-sys" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "lettre" version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" dependencies = [ "async-trait", "base64", "chumsky", "email-encoding", "email_address", "fastrand", "futures-io", "futures-util", "hostname", "httpdate", "idna", "mime", "nom 8.0.0", "percent-encoding", "quoted_printable", "rustls", "socket2 0.6.0", "tokio", "tokio-rustls", "url", "webpki-roots 1.0.2", ] [[package]] name = "libc" version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", "redox_syscall", ] [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "loco-gen" version = "0.16.3" source = "git+https://github.com/SeaQL/loco?branch=master#0e2860b7885e6c00e013b3618896a78095809ede" dependencies = [ "chrono", "clap", "colored 3.0.0", "cruet 0.14.0", "duct", "heck 0.4.1", "include_dir", "regex", "rrgen", "serde", "serde_json", "tera", "thiserror 1.0.69", "tracing", ] [[package]] name = "loco-rs" version = "0.16.3" source = "git+https://github.com/SeaQL/loco?branch=master#0e2860b7885e6c00e013b3618896a78095809ede" dependencies = [ "argon2", "async-trait", "axum", "axum-extra", "axum-test", "backtrace_printer", "byte-unit", "bytes", "chrono", "clap", "colored 3.0.0", "cruet 0.13.3", "dashmap 6.1.0", "duct", "duct_sh", "english-to-cron", "futures-util", "heck 0.4.1", "include_dir", "ipnetwork", "jsonwebtoken", "lettre", "loco-gen", "moka", "notify", "opendal", "rand 0.9.2", "redis", "regex", "scraper", "sea-orm", "sea-orm-migration", "semver", "serde", "serde_json", "serde_variant", "serde_yaml", "sqlx", "tera", "thiserror 1.0.69", "tokio", "tokio-cron-scheduler", "tokio-util", "toml", "tower 0.4.13", "tower-http", "tracing", "tracing-appender", "tracing-subscriber", "tree-fs", "ulid", "uuid", "validator", ] [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "markup5ever" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", "phf", "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "match_token" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ "regex-automata", ] [[package]] name = "matchit" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "migration" version = "0.1.0" dependencies = [ "loco-rs", "sea-orm-migration", "tokio", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "moka" version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", "parking_lot", "portable-atomic", "rustc_version", "smallvec", "tagptr", "uuid", ] [[package]] name = "multer" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", "http", "httparse", "memchr", "mime", "spin", "version_check", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nom" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[package]] name = "notify" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ "bitflags 2.9.4", "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio", "notify-types", "walkdir", "windows-sys 0.60.2", ] [[package]] name = "notify-types" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand 0.8.5", "smallvec", "zeroize", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "object" version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "opendal" version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb9838d0575c6dbaf3fcec7255af8d5771996d4af900bbb6fa9a314dec00a1a" dependencies = [ "anyhow", "backon", "base64", "bytes", "chrono", "futures", "getrandom 0.2.16", "http", "http-body", "log", "md-5", "percent-encoding", "quick-xml", "reqwest", "serde", "serde_json", "tokio", "uuid", ] [[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "os_pipe" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "ouroboros" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" dependencies = [ "aliasable", "ouroboros_macro", "static_assertions", ] [[package]] name = "ouroboros_macro" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ "heck 0.4.1", "proc-macro2", "proc-macro2-diagnostics", "quote", "syn 2.0.106", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link 0.2.1", ] [[package]] name = "parse-zoneinfo" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", "subtle", ] [[package]] name = "pem" version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64", "serde", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "pest_meta" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" dependencies = [ "pest", "sha2", ] [[package]] name = "pgvector" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" dependencies = [ "serde", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "pluralizer" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b3eba432a00a1f6c16f39147847a870e94e2e9b992759b503e330efec778cbe" dependencies = [ "once_cell", "regex", ] [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-crate" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit 0.23.6", ] [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "proc-macro-error2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "version_check", "yansi", ] [[package]] name = "psm" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e66fcd288453b748497d8fb18bccc83a16b0518e3906d4b8df0a8d42d93dbb1c" dependencies = [ "cc", ] [[package]] name = "ptr_meta" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quick-xml" version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.4", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.3", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "redis" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bc1ea653e0b2e097db3ebb5b7f678be339620b8041f66b30a308c1d45d36a7f" dependencies = [ "bytes", "cfg-if", "combine", "futures-util", "itoa", "num-bigint", "percent-encoding", "pin-project-lite", "ryu", "sha1_smol", "socket2 0.5.10", "tokio", "tokio-util", "url", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] [[package]] name = "regex" version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "relative-path" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rend" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-util", "tower 0.5.2", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", ] [[package]] name = "reserve-port" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ "thiserror 2.0.17", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rkyv" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", "tinyvec", "uuid", ] [[package]] name = "rkyv_derive" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "rrgen" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee7a7ede035354391a37e42aa4935b3d8921f0ded896d2ce44bb1a3b6dd76bab" dependencies = [ "cruet 0.13.3", "fs-err", "glob", "heck 0.4.1", "regex", "serde", "serde_json", "serde_regex", "serde_yaml", "tera", "thiserror 1.0.69", ] [[package]] name = "rsa" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rstest" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ "futures", "futures-timer", "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", "syn 2.0.106", "unicode-ident", ] [[package]] name = "rust-multipart-rfc7578_2" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" dependencies = [ "bytes", "futures-core", "futures-util", "http", "mime", "rand 0.9.2", "thiserror 2.0.17", ] [[package]] name = "rust_decimal" version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand 0.8.5", "rkyv", "serde", "serde_json", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustls" version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0e749d29b2064585327af5038a5a8eb73aeebad4a3472e83531a436563f7208" dependencies = [ "ahash 0.8.12", "cssparser", "ego-tree", "getopts", "html5ever", "indexmap", "precomputed-hash", "selectors", "tendril", ] [[package]] name = "sea-bae" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ "heck 0.4.1", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "sea-orm" version = "2.0.0-rc.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "880cc0a64f1ee0320d7d6cd2b9290c27e89750669cfc5b6afdce1654755a2983" dependencies = [ "async-stream", "async-trait", "bigdecimal", "chrono", "derive_more 2.0.1", "futures-util", "itertools", "log", "ouroboros", "pgvector", "rust_decimal", "sea-orm-macros", "sea-query", "sea-query-sqlx", "sea-schema", "serde", "serde_json", "sqlx", "strum", "thiserror 2.0.17", "time", "tracing", "url", "uuid", ] [[package]] name = "sea-orm-cli" version = "2.0.0-rc.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce69601010a0507a7e465a63bb63064984f7cfd05d04af39df70834e5713aeb6" dependencies = [ "chrono", "clap", "dotenvy", "glob", "indoc", "regex", "sea-schema", "sqlx", "tokio", "tracing", "tracing-subscriber", "url", ] [[package]] name = "sea-orm-macros" version = "2.0.0-rc.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aae91cb86cf389da96119df71967ffb1b3caf1687303def77336f8dc9d33e78" dependencies = [ "heck 0.5.0", "pluralizer", "proc-macro2", "quote", "sea-bae", "syn 2.0.106", "unicode-ident", ] [[package]] name = "sea-orm-migration" version = "2.0.0-rc.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab8128cb63269debc8a88dbef5577076a5b53f8d0055d2d0939f1533154e2ef" dependencies = [ "async-trait", "clap", "dotenvy", "sea-orm", "sea-orm-cli", "sea-schema", "tracing", "tracing-subscriber", ] [[package]] name = "sea-query" version = "1.0.0-rc.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58df33eded14fba318338034b4bd197973fd7570a9f93ee5f88a27a7731f44f3" dependencies = [ "bigdecimal", "chrono", "inherent", "ordered-float", "rust_decimal", "sea-query-derive", "serde_json", "time", "uuid", ] [[package]] name = "sea-query-derive" version = "1.0.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217e9422de35f26c16c5f671fce3c075a65e10322068dbc66078428634af6195" dependencies = [ "darling", "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.106", "thiserror 2.0.17", ] [[package]] name = "sea-query-sqlx" version = "0.8.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68873fa1776b4c25a26e7679f8ee22332978c721168ec1b0b32b6583d5a9381d" dependencies = [ "bigdecimal", "chrono", "rust_decimal", "sea-query", "serde_json", "sqlx", "time", "uuid", ] [[package]] name = "sea-schema" version = "0.17.0-rc.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "845b7ed3e7a4f4458fe7218931b54e92be0dce01fc3c310d996c7b76d9a37ea5" dependencies = [ "async-trait", "sea-query", "sea-query-sqlx", "sea-schema-derive", "sqlx", ] [[package]] name = "sea-schema-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "seahash" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "selectors" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ "bitflags 2.9.4", "cssparser", "derive_more 0.99.20", "fxhash", "log", "new_debug_unreachable", "phf", "phf_codegen", "precomputed-hash", "servo_arc", "smallvec", ] [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "serde_path_to_error" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", "serde_core", ] [[package]] name = "serde_regex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", "serde", ] [[package]] name = "serde_spanned" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "serde_variant" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a0068df419f9d9b6488fdded3f1c818522cdea328e02ce9d9f147380265a432" dependencies = [ "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "serial_test" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" dependencies = [ "dashmap 5.5.3", "futures", "lazy_static", "log", "parking_lot", "serial_test_derive", ] [[package]] name = "serial_test_derive" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "servo_arc" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4" dependencies = [ "stable_deref_trait", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shared_child" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ "libc", "sigchld", "windows-sys 0.60.2", ] [[package]] name = "shared_thread" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "sigchld" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" dependencies = [ "libc", "os_pipe", "signal-hook", ] [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core 0.6.4", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", "thiserror 2.0.17", "time", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slug" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ "deunicode", "wasm-bindgen", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "socket2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "sqlx" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", ] [[package]] name = "sqlx-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bigdecimal", "bytes", "chrono", "crc", "crossbeam-queue", "either", "event-listener", "futures-core", "futures-intrusive", "futures-io", "futures-util", "hashbrown 0.15.5", "hashlink", "indexmap", "log", "memchr", "once_cell", "percent-encoding", "rust_decimal", "rustls", "serde", "serde_json", "sha2", "smallvec", "thiserror 2.0.17", "time", "tokio", "tokio-stream", "tracing", "url", "uuid", "webpki-roots 0.26.11", ] [[package]] name = "sqlx-macros" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", "syn 2.0.106", ] [[package]] name = "sqlx-macros-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", "heck 0.5.0", "hex", "once_cell", "proc-macro2", "quote", "serde", "serde_json", "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.106", "tokio", "url", ] [[package]] name = "sqlx-mysql" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", "bigdecimal", "bitflags 2.9.4", "byteorder", "bytes", "chrono", "crc", "digest", "dotenvy", "either", "futures-channel", "futures-core", "futures-io", "futures-util", "generic-array", "hex", "hkdf", "hmac", "itoa", "log", "md-5", "memchr", "once_cell", "percent-encoding", "rand 0.8.5", "rsa", "rust_decimal", "serde", "sha1", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.17", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-postgres" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", "bigdecimal", "bitflags 2.9.4", "byteorder", "chrono", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", "futures-util", "hex", "hkdf", "hmac", "home", "itoa", "log", "md-5", "memchr", "num-bigint", "once_cell", "rand 0.8.5", "rust_decimal", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.17", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-sqlite" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", "flume", "futures-channel", "futures-core", "futures-executor", "futures-intrusive", "futures-util", "libsqlite3-sys", "log", "percent-encoding", "serde", "serde_urlencoded", "sqlx-core", "thiserror 2.0.17", "time", "tracing", "url", "uuid", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", "libc", "psm", "windows-sys 0.59.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", ] [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ "unicode-bidi", "unicode-normalization", "unicode-properties", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "tera" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ "chrono", "chrono-tz", "globwalk", "humansize", "lazy_static", "percent-encoding", "pest", "pest_derive", "rand 0.8.5", "regex", "serde", "serde_json", "slug", "unic-segment", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] [[package]] name = "time" version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "todolist" version = "0.1.0" dependencies = [ "async-trait", "axum", "chrono", "eyre", "include_dir", "insta", "loco-rs", "migration", "rstest", "sea-orm", "serde", "serde_json", "serial_test", "tokio", "tracing", "tracing-subscriber", "uuid", "validator", ] [[package]] name = "tokio" version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", "slab", "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] [[package]] name = "tokio-cron-scheduler" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2594dd7c2abbbafbb1c78d167fd10860dc7bd75f814cb051a1e0d3e796b9702" dependencies = [ "chrono", "cron", "num-derive", "num-traits", "tokio", "tracing", "uuid", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-stream" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_datetime" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime 0.6.11", "toml_write", "winnow", ] [[package]] name = "toml_edit" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", "toml_datetime 0.7.2", "toml_parser", "winnow", ] [[package]] name = "toml_parser" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] [[package]] name = "toml_write" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-http" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", "bitflags 2.9.4", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "http-range-header", "httpdate", "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-appender" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "thiserror 1.0.69", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-serde" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex-automata", "serde", "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", "tracing-serde", ] [[package]] name = "tree-fs" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6115680fa5fdb99b4ff19c9c3217e75116d2bb0eae82458c4e1818be6a10c7" dependencies = [ "rand 0.9.2", "serde", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "ulid" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" dependencies = [ "rand 0.9.2", "web-time", ] [[package]] name = "unic-char-property" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" dependencies = [ "unic-char-range", ] [[package]] name = "unic-char-range" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" [[package]] name = "unic-common" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" [[package]] name = "unic-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" dependencies = [ "unic-ucd-segment", ] [[package]] name = "unic-ucd-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" dependencies = [ "unic-char-property", "unic-char-range", "unic-ucd-version", ] [[package]] name = "unic-ucd-version" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" dependencies = [ "unic-common", ] [[package]] name = "unicase" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", "rand 0.9.2", "serde", "wasm-bindgen", ] [[package]] name = "validator" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ "idna", "once_cell", "regex", "serde", "serde_derive", "serde_json", "url", "validator_derive", ] [[package]] name = "validator_derive" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" dependencies = [ "darling", "once_cell", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-streams" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "web-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "web-time" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ "webpki-roots 1.0.2", ] [[package]] name = "webpki-roots" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ "libredox", "wasite", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link 0.2.1", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.5", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "zstd" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", ] ================================================ FILE: examples/loco_example/Cargo.toml ================================================ [workspace] [package] edition = "2024" name = "todolist" rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] loco-rs = { version = "0.16" } migration = { path = "migration" } async-trait = "0.1.74" axum = "0.8" chrono = "0.4" eyre = "0.6" include_dir = "0.7" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1.33.0", default-features = false } tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] } uuid = { version = "1.6.0", features = ["v4"] } validator = { version = "0.20" } [dependencies.sea-orm] features = [ "sqlx-sqlite", "sqlx-postgres", "runtime-tokio-rustls", "macros", ] version = "~2.0.0-rc.37" # sea-orm version [[bin]] name = "todolist-cli" path = "src/bin/main.rs" required-features = [] [dev-dependencies] insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } loco-rs = { version = "0.16", features = ["testing"] } rstest = "0.18.2" serial_test = "2.0.0" [patch.crates-io] loco-rs = { git = "https://github.com/SeaQL/loco", branch = "master" } ================================================ FILE: examples/loco_example/README.md ================================================ ![screenshot](Screenshot.png) > Adapted from https://github.com/loco-rs/todo-list # Loco with SeaORM example todo list Build your own todo list website using Loco. Follow the step-by-step guide [here](<(https://loco.rs/blog/frontend-website/)>) to create it effortlessly. ## Build Client Navigate to the `frontend` directory and build the client: ```sh $ cd frontend && yarn install && yarn build vite v5.0.8 building for production... ✓ 120 modules transformed. dist/index.html 0.46 kB │ gzip: 0.30 kB dist/assets/index-AbTMZIjW.css 1.26 kB │ gzip: 0.65 kB dist/assets/index-MJFpQvzE.js 235.64 kB │ gzip: 75.58 kB ✓ built in 2.01s ``` ## Run Locally You need: * A local postgres instance Check out your development [configuration](config/development.yaml). > To configure a database , please run a local postgres database with loco:loco and a db named [app name]_development.: docker run -d -p 5432:5432 -e POSTGRES_USER=loco -e POSTGRES_DB=[app name]_development -e POSTGRES_PASSWORD="loco" postgres:15.3-alpine Execute the following command to run your todo list website locally, serving static assets from `frontend/dist`: ```sh $ cargo loco start Finished dev [unoptimized + debuginfo] target(s) in 0.53s Running `target/debug/todolist-cli start` 2024-02-01T08:49:41.070430Z INFO loco_rs::db: auto migrating 2024-02-01T08:49:41.073698Z INFO sea_orm_migration::migrator: Applying all pending migrations 2024-02-01T08:49:41.078191Z INFO sea_orm_migration::migrator: No pending migrations 2024-02-01T08:49:41.100557Z INFO loco_rs::controller::app_routes: [GET] /api/_ping 2024-02-01T08:49:41.100617Z INFO loco_rs::controller::app_routes: [GET] /api/_health 2024-02-01T08:49:41.100667Z INFO loco_rs::controller::app_routes: [GET] /api/notes 2024-02-01T08:49:41.100702Z INFO loco_rs::controller::app_routes: [POST] /api/notes 2024-02-01T08:49:41.100738Z INFO loco_rs::controller::app_routes: [GET] /api/notes/{id} 2024-02-01T08:49:41.100791Z INFO loco_rs::controller::app_routes: [DELETE] /api/notes/{id} 2024-02-01T08:49:41.100817Z INFO loco_rs::controller::app_routes: [POST] /api/notes/{id} 2024-02-01T08:49:41.100934Z INFO loco_rs::controller::app_routes: [Middleware] Adding limit payload data="5mb" 2024-02-01T08:49:41.101017Z INFO loco_rs::controller::app_routes: [Middleware] Adding log trace id 2024-02-01T08:49:41.101057Z INFO loco_rs::controller::app_routes: [Middleware] Adding timeout layer 2024-02-01T08:49:41.101192Z INFO loco_rs::controller::app_routes: [Middleware] Adding cors 2024-02-01T08:49:41.101241Z INFO loco_rs::controller::app_routes: [Middleware] Adding static ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs environment: development database: automigrate logger: debug compilation: debug modes: server listening on port 3000 ``` ## Development To develop the UI, run the following commands: ```sh $ cd frontend && yarn install && yarn dev ``` To run the server: ```sh $ cargo loco start ``` ================================================ FILE: examples/loco_example/config/development.yaml ================================================ # Loco configuration file documentation # Application logging configuration logger: # Enable or disable logging. enable: true # Log level, options: trace, debug, info, warn or error. level: debug # Define the logging format. options: compact, pretty or Json format: compact # By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries # Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters. # override_filter: trace # Web server configuration server: # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} port: 3000 # The UI hostname or IP address that mailers will point to. host: http://localhost # Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block middlewares: # Allows to limit the payload size request. payload that bigger than this file will blocked the request. limit_payload: # Enable/Disable the middleware. enable: true # the limit size. can be b,kb,kib,mb,mib,gb,gib body_limit: 5mb # Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details. logger: # Enable/Disable the middleware. enable: true # when your code is panicked, the request still returns 500 status code. catch_panic: # Enable/Disable the middleware. enable: true # Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned. timeout_request: # Enable/Disable the middleware. enable: true # Duration time in milliseconds. timeout: 5000 cors: enable: true static: enable: true must_exist: true folder: uri: "/" path: "frontend/dist" fallback: "frontend/dist/index.html" # Database Configuration database: # Database connection URI uri: postgres://loco:loco@localhost:5432/loco_app # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. connect_timeout: 500 # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. min_connections: 1 # Maximum number of connections for a pool. max_connections: 1 # Run migration up when application loaded auto_migrate: true # Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_truncate: false # Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_recreate: false ================================================ FILE: examples/loco_example/dockerfile ================================================ FROM rust:1.74-slim as builder WORKDIR /usr/src/ COPY . . RUN cargo build --release FROM debian:bookworm-slim WORKDIR /usr/app COPY --from=builder /usr/src/frontend/dist /usr/app/frontend/dist COPY --from=builder /usr/src/frontend/dist/index.html /usr/app/frontend/dist/index.html COPY --from=builder /usr/src/config /usr/app/config COPY --from=builder /usr/src/target/release/todolist-cli /usr/app/todolist-cli ENTRYPOINT ["/usr/app/todolist-cli"] ================================================ FILE: examples/loco_example/frontend/.eslintrc.cjs ================================================ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, settings: { react: { version: '18.2' } }, plugins: ['react-refresh'], rules: { 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], }, } ================================================ FILE: examples/loco_example/frontend/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local .yarn .yarnrc.yml .pnp.* # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: examples/loco_example/frontend/index.html ================================================ Vite + React
================================================ FILE: examples/loco_example/frontend/package.json ================================================ { "name": "frontend", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "dependencies": { "axios": "^1.6.2", "react": "18.2.0", "react-dom": "18.2.0", "react-query": "3.39.3", "react-router-dom": "6.15.0" }, "devDependencies": { "@types/react": "18.2.43", "@types/react-dom": "18.2.17", "@vitejs/plugin-react": "4.2.1", "eslint": "8.55.0", "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.5", "vite": "5.0.8" } } ================================================ FILE: examples/loco_example/frontend/src/App.css ================================================ #root { max-width: 1280px; margin: 0 auto; text-align: center; } .logo { height: 10em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #ff1111aa); } .todo-list { text-align: left; } .todo-list .todo{ margin-top: 10px; } .todo-add { margin-top: 20px; text-align: left; } .todo-add input{ width: 70%; height: 30px; } .todo-add button{ float: right; } ================================================ FILE: examples/loco_example/frontend/src/App.jsx ================================================ import { useQuery, useMutation, useQueryClient, } from 'react-query' import axios from 'axios'; import './App.css' import { Routes, Route, Outlet, Link } from "react-router-dom"; import { useState } from "react"; export default function App() { return (

Loco Todo List

}> } /> } />
) } function Layout() { return ( ); } function TodoList() { const queryClient = useQueryClient(); const fetchTodos = async () => { const { data } = await axios.get(`api/notes`) return data; } const { isLoading, isError, data = [] } = useQuery(["todos"], fetchTodos); // a hook provided by react-query, it takes a key(name) and function that returns a promise const remove = async (id) => { try { const response = await axios.delete(`api/notes/${id}`); return response.data; } catch (error) { console.error('Error posting todo:', error); throw error; } }; const mutation = useMutation(remove, { onSuccess: () => { queryClient.invalidateQueries(["todos"]); }, }); console.log(data) if (isLoading) return (

isLoading...

); if (isError) return (

Could not get todo list from the server

); return (
{data.map((todo) => (
{todo.title}
))}
); } function AddTodo() { const [todo, setTodo] = useState(""); const queryClient = useQueryClient(); const add = async (newTodo) => { try { const response = await axios.post(`api/notes`, { title: newTodo, content: newTodo, }); return response.data; } catch (error) { console.error('Error posting todo:', error); throw error; } }; const mutation = useMutation(add, { onSuccess: () => { setTodo("") queryClient.invalidateQueries(["todos"]); }, }); return (
{ setTodo(event.target.value); }} type="text" />
); } function NoMatch() { return (

Sorry, this page not found

Go to the todo list page

); } ================================================ FILE: examples/loco_example/frontend/src/index.css ================================================ :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } a { font-weight: 500; color: #646cff; text-decoration: inherit; } a:hover { color: #535bf2; } body { margin: 0; display: flex; place-items: center; min-width: 320px; min-height: 100vh; } h1 { font-size: 3.2em; line-height: 1.1; } button { border-radius: 8px; border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; } button:hover { border-color: #646cff; } button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } a:hover { color: #747bff; } button { background-color: #f9f9f9; } } ================================================ FILE: examples/loco_example/frontend/src/main.jsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import { BrowserRouter } from "react-router-dom"; import App from './App.jsx' import './index.css' import { QueryClient, QueryClientProvider, } from 'react-query' const queryClient = new QueryClient(); ReactDOM.createRoot(document.getElementById('root')).render( ) ================================================ FILE: examples/loco_example/frontend/vite.config.js ================================================ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], server: { proxy: { "/api": { target: "http://127.0.0.1:3000", changeOrigin: true, secure: false, }, }, }, }) ================================================ FILE: examples/loco_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] loco-rs = { version = "0.16" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. # e.g. "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature ] version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/loco_example/migration/README.md ================================================ # Running Migrator CLI - Generate a new migration file ```sh cargo run -- migrate generate MIGRATION_NAME ``` - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/loco_example/migration/src/lib.rs ================================================ #![allow(elided_lifetimes_in_paths)] #![allow(clippy::wildcard_imports)] pub use sea_orm_migration::prelude::*; mod m20231103_114510_notes; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![Box::new(m20231103_114510_notes::Migration)] } } ================================================ FILE: examples/loco_example/migration/src/m20231103_114510_notes.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto("notes") .col(pk_auto("id")) .col(string_null("title")) .col(string_null("content")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("notes").to_owned()) .await } } ================================================ FILE: examples/loco_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/loco_example/src/app.rs ================================================ use std::path::Path; use async_trait::async_trait; use loco_rs::{ Result, app::{AppContext, Hooks}, bgworker::Queue, boot::{BootResult, StartMode, create_app}, config::Config, controller::AppRoutes, db::{self, truncate_table}, environment::Environment, task::Tasks, }; use migration::Migrator; use crate::{controllers, models::_entities::notes}; pub struct App; #[async_trait] impl Hooks for App { fn app_name() -> &'static str { env!("CARGO_CRATE_NAME") } async fn boot( mode: StartMode, environment: &Environment, config: Config, ) -> Result { create_app::(mode, environment, config).await } fn routes(_ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() .prefix("/api") .add_route(controllers::notes::routes()) } async fn connect_workers(_ctx: &AppContext, _queue: &Queue) -> Result<()> { Ok(()) } fn register_tasks(_tasks: &mut Tasks) {} async fn truncate(ctx: &AppContext) -> Result<()> { let db = &ctx.db; truncate_table(db, notes::Entity).await?; Ok(()) } async fn seed(ctx: &AppContext, base: &Path) -> Result<()> { let db = &ctx.db; db::seed::(db, &base.join("notes.yaml").display().to_string()).await?; Ok(()) } } ================================================ FILE: examples/loco_example/src/bin/main.rs ================================================ use loco_rs::cli; use migration::Migrator; use todolist::app::App; #[tokio::main] async fn main() -> eyre::Result<()> { cli::main::().await?; Ok(()) } ================================================ FILE: examples/loco_example/src/controllers/mod.rs ================================================ pub mod notes; ================================================ FILE: examples/loco_example/src/controllers/notes.rs ================================================ #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::models::_entities::notes::{ActiveModel, Entity, Model}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Params { pub title: Option, pub content: Option, } impl Params { fn update(&self, item: &mut ActiveModel) { item.title = Set(self.title.clone()); item.content = Set(self.content.clone()); } } async fn load_item(ctx: &AppContext, id: i32) -> Result { let item = Entity::find_by_id(id).one(&ctx.db).await?; item.ok_or_else(|| Error::NotFound) } pub async fn list(State(ctx): State) -> Result { format::json(Entity::find().all(&ctx.db).await?) } pub async fn add(State(ctx): State, Json(params): Json) -> Result { let mut item = ActiveModel { ..Default::default() }; params.update(&mut item); let item = item.insert(&ctx.db).await?; format::json(item) } pub async fn update( Path(id): Path, State(ctx): State, Json(params): Json, ) -> Result { let item = load_item(&ctx, id).await?; let mut item = item.into_active_model(); params.update(&mut item); let item = item.update(&ctx.db).await?; format::json(item) } pub async fn remove(Path(id): Path, State(ctx): State) -> Result { load_item(&ctx, id).await?.delete(&ctx.db).await?; format::empty() } pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { format::json(load_item(&ctx, id).await?) } pub fn routes() -> Routes { Routes::new() .prefix("notes") .add("/", get(list)) .add("/", post(add)) .add("/{id}", get(get_one)) .add("/{id}", delete(remove)) .add("/{id}", post(update)) } ================================================ FILE: examples/loco_example/src/fixtures/notes.yaml ================================================ --- - id: 1 title: Loco note 1 content: Loco note 1 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" - id: 2 title: Loco note 1 content: Loco note 1 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" ================================================ FILE: examples/loco_example/src/lib.rs ================================================ pub mod app; pub mod controllers; pub mod models; ================================================ FILE: examples/loco_example/src/models/_entities/mod.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4 pub mod prelude; pub mod notes; ================================================ FILE: examples/loco_example/src/models/_entities/notes.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.10 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "notes")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub title: Option, pub content: Option, } ================================================ FILE: examples/loco_example/src/models/_entities/prelude.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4 pub use super::notes::Entity as Notes; ================================================ FILE: examples/loco_example/src/models/mod.rs ================================================ pub mod _entities; pub mod notes; ================================================ FILE: examples/loco_example/src/models/notes.rs ================================================ use sea_orm::entity::prelude::*; use super::_entities::notes::ActiveModel; impl ActiveModelBehavior for ActiveModel { // extend activemodel below (keep comment for generators) } ================================================ FILE: examples/loco_example/tests/mod.rs ================================================ mod models; mod requests; mod tasks; ================================================ FILE: examples/loco_example/tests/models/mod.rs ================================================ ================================================ FILE: examples/loco_example/tests/requests/mod.rs ================================================ mod notes; ================================================ FILE: examples/loco_example/tests/requests/notes.rs ================================================ use insta::{assert_debug_snapshot, with_settings}; use loco_rs::testing; use migration::Migrator; use sea_orm::entity::prelude::*; use serial_test::serial; use todolist::{app::App, models::_entities::notes::Entity}; // TODO: see how to dedup / extract this to app-local test utils // not to framework, because that would require a runtime dep on insta macro_rules! configure_insta { ($($expr:expr),*) => { let mut settings = insta::Settings::clone_current(); settings.set_prepend_module_to_snapshot(false); settings.set_snapshot_suffix("notes_request"); let _guard = settings.bind_to_scope(); }; } // Disabled integration test for GitHub CI /* #[tokio::test] #[serial] async fn can_get_notes() { configure_insta!(); testing::request::(|request, ctx| async move { testing::seed::(&ctx.db).await.unwrap(); let notes = request.get("/api/notes").await; with_settings!({ filters => { let mut combined_filters = testing::CLEANUP_DATE.to_vec(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } }, { assert_debug_snapshot!( (notes.status_code(), notes.text()) ); }); }) .await; } #[tokio::test] #[serial] async fn can_add_note() { configure_insta!(); testing::request::(|request, _ctx| async move { let payload = serde_json::json!({ "title": "loco", "content": "loco note test", }); let add_note_request = request.post("/api/notes").json(&payload).await; with_settings!({ filters => { let mut combined_filters = testing::CLEANUP_DATE.to_vec(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } }, { assert_debug_snapshot!( (add_note_request.status_code(), add_note_request.text()) ); }); }) .await; } #[tokio::test] #[serial] async fn can_get_note() { configure_insta!(); testing::request::(|request, ctx| async move { testing::seed::(&ctx.db).await.unwrap(); let add_note_request = request.get("/api/notes/1").await; with_settings!({ filters => { let mut combined_filters = testing::CLEANUP_DATE.to_vec(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } }, { assert_debug_snapshot!( (add_note_request.status_code(), add_note_request.text()) ); }); }) .await; } #[tokio::test] #[serial] async fn can_delete_note() { configure_insta!(); testing::request::(|request, ctx| async move { testing::seed::(&ctx.db).await.unwrap(); let count_before_delete = Entity::find().all(&ctx.db).await.unwrap().len(); let delete_note_request = request.delete("/api/notes/1").await; with_settings!({ filters => { let mut combined_filters = testing::CLEANUP_DATE.to_vec(); combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]); combined_filters } }, { assert_debug_snapshot!( (delete_note_request.status_code(), delete_note_request.text()) ); }); let count_after_delete = Entity::find().all(&ctx.db).await.unwrap().len(); assert_eq!(count_after_delete, count_before_delete - 1); }) .await; } */ ================================================ FILE: examples/loco_example/tests/requests/snapshots/can_add_note@notes_request.snap ================================================ --- source: tests/requests/notes.rs expression: "(add_note_request.status_code(), add_note_request.text())" --- ( 200, "{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"loco\",\"content\":\"loco note test\"}", ) ================================================ FILE: examples/loco_example/tests/requests/snapshots/can_delete_note@notes_request.snap ================================================ --- source: tests/requests/notes.rs expression: "(delete_note_request.status_code(), delete_note_request.text())" --- ( 200, "", ) ================================================ FILE: examples/loco_example/tests/requests/snapshots/can_get_note@notes_request.snap ================================================ --- source: tests/requests/notes.rs expression: "(add_note_request.status_code(), add_note_request.text())" --- ( 200, "{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"}", ) ================================================ FILE: examples/loco_example/tests/requests/snapshots/can_get_notes@notes_request.snap ================================================ --- source: tests/requests/notes.rs expression: "(notes.status_code(), notes.text())" --- ( 200, "[{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"},{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"}]", ) ================================================ FILE: examples/loco_example/tests/tasks/mod.rs ================================================ pub mod seed; ================================================ FILE: examples/loco_example/tests/tasks/seed.rs ================================================ //! This task implements data seeding functionality for initializing new //! development/demo environments. //! //! # Example //! //! Run the task with the following command: //! ```sh //! cargo run task //! ``` //! //! To override existing data and reset the data structure, use the following //! command with the `refresh:true` argument: //! ```sh //! cargo run task seed_data refresh:true //! ``` use loco_rs::{db, prelude::*}; use migration::Migrator; use todolist::app::App; #[allow(clippy::module_name_repetitions)] pub struct SeedData; #[async_trait] impl Task for SeedData { fn task(&self) -> TaskInfo { TaskInfo { name: "seed_data".to_string(), detail: "Task for seeding data".to_string(), } } async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> { let refresh = vars .cli_arg("refresh") .is_ok_and(|refresh| refresh == "true"); if refresh { db::reset::(&app_context.db).await?; } let path = std::path::Path::new("src/fixtures"); db::run_app_seed::(app_context, path).await?; Ok(()) } } ================================================ FILE: examples/loco_seaography/.cargo/config.toml ================================================ [alias] loco = "run --" playground = "run --example playground" ================================================ FILE: examples/loco_seaography/.devcontainer/Dockerfile ================================================ FROM mcr.microsoft.com/devcontainers/rust:1 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends postgresql-client \ && cargo install sea-orm-cli cargo-insta \ && chown -R vscode /usr/local/cargo COPY .env /.env ================================================ FILE: examples/loco_seaography/.devcontainer/devcontainer.json ================================================ { "name": "Loco", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "forwardPorts": [ 3000 ] } ================================================ FILE: examples/loco_seaography/.devcontainer/docker-compose.yml ================================================ version: "3" services: app: build: context: . dockerfile: Dockerfile command: sleep infinity networks: - db - redis - mailer volumes: - ../..:/workspaces:cached env_file: - .env db: image: postgres:15.3-alpine restart: unless-stopped ports: - 5432:5432 networks: - db volumes: - postgres-data:/var/lib/postgresql/data env_file: - .env redis: image: redis:latest restart: unless-stopped ports: - 6379:6379 networks: - redis mailer: image: mailtutan/mailtutan:latest restart: unless-stopped ports: - 1080:1080 - 1025:1025 networks: - mailer volumes: postgres-data: networks: db: redis: mailer: ================================================ FILE: examples/loco_seaography/.github/workflows/ci.yaml ================================================ name: CI on: push: branches: - master - main pull_request: env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: rustfmt: name: Check Style runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true components: rustfmt - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: Run Clippy runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo clippy uses: actions-rs/cargo@v1 with: command: clippy args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms test: name: Run Tests runs-on: ubuntu-latest permissions: contents: read services: redis: image: redis options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - "6379:6379" postgres: image: postgres env: POSTGRES_DB: postgres_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - "5432:5432" # Set health checks to wait until postgres has started options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test args: --all-features --all env: REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}} DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test ================================================ FILE: examples/loco_seaography/.gitignore ================================================ **/config/local.yaml **/config/*.local.yaml **/config/production.yaml # Generated by Cargo # will have compiled files and executables debug/ target/ # include cargo lock !Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb # Temporary upload directory uploads # Local SQLite databases *.db ================================================ FILE: examples/loco_seaography/.rustfmt.toml ================================================ comment_width = 80 format_strings = true group_imports = "StdExternalCrate" imports_granularity = "Crate" max_width = 100 use_small_heuristics = "Default" wrap_comments = true ================================================ FILE: examples/loco_seaography/Cargo.lock ================================================ # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "Inflector" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" dependencies = [ "lazy_static", "regex", ] [[package]] name = "addr2line" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.16", "once_cell", "version_check", ] [[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aliasable" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "alloc-no-stdlib" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.60.2", ] [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert-json-diff" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ "serde", "serde_json", ] [[package]] name = "async-compression" version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ "compression-codecs", "compression-core", "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "async-graphql" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "036618f842229ba0b89652ffe425f96c7c16a49f7e3cb23b56fca7f61fd74980" dependencies = [ "async-graphql-derive", "async-graphql-parser", "async-graphql-value", "async-stream", "async-trait", "base64", "bytes", "chrono", "fnv", "futures-channel", "futures-timer", "futures-util", "http", "indexmap", "lru", "mime", "multer", "num-traits", "pin-project-lite", "regex", "rust_decimal", "serde", "serde_json", "serde_urlencoded", "static_assertions_next", "thiserror 1.0.69", ] [[package]] name = "async-graphql-axum" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8725874ecfbf399e071150b8619c4071d7b2b7a2f117e173dddef53c6bdb6bb1" dependencies = [ "async-graphql", "axum", "bytes", "futures-util", "serde_json", "tokio", "tokio-stream", "tokio-util", "tower-service", ] [[package]] name = "async-graphql-derive" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd45deb3dbe5da5cdb8d6a670a7736d735ba65b455328440f236dfb113727a3d" dependencies = [ "Inflector", "async-graphql-parser", "darling 0.20.11", "proc-macro-crate", "proc-macro2", "quote", "strum 0.26.3", "syn 2.0.106", "thiserror 1.0.69", ] [[package]] name = "async-graphql-parser" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b7607e59424a35dadbc085b0d513aa54ec28160ee640cf79ec3b634eba66d3" dependencies = [ "async-graphql-value", "pest", "serde", "serde_json", ] [[package]] name = "async-graphql-value" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ecdaff7c9cffa3614a9f9999bf9ee4c3078fe3ce4d6a6e161736b56febf2de" dependencies = [ "bytes", "indexmap", "serde", "serde_json", ] [[package]] name = "async-stream" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", "pin-project-lite", ] [[package]] name = "async-stream-impl" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "async-trait" version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auto-future" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" dependencies = [ "axum-core", "axum-macros", "base64", "bytes", "form_urlencoded", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "itoa", "matchit", "memchr", "mime", "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", "sha1", "sync_wrapper", "tokio", "tokio-tungstenite", "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-core" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-extra" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ "axum", "axum-core", "bytes", "cookie", "futures-util", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", "serde_core", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-macros" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "axum-test" version = "17.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" dependencies = [ "anyhow", "assert-json-diff", "auto-future", "axum", "bytes", "bytesize", "cookie", "http", "http-body-util", "hyper", "hyper-util", "mime", "pretty_assertions", "reserve-port", "rust-multipart-rfc7578_2", "serde", "serde_json", "serde_urlencoded", "smallvec", "tokio", "tower 0.5.2", "url", ] [[package]] name = "backon" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" dependencies = [ "fastrand", "gloo-timers", "tokio", ] [[package]] name = "backtrace" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-link 0.2.1", ] [[package]] name = "backtrace_printer" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d28de81c708c843640982b66573df0f0168d87e42854b563971f326745aab7" dependencies = [ "btparse-stable", "colored 2.2.0", "regex", "thiserror 1.0.69", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bigdecimal" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", "num-bigint", "num-integer", "num-traits", "serde", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borsh" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases", ] [[package]] name = "borsh-derive" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "brotli" version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] name = "bstr" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "serde", ] [[package]] name = "btparse-stable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d75b8252ed252f881d1dc4482ae3c3854df6ee8183c1906bac50ff358f4f89f" [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-unit" version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" dependencies = [ "serde", "utf8-width", ] [[package]] name = "bytecheck" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", "simdutf8", ] [[package]] name = "bytecheck_derive" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" dependencies = [ "serde", ] [[package]] name = "bytesize" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" [[package]] name = "cc" version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-link 0.2.1", ] [[package]] name = "chrono-tz" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", "phf", "phf_codegen", ] [[package]] name = "chumsky" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ "hashbrown 0.14.5", "stacker", ] [[package]] name = "clap" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "clap_lex" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "colored" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "futures-core", "memchr", "pin-project-lite", "tokio", "tokio-util", ] [[package]] name = "compression-codecs" version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" dependencies = [ "brotli", "compression-core", "flate2", "memchr", "zstd", "zstd-safe", ] [[package]] name = "compression-core" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cookie" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time", "version_check", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "cron" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" dependencies = [ "chrono", "nom 7.1.3", "once_cell", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "cruet" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "113a9e83d8f614be76de8df1f25bf9d0ea6e85ea573710a3d3f3abe1438ae49c" dependencies = [ "once_cell", "regex", ] [[package]] name = "cruet" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6132609543972496bc97b1e01f1ce6586768870aeb4cabeb3385f4e05b5caead" dependencies = [ "once_cell", "regex", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "cssparser" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" dependencies = [ "cssparser-macros", "dtoa-short", "itoa", "phf", "smallvec", ] [[package]] name = "cssparser-macros" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", "syn 2.0.106", ] [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core 0.20.11", "darling_macro 0.20.11", ] [[package]] name = "darling" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core 0.21.3", "darling_macro 0.21.3", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.106", ] [[package]] name = "darling_core" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.106", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", "syn 2.0.106", ] [[package]] name = "darling_macro" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", "syn 2.0.106", ] [[package]] name = "dashmap" version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "data-encoding" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "deranged" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", "serde_core", ] [[package]] name = "derive_more" version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "unicode-xid", ] [[package]] name = "deunicode" version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dtoa-short" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" dependencies = [ "dtoa", ] [[package]] name = "duct" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7478638a31d1f1f3d6c9f5e57c76b906a04ac4879d6fd0fb6245bc88f73fd0b" dependencies = [ "libc", "os_pipe", "shared_child", "shared_thread", ] [[package]] name = "duct_sh" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8139179d1d133ab7153920ba3812915b17c61e2514a6f98b1fd03f2c07668d1" dependencies = [ "duct", ] [[package]] name = "ego-tree" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c6ba7d4eec39eaa9ab24d44a0e73a7949a1095a8b3f3abb11eddf27dbb56a53" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] [[package]] name = "email-encoding" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" dependencies = [ "base64", "memchr", ] [[package]] name = "email_address" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "english-to-cron" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e26fb7377cbec9a94f60428e6e6afbe10c699a14639b4d3d4b67b25c0bbe0806" dependencies = [ "regex", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "etcetera" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", "windows-sys 0.48.0", ] [[package]] name = "event-listener" version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flate2" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", "spin", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] [[package]] name = "fsevent-sys" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ "libc", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-intrusive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", "parking_lot", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "fxhash" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ "byteorder", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getopts" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", "regex-syntax", ] [[package]] name = "globwalk" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ "bitflags 2.9.4", "ignore", "walkdir", ] [[package]] name = "gloo-timers" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash 0.7.8", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.12", "allocator-api2", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown 0.15.5", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "hostname" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", "windows-link 0.1.3", ] [[package]] name = "html5ever" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", "match_token", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "http-range-header" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humansize" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ "libm", ] [[package]] name = "hyper" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "pin-utils", "smallvec", "tokio", "want", ] [[package]] name = "hyper-util" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2 0.5.10", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "ignore" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", "regex-automata", "same-file", "walkdir", "winapi-util", ] [[package]] name = "include_dir" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "indenter" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown 0.16.0", "serde", "serde_core", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "inherent" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "inotify" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ "bitflags 2.9.4", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] [[package]] name = "insta" version = "1.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" dependencies = [ "console", "once_cell", "pest", "pest_derive", "regex", "serde", "similar", ] [[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags 2.9.4", "cfg-if", "libc", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "ipnetwork" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" dependencies = [ "serde", ] [[package]] name = "iri-string" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "jsonwebtoken" version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ "base64", "js-sys", "pem", "ring", "serde", "serde_json", "simple_asn1", ] [[package]] name = "kqueue" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", ] [[package]] name = "kqueue-sys" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "lettre" version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" dependencies = [ "async-trait", "base64", "chumsky", "email-encoding", "email_address", "fastrand", "futures-io", "futures-util", "hostname", "httpdate", "idna", "mime", "nom 8.0.0", "percent-encoding", "quoted_printable", "rustls", "socket2 0.6.0", "tokio", "tokio-rustls", "url", "webpki-roots 1.0.2", ] [[package]] name = "libc" version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", "redox_syscall", ] [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "loco-gen" version = "0.16.3" source = "git+https://github.com/SeaQL/loco?branch=master#0e2860b7885e6c00e013b3618896a78095809ede" dependencies = [ "chrono", "clap", "colored 3.0.0", "cruet 0.14.0", "duct", "heck 0.4.1", "include_dir", "regex", "rrgen", "serde", "serde_json", "tera", "thiserror 1.0.69", "tracing", ] [[package]] name = "loco-rs" version = "0.16.3" source = "git+https://github.com/SeaQL/loco?branch=master#0e2860b7885e6c00e013b3618896a78095809ede" dependencies = [ "argon2", "async-trait", "axum", "axum-extra", "axum-test", "backtrace_printer", "byte-unit", "bytes", "chrono", "clap", "colored 3.0.0", "cruet 0.13.3", "dashmap 6.1.0", "duct", "duct_sh", "english-to-cron", "futures-util", "heck 0.4.1", "include_dir", "ipnetwork", "jsonwebtoken", "lettre", "loco-gen", "moka", "notify", "opendal", "rand 0.9.2", "redis", "regex", "scraper", "sea-orm", "sea-orm-migration", "semver", "serde", "serde_json", "serde_variant", "serde_yaml", "sqlx", "tera", "thiserror 1.0.69", "tokio", "tokio-cron-scheduler", "tokio-util", "toml", "tower 0.4.13", "tower-http", "tracing", "tracing-appender", "tracing-subscriber", "tree-fs", "ulid", "uuid", "validator", ] [[package]] name = "loco_seaography" version = "0.1.0" dependencies = [ "async-graphql-axum", "async-trait", "axum", "chrono", "eyre", "include_dir", "insta", "lazy_static", "loco-rs", "migration", "rstest", "sea-orm", "seaography", "serde", "serde_json", "serial_test", "tokio", "tokio-util", "tower-service", "tracing", "tracing-subscriber", "uuid", "validator", ] [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ "hashbrown 0.15.5", ] [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "markup5ever" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", "phf", "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "match_token" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ "regex-automata", ] [[package]] name = "matchit" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "migration" version = "0.1.0" dependencies = [ "loco-rs", "sea-orm-migration", "tokio", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "moka" version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", "parking_lot", "portable-atomic", "rustc_version", "smallvec", "tagptr", "uuid", ] [[package]] name = "multer" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", "http", "httparse", "memchr", "mime", "spin", "version_check", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nom" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[package]] name = "notify" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ "bitflags 2.9.4", "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio", "notify-types", "walkdir", "windows-sys 0.60.2", ] [[package]] name = "notify-types" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand 0.8.5", "smallvec", "zeroize", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "object" version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "opendal" version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb9838d0575c6dbaf3fcec7255af8d5771996d4af900bbb6fa9a314dec00a1a" dependencies = [ "anyhow", "backon", "base64", "bytes", "chrono", "futures", "getrandom 0.2.16", "http", "http-body", "log", "md-5", "percent-encoding", "quick-xml", "reqwest", "serde", "serde_json", "tokio", "uuid", ] [[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "os_pipe" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "ouroboros" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" dependencies = [ "aliasable", "ouroboros_macro", "static_assertions", ] [[package]] name = "ouroboros_macro" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ "heck 0.4.1", "proc-macro2", "proc-macro2-diagnostics", "quote", "syn 2.0.106", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link 0.2.1", ] [[package]] name = "parse-zoneinfo" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", "subtle", ] [[package]] name = "pem" version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64", "serde", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "pest_meta" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" dependencies = [ "pest", "sha2", ] [[package]] name = "pgvector" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" dependencies = [ "serde", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "pluralizer" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b3eba432a00a1f6c16f39147847a870e94e2e9b992759b503e330efec778cbe" dependencies = [ "once_cell", "regex", ] [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-crate" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit 0.23.6", ] [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "proc-macro-error2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "version_check", "yansi", ] [[package]] name = "psm" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e66fcd288453b748497d8fb18bccc83a16b0518e3906d4b8df0a8d42d93dbb1c" dependencies = [ "cc", ] [[package]] name = "ptr_meta" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quick-xml" version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.4", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.3", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "redis" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bc1ea653e0b2e097db3ebb5b7f678be339620b8041f66b30a308c1d45d36a7f" dependencies = [ "bytes", "cfg-if", "combine", "futures-util", "itoa", "num-bigint", "percent-encoding", "pin-project-lite", "ryu", "sha1_smol", "socket2 0.5.10", "tokio", "tokio-util", "url", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] [[package]] name = "regex" version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "relative-path" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rend" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-util", "tower 0.5.2", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", ] [[package]] name = "reserve-port" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ "thiserror 2.0.17", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rkyv" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", "tinyvec", "uuid", ] [[package]] name = "rkyv_derive" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "rrgen" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee7a7ede035354391a37e42aa4935b3d8921f0ded896d2ce44bb1a3b6dd76bab" dependencies = [ "cruet 0.13.3", "fs-err", "glob", "heck 0.4.1", "regex", "serde", "serde_json", "serde_regex", "serde_yaml", "tera", "thiserror 1.0.69", ] [[package]] name = "rsa" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rstest" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ "futures", "futures-timer", "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", "syn 2.0.106", "unicode-ident", ] [[package]] name = "rust-multipart-rfc7578_2" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" dependencies = [ "bytes", "futures-core", "futures-util", "http", "mime", "rand 0.9.2", "thiserror 2.0.17", ] [[package]] name = "rust_decimal" version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand 0.8.5", "rkyv", "serde", "serde_json", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustls" version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0e749d29b2064585327af5038a5a8eb73aeebad4a3472e83531a436563f7208" dependencies = [ "ahash 0.8.12", "cssparser", "ego-tree", "getopts", "html5ever", "indexmap", "precomputed-hash", "selectors", "tendril", ] [[package]] name = "sea-bae" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ "heck 0.4.1", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "sea-orm" version = "2.0.0-rc.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25c0efc234bdf1f073cc9b448da21a22ed0ac7ff0958a0b3bc4994041dc059df" dependencies = [ "async-stream", "async-trait", "bigdecimal", "chrono", "derive_more 2.0.1", "futures-util", "itertools 0.14.0", "log", "ouroboros", "pgvector", "rust_decimal", "sea-orm-macros", "sea-query", "sea-query-sqlx", "sea-schema", "serde", "serde_json", "sqlx", "strum 0.27.2", "thiserror 2.0.17", "time", "tracing", "url", "uuid", ] [[package]] name = "sea-orm-cli" version = "2.0.0-rc.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f50ae78e4442db69930949a8bcf3b82b0e992c8ea389ae2b1d80504029adab5d" dependencies = [ "chrono", "clap", "dotenvy", "glob", "indoc", "regex", "sea-schema", "sqlx", "tokio", "tracing", "tracing-subscriber", "url", ] [[package]] name = "sea-orm-macros" version = "2.0.0-rc.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35b6ce0ae263925930d4e0f95e24a5a8b069dccc61a1ef68da26338470d94929" dependencies = [ "heck 0.5.0", "pluralizer", "proc-macro-crate", "proc-macro2", "quote", "sea-bae", "syn 2.0.106", "unicode-ident", ] [[package]] name = "sea-orm-migration" version = "2.0.0-rc.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433fa69cd3f8dc754a2dc51106aee10262fec84ac5195ff7d9966dd1c0d467bd" dependencies = [ "async-trait", "clap", "dotenvy", "sea-orm", "sea-orm-cli", "sea-schema", "tracing", "tracing-subscriber", ] [[package]] name = "sea-query" version = "1.0.0-rc.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93948054fb2d208555a96d03d2c887591deb42ffe3210eedbd8a3234c6fb6d34" dependencies = [ "chrono", "inherent", "ordered-float", "rust_decimal", "sea-query-derive", "serde_json", "time", "uuid", ] [[package]] name = "sea-query-derive" version = "1.0.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217e9422de35f26c16c5f671fce3c075a65e10322068dbc66078428634af6195" dependencies = [ "darling 0.20.11", "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.106", "thiserror 2.0.17", ] [[package]] name = "sea-query-sqlx" version = "0.8.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693f3ac3a10a228afaf3512b122cffc07c57b4269d233c7ff60571ebb4f0dd17" dependencies = [ "chrono", "rust_decimal", "sea-query", "serde_json", "sqlx", "time", "uuid", ] [[package]] name = "sea-schema" version = "0.17.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b363dd21c20fe4d1488819cb2bc7f8d4696c62dd9f39554f97639f54d57dd0ab" dependencies = [ "async-trait", "sea-query", "sea-query-sqlx", "sea-schema-derive", "sqlx", ] [[package]] name = "sea-schema-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "seahash" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "seaography" version = "2.0.0-rc.6" source = "git+https://github.com/SeaQL/seaography.git?branch=main#ee380956016e74531015051a31f889e45337a335" dependencies = [ "async-graphql", "fnv", "heck 0.4.1", "itertools 0.12.1", "lazy_static", "sea-orm", "seaography-macros", "serde", "serde_json", "thiserror 1.0.69", ] [[package]] name = "seaography-macros" version = "2.0.0-rc.6" source = "git+https://github.com/SeaQL/seaography.git?branch=main#ee380956016e74531015051a31f889e45337a335" dependencies = [ "darling 0.21.3", "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "selectors" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ "bitflags 2.9.4", "cssparser", "derive_more 0.99.20", "fxhash", "log", "new_debug_unreachable", "phf", "phf_codegen", "precomputed-hash", "servo_arc", "smallvec", ] [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "serde_path_to_error" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", "serde_core", ] [[package]] name = "serde_regex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", "serde", ] [[package]] name = "serde_spanned" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "serde_variant" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a0068df419f9d9b6488fdded3f1c818522cdea328e02ce9d9f147380265a432" dependencies = [ "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "serial_test" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" dependencies = [ "dashmap 5.5.3", "futures", "lazy_static", "log", "parking_lot", "serial_test_derive", ] [[package]] name = "serial_test_derive" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "servo_arc" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4" dependencies = [ "stable_deref_trait", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shared_child" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ "libc", "sigchld", "windows-sys 0.60.2", ] [[package]] name = "shared_thread" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "sigchld" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" dependencies = [ "libc", "os_pipe", "signal-hook", ] [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core 0.6.4", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", "thiserror 2.0.17", "time", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slug" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ "deunicode", "wasm-bindgen", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "socket2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "sqlx" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", ] [[package]] name = "sqlx-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes", "chrono", "crc", "crossbeam-queue", "either", "event-listener", "futures-core", "futures-intrusive", "futures-io", "futures-util", "hashbrown 0.15.5", "hashlink", "indexmap", "log", "memchr", "once_cell", "percent-encoding", "rust_decimal", "rustls", "serde", "serde_json", "sha2", "smallvec", "thiserror 2.0.17", "time", "tokio", "tokio-stream", "tracing", "url", "uuid", "webpki-roots 0.26.11", ] [[package]] name = "sqlx-macros" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", "syn 2.0.106", ] [[package]] name = "sqlx-macros-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", "heck 0.5.0", "hex", "once_cell", "proc-macro2", "quote", "serde", "serde_json", "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.106", "tokio", "url", ] [[package]] name = "sqlx-mysql" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", "bitflags 2.9.4", "byteorder", "bytes", "chrono", "crc", "digest", "dotenvy", "either", "futures-channel", "futures-core", "futures-io", "futures-util", "generic-array", "hex", "hkdf", "hmac", "itoa", "log", "md-5", "memchr", "once_cell", "percent-encoding", "rand 0.8.5", "rsa", "rust_decimal", "serde", "sha1", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.17", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-postgres" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", "bitflags 2.9.4", "byteorder", "chrono", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", "futures-util", "hex", "hkdf", "hmac", "home", "itoa", "log", "md-5", "memchr", "once_cell", "rand 0.8.5", "rust_decimal", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.17", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-sqlite" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", "flume", "futures-channel", "futures-core", "futures-executor", "futures-intrusive", "futures-util", "libsqlite3-sys", "log", "percent-encoding", "serde", "serde_urlencoded", "sqlx-core", "thiserror 2.0.17", "time", "tracing", "url", "uuid", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", "libc", "psm", "windows-sys 0.59.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "static_assertions_next" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" [[package]] name = "string_cache" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", ] [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ "unicode-bidi", "unicode-normalization", "unicode-properties", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "strum_macros" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "rustversion", "syn 2.0.106", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "tera" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ "chrono", "chrono-tz", "globwalk", "humansize", "lazy_static", "percent-encoding", "pest", "pest_derive", "rand 0.8.5", "regex", "serde", "serde_json", "slug", "unic-segment", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] [[package]] name = "time" version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", "slab", "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] [[package]] name = "tokio-cron-scheduler" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2594dd7c2abbbafbb1c78d167fd10860dc7bd75f814cb051a1e0d3e796b9702" dependencies = [ "chrono", "cron", "num-derive", "num-traits", "tokio", "tracing", "uuid", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-stream" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-tungstenite" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", "tokio", "tungstenite", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_datetime" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime 0.6.11", "toml_write", "winnow", ] [[package]] name = "toml_edit" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", "toml_datetime 0.7.2", "toml_parser", "winnow", ] [[package]] name = "toml_parser" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] [[package]] name = "toml_write" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-http" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", "bitflags 2.9.4", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "http-range-header", "httpdate", "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-appender" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "thiserror 1.0.69", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-serde" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex-automata", "serde", "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", "tracing-serde", ] [[package]] name = "tree-fs" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6115680fa5fdb99b4ff19c9c3217e75116d2bb0eae82458c4e1818be6a10c7" dependencies = [ "rand 0.9.2", "serde", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", "http", "httparse", "log", "rand 0.9.2", "sha1", "thiserror 2.0.17", "utf-8", ] [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "ulid" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" dependencies = [ "rand 0.9.2", "web-time", ] [[package]] name = "unic-char-property" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" dependencies = [ "unic-char-range", ] [[package]] name = "unic-char-range" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" [[package]] name = "unic-common" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" [[package]] name = "unic-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" dependencies = [ "unic-ucd-segment", ] [[package]] name = "unic-ucd-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" dependencies = [ "unic-char-property", "unic-char-range", "unic-ucd-version", ] [[package]] name = "unic-ucd-version" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" dependencies = [ "unic-common", ] [[package]] name = "unicase" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", "rand 0.9.2", "serde", "wasm-bindgen", ] [[package]] name = "validator" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ "idna", "once_cell", "regex", "serde", "serde_derive", "serde_json", "url", "validator_derive", ] [[package]] name = "validator_derive" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" dependencies = [ "darling 0.20.11", "once_cell", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-streams" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "web-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "web-time" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ "webpki-roots 1.0.2", ] [[package]] name = "webpki-roots" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ "libredox", "wasite", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link 0.2.1", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.5", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "zstd" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", ] ================================================ FILE: examples/loco_seaography/Cargo.toml ================================================ [workspace] [package] edition = "2024" name = "loco_seaography" rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] loco-rs = { version = "0.16" } migration = { path = "migration" } async-graphql-axum = { version = "7.0" } async-trait = "0.1.74" axum = { version = "0.8", features = ["multipart"] } chrono = "0.4" eyre = "0.6" include_dir = "0.7" lazy_static = { version = "1.4" } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1.33.0", default-features = false } tokio-util = "0.7.11" tower-service = { version = "0.3" } tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] } uuid = { version = "1.6.0", features = ["v4"] } validator = { version = "0.20" } [dependencies.sea-orm] features = [ "sqlx-sqlite", "sqlx-postgres", "runtime-tokio-rustls", "macros", ] version = "~2.0.0-rc.37" # sea-orm version [dependencies.seaography] branch = "main" features = ["graphql-playground", "with-decimal", "with-chrono"] git = "https://github.com/SeaQL/seaography.git" version = "~2.0.0-rc.1" # seaography version [[bin]] name = "loco_seaography-cli" path = "src/bin/main.rs" required-features = [] [dev-dependencies] insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } loco-rs = { version = "0.16", features = ["testing"] } rstest = "0.18.2" serial_test = "2.0.0" [patch.crates-io] loco-rs = { git = "https://github.com/SeaQL/loco", branch = "master" } ================================================ FILE: examples/loco_seaography/README.md ================================================ # Adding GraphQL Support to Loco with Seaography In this tutorial, we would add a GraphQL endpoint with [Seaography](https://github.com/SeaQL/seaography) based on our Loco starter application. Read The full tutorial [here](https://www.sea-ql.org/blog/2024-07-01-graphql-support-with-loco-seaography/). Read our first tutorial of the series, [Getting Started with Loco & SeaORM](https://www.sea-ql.org/blog/2024-05-28-getting-started-with-loco-seaorm/), if you haven't. ![Screenshot Query](Screenshot-Query.png) ![Screenshot Create](Screenshot-Create.png) ================================================ FILE: examples/loco_seaography/config/development.yaml ================================================ # Loco configuration file documentation # Application logging configuration logger: # Enable or disable logging. enable: true # Enable pretty backtrace (sets RUST_BACKTRACE=1) pretty_backtrace: true # Log level, options: trace, debug, info, warn or error. level: debug # Define the logging format. options: compact, pretty or Json format: compact # By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries # Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters. # override_filter: trace # Web server configuration server: # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} port: 3000 # The UI hostname or IP address that mailers will point to. host: http://localhost # Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block middlewares: # Enable Etag cache header middleware etag: enable: true # Allows to limit the payload size request. payload that bigger than this file will blocked the request. limit_payload: # Enable/Disable the middleware. enable: true # the limit size. can be b,kb,kib,mb,mib,gb,gib body_limit: 5mb # Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details. logger: # Enable/Disable the middleware. enable: true # when your code is panicked, the request still returns 500 status code. catch_panic: # Enable/Disable the middleware. enable: true # Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned. timeout_request: # Enable/Disable the middleware. enable: false # Duration time in milliseconds. timeout: 5000 cors: enable: true # Set the value of the [`Access-Control-Allow-Origin`][mdn] header # allow_origins: # - https://loco.rs # Set the value of the [`Access-Control-Allow-Headers`][mdn] header # allow_headers: # - Content-Type # Set the value of the [`Access-Control-Allow-Methods`][mdn] header # allow_methods: # - POST # Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds # max_age: 3600 # Worker Configuration workers: # specifies the worker mode. Options: # - BackgroundQueue - Workers operate asynchronously in the background, processing queued. # - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed. # - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities. mode: BackgroundQueue # Mailer Configuration. mailer: # SMTP mailer configuration. smtp: # Enable/Disable smtp mailer. enable: true # SMTP server host. e.x localhost, smtp.gmail.com host: {{ get_env(name="MAILER_HOST", default="localhost") }} # SMTP server port port: 1025 # Use secure connection (SSL/TLS). secure: false # auth: # user: # password: # Initializers Configuration # initializers: # oauth2: # authorization_code: # Authorization code grant type # - client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config. # ... other fields # Database Configuration database: # Database connection URI uri: {{ get_env(name="DATABASE_URL", default="sqlite://loco_seaography.db?mode=rwc") }} # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. connect_timeout: 500 # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. min_connections: 1 # Maximum number of connections for a pool. max_connections: 1 # Run migration up when application loaded auto_migrate: true # Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_truncate: false # Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_recreate: false # Redis Configuration redis: # Redis connection URI uri: {{ get_env(name="REDIS_URL", default="redis://127.0.0.1") }} # Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_flush: false # Authentication Configuration auth: # JWT authentication jwt: # Secret key for token generation and verification secret: pByQUgg4GmXKAqQQvAGo # Token expiration time in seconds expiration: 604800 # 7 days ================================================ FILE: examples/loco_seaography/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] loco-rs = { version = "0.16" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. # e.g. "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature ] version = "~2.0.0-rc.37" # sea-orm-migration version [patch.crates-io] loco-rs = { git = "https://github.com/SeaQL/loco", branch = "master" } ================================================ FILE: examples/loco_seaography/migration/README.md ================================================ # Running Migrator CLI - Generate a new migration file ```sh cargo run -- migrate generate MIGRATION_NAME ``` - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/loco_seaography/migration/src/lib.rs ================================================ #![allow(elided_lifetimes_in_paths)] #![allow(clippy::wildcard_imports)] pub use sea_orm_migration::prelude::*; mod m20220101_000001_users; mod m20231103_114510_notes; mod m20240520_173001_files; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220101_000001_users::Migration), Box::new(m20231103_114510_notes::Migration), Box::new(m20240520_173001_files::Migration), ] } } ================================================ FILE: examples/loco_seaography/migration/src/m20220101_000001_users.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let table = table_auto("users") .col(pk_auto("id")) .col(uuid("pid")) .col(string_uniq("email")) .col(string("password")) .col(string("api_key").unique_key()) .col(string("name")) .col(string_null("reset_token")) .col(timestamp_null("reset_sent_at")) .col(string_null("email_verification_token")) .col(timestamp_null("email_verification_sent_at")) .col(timestamp_null("email_verified_at")) .to_owned(); manager.create_table(table).await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("users").to_owned()) .await } } ================================================ FILE: examples/loco_seaography/migration/src/m20231103_114510_notes.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto("notes") .col(pk_auto("id")) .col(string_null("title")) .col(string_null("content")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("notes").to_owned()) .await } } ================================================ FILE: examples/loco_seaography/migration/src/m20240520_173001_files.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto("files") .col(pk_auto("id")) .col(integer("notes_id")) .col(string("file_path")) .foreign_key( ForeignKey::create() .name("FK_files_notes_id") .from("files", "notes_id") .to("notes", "id"), ) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("files").to_owned()) .await } } ================================================ FILE: examples/loco_seaography/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/loco_seaography/src/app.rs ================================================ use std::path::Path; use async_trait::async_trait; use loco_rs::{ Result, app::{AppContext, Hooks, Initializer}, bgworker::{BackgroundWorker, Queue}, boot::{BootResult, StartMode, create_app}, config::Config, controller::AppRoutes, db::{self, truncate_table}, environment::Environment, task::Tasks, }; use migration::Migrator; use crate::{ controllers, initializers, models::_entities::{notes, users}, tasks, workers::downloader::DownloadWorker, }; pub struct App; #[async_trait] impl Hooks for App { fn app_name() -> &'static str { env!("CARGO_CRATE_NAME") } fn app_version() -> String { format!( "{} ({})", env!("CARGO_PKG_VERSION"), option_env!("BUILD_SHA") .or(option_env!("GITHUB_SHA")) .unwrap_or("dev") ) } async fn boot( mode: StartMode, environment: &Environment, config: Config, ) -> Result { create_app::(mode, environment, config).await } async fn initializers(_ctx: &AppContext) -> Result>> { let initializers: Vec> = vec![Box::new(initializers::graphql::GraphQLInitializer)]; Ok(initializers) } fn routes(_ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() .prefix("/api") .add_route(controllers::notes::routes()) .add_route(controllers::auth::routes()) .add_route(controllers::user::routes()) .add_route(controllers::files::routes()) .add_route(controllers::graphql::routes()) } async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> { queue.register(DownloadWorker::build(ctx)).await?; Ok(()) } fn register_tasks(tasks: &mut Tasks) { tasks.register(tasks::seed::SeedData); } async fn truncate(ctx: &AppContext) -> Result<()> { let db = &ctx.db; truncate_table(db, users::Entity).await?; truncate_table(db, notes::Entity).await?; Ok(()) } async fn seed(ctx: &AppContext, base: &Path) -> Result<()> { let db = &ctx.db; db::seed::(db, &base.join("users.yaml").display().to_string()).await?; db::seed::(db, &base.join("notes.yaml").display().to_string()).await?; Ok(()) } } ================================================ FILE: examples/loco_seaography/src/bin/main.rs ================================================ use loco_rs::cli; use loco_seaography::app::App; use migration::Migrator; #[tokio::main] async fn main() -> eyre::Result<()> { cli::main::().await?; Ok(()) } ================================================ FILE: examples/loco_seaography/src/controllers/auth.rs ================================================ use axum::debug_handler; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::{ mailers::auth::AuthMailer, models::{ _entities::users, users::{LoginParams, RegisterParams}, }, views::auth::LoginResponse, }; #[derive(Debug, Deserialize, Serialize)] pub struct VerifyParams { pub token: String, } #[derive(Debug, Deserialize, Serialize)] pub struct ForgotParams { pub email: String, } #[derive(Debug, Deserialize, Serialize)] pub struct ResetParams { pub token: String, pub password: String, } /// Register function creates a new user with the given parameters and sends a /// welcome email to the user #[debug_handler] async fn register( State(ctx): State, Json(params): Json, ) -> Result { let res = users::Model::create_with_password(&ctx.db, ¶ms).await; let user = match res { Ok(user) => user, Err(err) => { tracing::info!( message = err.to_string(), user_email = ¶ms.email, "could not register user", ); return format::json(()); } }; // Skip email verification, all new registrations are considered verified let _user = user.into_active_model().verified(&ctx.db).await?; // Skip sending verification email as we don't have a mail server /* let user = user .into_active_model() .set_email_verification_sent(&ctx.db) .await?; AuthMailer::send_welcome(&ctx, &user).await?; */ format::json(()) } /// Verify register user. if the user not verified his email, he can't login to /// the system. #[debug_handler] async fn verify( State(ctx): State, Json(params): Json, ) -> Result { let user = users::Model::find_by_verification_token(&ctx.db, ¶ms.token).await?; if user.email_verified_at.is_some() { tracing::info!(pid = user.pid.to_string(), "user already verified"); } else { let active_model = user.into_active_model(); let user = active_model.verified(&ctx.db).await?; tracing::info!(pid = user.pid.to_string(), "user verified"); } format::json(()) } /// In case the user forgot his password this endpoints generate a forgot token /// and send email to the user. In case the email not found in our DB, we are /// returning a valid request for for security reasons (not exposing users DB /// list). #[debug_handler] async fn forgot( State(ctx): State, Json(params): Json, ) -> Result { let Ok(user) = users::Model::find_by_email(&ctx.db, ¶ms.email).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller return format::json(()); }; let user = user .into_active_model() .set_forgot_password_sent(&ctx.db) .await?; AuthMailer::forgot_password(&ctx, &user).await?; format::json(()) } /// reset user password by the given parameters #[debug_handler] async fn reset(State(ctx): State, Json(params): Json) -> Result { let Ok(user) = users::Model::find_by_reset_token(&ctx.db, ¶ms.token).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller tracing::info!("reset token not found"); return format::json(()); }; user.into_active_model() .reset_password(&ctx.db, ¶ms.password) .await?; format::json(()) } /// Creates a user login and returns a token #[debug_handler] async fn login(State(ctx): State, Json(params): Json) -> Result { let user = users::Model::find_by_email(&ctx.db, ¶ms.email).await?; let valid = user.verify_password(¶ms.password); if !valid { return unauthorized("unauthorized!"); } let jwt_secret = ctx.config.get_jwt_config()?; let token = user .generate_jwt(&jwt_secret.secret, &jwt_secret.expiration) .or_else(|_| unauthorized("unauthorized!"))?; format::json(LoginResponse::new(&user, &token)) } pub fn routes() -> Routes { Routes::new() .prefix("auth") .add("/register", post(register)) .add("/verify", post(verify)) .add("/login", post(login)) .add("/forgot", post(forgot)) .add("/reset", post(reset)) } ================================================ FILE: examples/loco_seaography/src/controllers/files.rs ================================================ #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use std::path::PathBuf; use axum::{body::Body, debug_handler, extract::Multipart}; use loco_rs::prelude::*; use sea_orm::QueryOrder; use tokio::{fs, io::AsyncWriteExt}; use tokio_util::io::ReaderStream; use crate::models::_entities::files; const UPLOAD_DIR: &str = "./uploads"; #[debug_handler] pub async fn upload( _auth: auth::JWT, Path(notes_id): Path, State(ctx): State, mut multipart: Multipart, ) -> Result { // Collect all uploaded files let mut files = Vec::new(); // Iterate all files in the POST body while let Some(field) = multipart.next_field().await.map_err(|err| { tracing::error!(error = ?err,"could not readd multipart"); Error::BadRequest("could not readd multipart".into()) })? { // Get the file name let file_name = match field.file_name() { Some(file_name) => file_name.to_string(), _ => return Err(Error::BadRequest("file name not found".into())), }; // Get the file content as bytes let content = field.bytes().await.map_err(|err| { tracing::error!(error = ?err,"could not readd bytes"); Error::BadRequest("could not readd bytes".into()) })?; // Create a folder to store the uploaded file let now = chrono::offset::Local::now() .format("%Y%m%d_%H%M%S") .to_string(); let uuid = uuid::Uuid::new_v4().to_string(); let folder = format!("{now}_{uuid}"); let upload_folder = PathBuf::from(UPLOAD_DIR).join(&folder); fs::create_dir_all(&upload_folder).await?; // Write the file into the newly created folder let path = upload_folder.join(file_name); let mut f = fs::OpenOptions::new() .create_new(true) .write(true) .open(&path) .await?; f.write_all(&content).await?; f.flush().await?; // Record the file upload in database let file = files::ActiveModel { notes_id: ActiveValue::Set(notes_id), file_path: ActiveValue::Set( path.strip_prefix(UPLOAD_DIR) .unwrap() .to_str() .unwrap() .to_string(), ), ..Default::default() } .insert(&ctx.db) .await?; files.push(file); } format::json(files) } #[debug_handler] pub async fn list( _auth: auth::JWT, Path(notes_id): Path, State(ctx): State, ) -> Result { // Fetch all files uploaded for a specific notes let files = files::Entity::find() .filter(files::Column::NotesId.eq(notes_id)) .order_by_asc(files::Column::Id) .all(&ctx.db) .await?; format::json(files) } #[debug_handler] pub async fn view( _auth: auth::JWT, Path(files_id): Path, State(ctx): State, ) -> Result { // Fetch the file info from database let file = files::Entity::find_by_id(files_id) .one(&ctx.db) .await? .expect("File not found"); // Stream the file let file = fs::File::open(format!("{UPLOAD_DIR}/{}", file.file_path)).await?; let stream = ReaderStream::new(file); let body = Body::from_stream(stream); Ok(format::render().response().body(body)?) } pub fn routes() -> Routes { // Bind the routes Routes::new() .prefix("files") .add("/upload/{notes_id}", post(upload)) .add("/list/{notes_id}", get(list)) .add("/view/{files_id}", get(view)) } ================================================ FILE: examples/loco_seaography/src/controllers/graphql.rs ================================================ use async_graphql::{ dynamic::Schema, http::{GraphQLPlaygroundConfig, playground_source}, }; use async_graphql_axum::GraphQLRequest; use loco_rs::prelude::*; use seaography::async_graphql; // GraphQL playground UI async fn graphql_playground() -> Result { // The `GraphQLPlaygroundConfig` take one parameter // which is the URL of the GraphQL handler: `/api/graphql` let res = playground_source(GraphQLPlaygroundConfig::new("/api/graphql")); Ok(Response::new(res.into())) } async fn graphql_handler( // _auth: auth::JWT, State(ctx): State, gql_req: GraphQLRequest, ) -> Result { let gql_req = gql_req.into_inner(); let schema: Schema = ctx.shared_store.get().ok_or(( axum::http::StatusCode::INTERNAL_SERVER_ERROR, "GraphQL not setup", ))?; let res = schema.execute(gql_req).await.into(); Ok(res) } pub fn routes() -> Routes { // Define route Routes::new() // We put all GraphQL route behind `graphql` prefix .prefix("graphql") // GraphQL playground page is a GET request .add("/", get(graphql_playground)) // GraphQL handler is a POST request .add("/", post(graphql_handler)) } ================================================ FILE: examples/loco_seaography/src/controllers/mod.rs ================================================ pub mod auth; pub mod files; pub mod graphql; pub mod notes; pub mod user; ================================================ FILE: examples/loco_seaography/src/controllers/notes.rs ================================================ #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use axum::debug_handler; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::models::_entities::notes::{ActiveModel, Entity, Model}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Params { pub title: Option, pub content: Option, } impl Params { fn update(&self, item: &mut ActiveModel) { item.title = Set(self.title.clone()); item.content = Set(self.content.clone()); } } async fn load_item(ctx: &AppContext, id: i32) -> Result { let item = Entity::find_by_id(id).one(&ctx.db).await?; item.ok_or_else(|| Error::NotFound) } #[debug_handler] pub async fn list(State(ctx): State) -> Result { format::json(Entity::find().all(&ctx.db).await?) } #[debug_handler] pub async fn add(State(ctx): State, Json(params): Json) -> Result { let mut item = ActiveModel { ..Default::default() }; params.update(&mut item); let item = item.insert(&ctx.db).await?; format::json(item) } #[debug_handler] pub async fn update( Path(id): Path, State(ctx): State, Json(params): Json, ) -> Result { let item = load_item(&ctx, id).await?; let mut item = item.into_active_model(); params.update(&mut item); let item = item.update(&ctx.db).await?; format::json(item) } #[debug_handler] pub async fn remove(Path(id): Path, State(ctx): State) -> Result { load_item(&ctx, id).await?.delete(&ctx.db).await?; format::empty() } #[debug_handler] pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { format::json(load_item(&ctx, id).await?) } pub fn routes() -> Routes { Routes::new() .prefix("notes") .add("/", get(list)) .add("/", post(add)) .add("/{id}", get(get_one)) .add("/{id}", delete(remove)) .add("/{id}", post(update)) } ================================================ FILE: examples/loco_seaography/src/controllers/user.rs ================================================ use axum::debug_handler; use loco_rs::prelude::*; use crate::{models::_entities::users, views::user::CurrentResponse}; #[debug_handler] async fn current(auth: auth::JWT, State(ctx): State) -> Result { let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; format::json(CurrentResponse::new(&user)) } pub fn routes() -> Routes { Routes::new().prefix("user").add("/current", get(current)) } ================================================ FILE: examples/loco_seaography/src/fixtures/notes.yaml ================================================ --- - id: 1 title: Loco note 1 content: Loco note 1 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" - id: 2 title: Loco note 2 content: Loco note 2 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" ================================================ FILE: examples/loco_seaography/src/fixtures/users.yaml ================================================ --- - id: 1 pid: 11111111-1111-1111-1111-111111111111 email: user1@example.com password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" api_key: lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758 name: user1 created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" - id: 2 pid: 22222222-2222-2222-2222-222222222222 email: user2@example.com password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" api_key: lo-153561ca-fa84-4e1b-813a-c62526d0a77e name: user2 created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" ================================================ FILE: examples/loco_seaography/src/graphql/mod.rs ================================================ pub mod query_root; ================================================ FILE: examples/loco_seaography/src/graphql/query_root.rs ================================================ use async_graphql::dynamic::*; use sea_orm::DatabaseConnection; use seaography::{Builder, BuilderContext, async_graphql}; lazy_static::lazy_static! { static ref CONTEXT: BuilderContext = BuilderContext::default(); } pub fn schema( database: DatabaseConnection, depth: Option, complexity: Option, ) -> Result { // Construct GraphQL schema let builder = Builder::new(&CONTEXT, database.clone()); // Register SeaORM Entities let builder = crate::models::_entities::register_entity_modules(builder); builder // Maximum depth of the constructed query .set_depth_limit(depth) // Maximum complexity of the constructed query .set_complexity_limit(complexity) .schema_builder() // GraphQL schema with database connection .data(database) .finish() } ================================================ FILE: examples/loco_seaography/src/initializers/graphql.rs ================================================ use crate::graphql::query_root; use async_trait::async_trait; use axum::Router as AxumRouter; use loco_rs::prelude::*; // Maximum depth of the constructed query const DEPTH: Option = None; // Maximum complexity of the constructed query const COMPLEXITY: Option = None; pub struct GraphQLInitializer; #[async_trait] impl Initializer for GraphQLInitializer { fn name(&self) -> String { "graphql".to_string() } async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result { let schema = query_root::schema(ctx.db.clone(), DEPTH, COMPLEXITY) .expect("Failed to build GraphQL schema"); ctx.shared_store.insert(schema); Ok(router) } } ================================================ FILE: examples/loco_seaography/src/initializers/mod.rs ================================================ pub mod graphql; ================================================ FILE: examples/loco_seaography/src/lib.rs ================================================ pub mod app; pub mod controllers; pub mod graphql; pub mod initializers; pub mod mailers; pub mod models; pub mod tasks; pub mod views; pub mod workers; ================================================ FILE: examples/loco_seaography/src/mailers/auth/forgot/html.t ================================================ ; Hey {{name}}, Forgot your password? No worries! You can reset it by clicking the link below: Reset Your Password If you didn't request a password reset, please ignore this email. Best regards,
The Loco Team
================================================ FILE: examples/loco_seaography/src/mailers/auth/forgot/subject.t ================================================ Your reset password link ================================================ FILE: examples/loco_seaography/src/mailers/auth/forgot/text.t ================================================ Reset your password with this link: http://localhost/reset#{{resetToken}} ================================================ FILE: examples/loco_seaography/src/mailers/auth/welcome/html.t ================================================ ; Dear {{name}}, Welcome to Loco! You can now log in to your account. Before you get started, please verify your account by clicking the link below: Verify Your Account

Best regards,
The Loco Team

================================================ FILE: examples/loco_seaography/src/mailers/auth/welcome/subject.t ================================================ Welcome {{name}} ================================================ FILE: examples/loco_seaography/src/mailers/auth/welcome/text.t ================================================ Welcome {{name}}, you can now log in. Verify your account with the link below: http://localhost/verify#{{verifyToken}} ================================================ FILE: examples/loco_seaography/src/mailers/auth.rs ================================================ // auth mailer #![allow(non_upper_case_globals)] use loco_rs::prelude::*; use serde_json::json; use crate::models::users; static welcome: Dir<'_> = include_dir!("src/mailers/auth/welcome"); static forgot: Dir<'_> = include_dir!("src/mailers/auth/forgot"); // #[derive(Mailer)] // -- disabled for faster build speed. it works. but lets // move on for now. #[allow(clippy::module_name_repetitions)] pub struct AuthMailer {} impl Mailer for AuthMailer {} impl AuthMailer { /// Sending welcome email the the given user /// /// # Errors /// /// When email sending is failed pub async fn send_welcome(ctx: &AppContext, user: &users::Model) -> Result<()> { Self::mail_template( ctx, &welcome, mailer::Args { to: user.email.to_string(), locals: json!({ "name": user.name, "verifyToken": user.email_verification_token, "domain": ctx.config.server.full_url() }), ..Default::default() }, ) .await?; Ok(()) } /// Sending forgot password email /// /// # Errors /// /// When email sending is failed pub async fn forgot_password(ctx: &AppContext, user: &users::Model) -> Result<()> { Self::mail_template( ctx, &forgot, mailer::Args { to: user.email.to_string(), locals: json!({ "name": user.name, "resetToken": user.reset_token, "domain": ctx.config.server.full_url() }), ..Default::default() }, ) .await?; Ok(()) } } ================================================ FILE: examples/loco_seaography/src/mailers/mod.rs ================================================ pub mod auth; ================================================ FILE: examples/loco_seaography/src/models/_entities/files.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.10 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "files")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub notes_id: i32, pub file_path: String, #[sea_orm(belongs_to, from = "notes_id", to = "id")] pub notes: HasOne, } ================================================ FILE: examples/loco_seaography/src/models/_entities/mod.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0-rc.5 pub mod prelude; pub mod files; pub mod notes; pub mod users; seaography::register_entity_modules!([files, notes, users]); ================================================ FILE: examples/loco_seaography/src/models/_entities/notes.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.10 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "notes")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub title: Option, pub content: Option, #[sea_orm(has_many)] pub files: HasMany, } ================================================ FILE: examples/loco_seaography/src/models/_entities/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0-rc.5 pub use super::{files::Entity as Files, notes::Entity as Notes, users::Entity as Users}; ================================================ FILE: examples/loco_seaography/src/models/_entities/users.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0-rc.5 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "users")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub pid: Uuid, #[sea_orm(unique)] pub email: String, pub password: String, #[sea_orm(unique)] pub api_key: String, pub name: String, pub reset_token: Option, pub reset_sent_at: Option, pub email_verification_token: Option, pub email_verification_sent_at: Option, pub email_verified_at: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity {} ================================================ FILE: examples/loco_seaography/src/models/files.rs ================================================ use sea_orm::entity::prelude::*; use super::_entities::files::ActiveModel; impl ActiveModelBehavior for ActiveModel { // extend activemodel below (keep comment for generators) } ================================================ FILE: examples/loco_seaography/src/models/mod.rs ================================================ pub mod _entities; pub mod files; pub mod notes; pub mod users; ================================================ FILE: examples/loco_seaography/src/models/notes.rs ================================================ use sea_orm::entity::prelude::*; use super::_entities::notes::ActiveModel; impl ActiveModelBehavior for ActiveModel { // extend activemodel below (keep comment for generators) } ================================================ FILE: examples/loco_seaography/src/models/users.rs ================================================ use async_trait::async_trait; use chrono::offset::Local; use loco_rs::{auth::jwt, hash, prelude::*}; use serde::{Deserialize, Serialize}; use uuid::Uuid; pub use super::_entities::users::{self, ActiveModel, Entity, Model}; #[derive(Debug, Deserialize, Serialize)] pub struct LoginParams { pub email: String, pub password: String, } #[derive(Debug, Deserialize, Serialize)] pub struct RegisterParams { pub email: String, pub password: String, pub name: String, } #[derive(Debug, Validate, Deserialize)] pub struct Validator { #[validate(length(min = 2, message = "Name must be at least 2 characters long."))] pub name: String, #[validate(email(message = "invalid email"))] pub email: String, } impl Validatable for super::_entities::users::ActiveModel { fn validator(&self) -> Box { Box::new(Validator { name: self.name.as_ref().to_owned(), email: self.email.as_ref().to_owned(), }) } } #[async_trait::async_trait] impl ActiveModelBehavior for super::_entities::users::ActiveModel { async fn before_save(self, _db: &C, insert: bool) -> Result where C: ConnectionTrait, { self.validate()?; if insert { let mut this = self; this.pid = ActiveValue::Set(Uuid::new_v4()); this.api_key = ActiveValue::Set(format!("lo-{}", Uuid::new_v4())); Ok(this) } else { Ok(self) } } } #[async_trait] impl Authenticable for super::_entities::users::Model { async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ApiKey.eq(api_key)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult { Self::find_by_pid(db, claims_key).await } } impl super::_entities::users::Model { /// finds a user by the provided email /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::Email.eq(email)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided verification token /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_verification_token( db: &DatabaseConnection, token: &str, ) -> ModelResult { let user = users::Entity::find() .filter(users::Column::EmailVerificationToken.eq(token)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// /// finds a user by the provided reset token /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_reset_token(db: &DatabaseConnection, token: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ResetToken.eq(token)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided pid /// /// # Errors /// /// When could not find user or DB query error pub async fn find_by_pid(db: &DatabaseConnection, pid: &str) -> ModelResult { let parse_uuid = Uuid::parse_str(pid).map_err(|e| ModelError::Any(e.into()))?; let user = users::Entity::find() .filter(users::Column::Pid.eq(parse_uuid)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided api key /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ApiKey.eq(api_key)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// Verifies whether the provided plain password matches the hashed password /// /// # Errors /// /// when could not verify password #[must_use] pub fn verify_password(&self, password: &str) -> bool { hash::verify_password(password, &self.password) } /// Asynchronously creates a user with a password and saves it to the /// database. /// /// # Errors /// /// When could not save the user into the DB pub async fn create_with_password( db: &DatabaseConnection, params: &RegisterParams, ) -> ModelResult { let txn = db.begin().await?; if users::Entity::find() .filter(users::Column::Email.eq(¶ms.email)) .one(&txn) .await? .is_some() { return Err(ModelError::EntityAlreadyExists {}); } let password_hash = hash::hash_password(¶ms.password).map_err(|e| ModelError::Any(e.into()))?; let user = users::ActiveModel { email: ActiveValue::set(params.email.to_string()), password: ActiveValue::set(password_hash), name: ActiveValue::set(params.name.to_string()), ..Default::default() } .insert(&txn) .await?; txn.commit().await?; Ok(user) } /// Creates a JWT /// /// # Errors /// /// when could not convert user claims to jwt token pub fn generate_jwt(&self, secret: &str, expiration: &u64) -> ModelResult { Ok(jwt::JWT::new(secret).generate_token( *expiration, self.pid.to_string(), Default::default(), )?) } } impl super::_entities::users::ActiveModel { /// Sets the email verification information for the user and /// updates it in the database. /// /// This method is used to record the timestamp when the email verification /// was sent and generate a unique verification token for the user. /// /// # Errors /// /// when has DB query error pub async fn set_email_verification_sent( mut self, db: &DatabaseConnection, ) -> ModelResult { self.email_verification_sent_at = ActiveValue::set(Some(Local::now().naive_local())); self.email_verification_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); Ok(self.update(db).await?) } /// Sets the information for a reset password request, /// generates a unique reset password token, and updates it in the /// database. /// /// This method records the timestamp when the reset password token is sent /// and generates a unique token for the user. /// /// # Arguments /// /// # Errors /// /// when has DB query error pub async fn set_forgot_password_sent(mut self, db: &DatabaseConnection) -> ModelResult { self.reset_sent_at = ActiveValue::set(Some(Local::now().naive_local())); self.reset_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); Ok(self.update(db).await?) } /// Records the verification time when a user verifies their /// email and updates it in the database. /// /// This method sets the timestamp when the user successfully verifies their /// email. /// /// # Errors /// /// when has DB query error pub async fn verified(mut self, db: &DatabaseConnection) -> ModelResult { self.email_verified_at = ActiveValue::set(Some(Local::now().naive_local())); Ok(self.update(db).await?) } /// Resets the current user password with a new password and /// updates it in the database. /// /// This method hashes the provided password and sets it as the new password /// for the user. /// # Errors /// /// when has DB query error or could not hashed the given password pub async fn reset_password( mut self, db: &DatabaseConnection, password: &str, ) -> ModelResult { self.password = ActiveValue::set(hash::hash_password(password).map_err(|e| ModelError::Any(e.into()))?); Ok(self.update(db).await?) } } ================================================ FILE: examples/loco_seaography/src/tasks/mod.rs ================================================ pub mod seed; ================================================ FILE: examples/loco_seaography/src/tasks/seed.rs ================================================ //! This task implements data seeding functionality for initializing new //! development/demo environments. //! //! # Example //! //! Run the task with the following command: //! ```sh //! cargo run task //! ``` //! //! To override existing data and reset the data structure, use the following //! command with the `refresh:true` argument: //! ```sh //! cargo run task seed_data refresh:true //! ``` use loco_rs::{db, prelude::*}; use migration::Migrator; use crate::app::App; #[allow(clippy::module_name_repetitions)] pub struct SeedData; #[async_trait] impl Task for SeedData { fn task(&self) -> TaskInfo { TaskInfo { name: "seed_data".to_string(), detail: "Task for seeding data".to_string(), } } async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> { let refresh = vars .cli_arg("refresh") .is_ok_and(|refresh| refresh == "true"); if refresh { db::reset::(&app_context.db).await?; } let path = std::path::Path::new("src/fixtures"); db::run_app_seed::(app_context, path).await?; Ok(()) } } ================================================ FILE: examples/loco_seaography/src/views/auth.rs ================================================ use serde::{Deserialize, Serialize}; use crate::models::_entities::users; #[derive(Debug, Deserialize, Serialize)] pub struct LoginResponse { pub token: String, pub pid: String, pub name: String, pub is_verified: bool, } impl LoginResponse { #[must_use] pub fn new(user: &users::Model, token: &String) -> Self { Self { token: token.to_string(), pid: user.pid.to_string(), name: user.name.clone(), is_verified: user.email_verified_at.is_some(), } } } ================================================ FILE: examples/loco_seaography/src/views/mod.rs ================================================ pub mod auth; pub mod user; ================================================ FILE: examples/loco_seaography/src/views/user.rs ================================================ use serde::{Deserialize, Serialize}; use crate::models::_entities::users; #[derive(Debug, Deserialize, Serialize)] pub struct CurrentResponse { pub pid: String, pub name: String, pub email: String, } impl CurrentResponse { #[must_use] pub fn new(user: &users::Model) -> Self { Self { pid: user.pid.to_string(), name: user.name.clone(), email: user.email.clone(), } } } ================================================ FILE: examples/loco_seaography/src/workers/downloader.rs ================================================ use std::time::Duration; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use tokio::time::sleep; use crate::models::users; pub struct DownloadWorker { pub ctx: AppContext, } #[derive(Deserialize, Debug, Serialize)] pub struct DownloadWorkerArgs { pub user_guid: String, } #[async_trait] impl BackgroundWorker for DownloadWorker { fn build(ctx: &AppContext) -> Self { Self { ctx: ctx.clone() } } async fn perform(&self, args: DownloadWorkerArgs) -> Result<()> { // TODO: Some actual work goes here... println!("================================================"); println!("Sending payment report to user {}", args.user_guid); sleep(Duration::from_millis(2000)).await; let all = users::Entity::find() .all(&self.ctx.db) .await .map_err(Box::from)?; for user in &all { println!("user: {}", user.id); } println!("================================================"); Ok(()) } } ================================================ FILE: examples/loco_seaography/src/workers/mod.rs ================================================ pub mod downloader; ================================================ FILE: examples/loco_starter/.cargo/config.toml ================================================ [alias] loco = "run --" playground = "run --example playground" ================================================ FILE: examples/loco_starter/.devcontainer/Dockerfile ================================================ FROM mcr.microsoft.com/devcontainers/rust:1 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends postgresql-client \ && cargo install sea-orm-cli cargo-insta \ && chown -R vscode /usr/local/cargo COPY .env /.env ================================================ FILE: examples/loco_starter/.devcontainer/devcontainer.json ================================================ { "name": "Loco", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "forwardPorts": [ 3000 ] } ================================================ FILE: examples/loco_starter/.devcontainer/docker-compose.yml ================================================ version: "3" services: app: build: context: . dockerfile: Dockerfile command: sleep infinity networks: - db - redis - mailer volumes: - ../..:/workspaces:cached env_file: - .env db: image: postgres:15.3-alpine restart: unless-stopped ports: - 5432:5432 networks: - db volumes: - postgres-data:/var/lib/postgresql/data env_file: - .env redis: image: redis:latest restart: unless-stopped ports: - 6379:6379 networks: - redis mailer: image: mailtutan/mailtutan:latest restart: unless-stopped ports: - 1080:1080 - 1025:1025 networks: - mailer volumes: postgres-data: networks: db: redis: mailer: ================================================ FILE: examples/loco_starter/.github/workflows/ci.yaml ================================================ name: CI on: push: branches: - master - main pull_request: env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: rustfmt: name: Check Style runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true components: rustfmt - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: Run Clippy runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo clippy uses: actions-rs/cargo@v1 with: command: clippy args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms test: name: Run Tests runs-on: ubuntu-latest permissions: contents: read services: redis: image: redis options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - "6379:6379" postgres: image: postgres env: POSTGRES_DB: postgres_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - "5432:5432" # Set health checks to wait until postgres has started options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test args: --all-features --all env: REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}} DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test ================================================ FILE: examples/loco_starter/.gitignore ================================================ **/config/local.yaml **/config/*.local.yaml **/config/production.yaml # Generated by Cargo # will have compiled files and executables debug/ target/ # include cargo lock !Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb uploads ================================================ FILE: examples/loco_starter/.rustfmt.toml ================================================ comment_width = 80 format_strings = true group_imports = "StdExternalCrate" imports_granularity = "Crate" max_width = 100 use_small_heuristics = "Default" wrap_comments = true ================================================ FILE: examples/loco_starter/Cargo.lock ================================================ # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.16", "once_cell", "version_check", ] [[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aliasable" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "alloc-no-stdlib" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.60.2", ] [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert-json-diff" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ "serde", "serde_json", ] [[package]] name = "async-compression" version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ "compression-codecs", "compression-core", "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "async-stream" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", "pin-project-lite", ] [[package]] name = "async-stream-impl" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "async-trait" version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auto-future" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" dependencies = [ "axum-core", "axum-macros", "bytes", "form_urlencoded", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "itoa", "matchit", "memchr", "mime", "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", "sync_wrapper", "tokio", "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-core" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-extra" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ "axum", "axum-core", "bytes", "cookie", "futures-util", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", "serde_core", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-macros" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "axum-test" version = "17.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" dependencies = [ "anyhow", "assert-json-diff", "auto-future", "axum", "bytes", "bytesize", "cookie", "http", "http-body-util", "hyper", "hyper-util", "mime", "pretty_assertions", "reserve-port", "rust-multipart-rfc7578_2", "serde", "serde_json", "serde_urlencoded", "smallvec", "tokio", "tower 0.5.2", "url", ] [[package]] name = "backon" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" dependencies = [ "fastrand", "gloo-timers", "tokio", ] [[package]] name = "backtrace" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-link 0.2.1", ] [[package]] name = "backtrace_printer" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d28de81c708c843640982b66573df0f0168d87e42854b563971f326745aab7" dependencies = [ "btparse-stable", "colored 2.2.0", "regex", "thiserror 1.0.69", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bigdecimal" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", "num-bigint", "num-integer", "num-traits", "serde", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borsh" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases", ] [[package]] name = "borsh-derive" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "brotli" version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] name = "bstr" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "serde", ] [[package]] name = "btparse-stable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d75b8252ed252f881d1dc4482ae3c3854df6ee8183c1906bac50ff358f4f89f" [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-unit" version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" dependencies = [ "serde", "utf8-width", ] [[package]] name = "bytecheck" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", "simdutf8", ] [[package]] name = "bytecheck_derive" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytesize" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" [[package]] name = "cc" version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-link 0.2.1", ] [[package]] name = "chrono-tz" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", "phf", "phf_codegen", ] [[package]] name = "chumsky" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ "hashbrown 0.14.5", "stacker", ] [[package]] name = "clap" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "clap_lex" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "colored" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "futures-core", "memchr", "pin-project-lite", "tokio", "tokio-util", ] [[package]] name = "compression-codecs" version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" dependencies = [ "brotli", "compression-core", "flate2", "memchr", "zstd", "zstd-safe", ] [[package]] name = "compression-core" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cookie" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time", "version_check", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "cron" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" dependencies = [ "chrono", "nom 7.1.3", "once_cell", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "cruet" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "113a9e83d8f614be76de8df1f25bf9d0ea6e85ea573710a3d3f3abe1438ae49c" dependencies = [ "once_cell", "regex", ] [[package]] name = "cruet" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6132609543972496bc97b1e01f1ce6586768870aeb4cabeb3385f4e05b5caead" dependencies = [ "once_cell", "regex", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "cssparser" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" dependencies = [ "cssparser-macros", "dtoa-short", "itoa", "phf", "smallvec", ] [[package]] name = "cssparser-macros" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", "syn 2.0.106", ] [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.106", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", "syn 2.0.106", ] [[package]] name = "dashmap" version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "deranged" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", "serde_core", ] [[package]] name = "derive_more" version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "unicode-xid", ] [[package]] name = "deunicode" version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dtoa-short" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" dependencies = [ "dtoa", ] [[package]] name = "duct" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7478638a31d1f1f3d6c9f5e57c76b906a04ac4879d6fd0fb6245bc88f73fd0b" dependencies = [ "libc", "os_pipe", "shared_child", "shared_thread", ] [[package]] name = "duct_sh" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8139179d1d133ab7153920ba3812915b17c61e2514a6f98b1fd03f2c07668d1" dependencies = [ "duct", ] [[package]] name = "ego-tree" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c6ba7d4eec39eaa9ab24d44a0e73a7949a1095a8b3f3abb11eddf27dbb56a53" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] [[package]] name = "email-encoding" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" dependencies = [ "base64", "memchr", ] [[package]] name = "email_address" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "english-to-cron" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e26fb7377cbec9a94f60428e6e6afbe10c699a14639b4d3d4b67b25c0bbe0806" dependencies = [ "regex", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "etcetera" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", "windows-sys 0.48.0", ] [[package]] name = "event-listener" version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flate2" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", "spin", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] [[package]] name = "fsevent-sys" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ "libc", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-intrusive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", "parking_lot", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "fxhash" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ "byteorder", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getopts" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", "regex-syntax", ] [[package]] name = "globwalk" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ "bitflags 2.9.4", "ignore", "walkdir", ] [[package]] name = "gloo-timers" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash 0.7.8", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.12", "allocator-api2", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown 0.15.5", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "hostname" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", "windows-link 0.1.3", ] [[package]] name = "html5ever" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", "match_token", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "http-range-header" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humansize" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ "libm", ] [[package]] name = "hyper" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "pin-utils", "smallvec", "tokio", "want", ] [[package]] name = "hyper-util" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2 0.6.0", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "ignore" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", "regex-automata", "same-file", "walkdir", "winapi-util", ] [[package]] name = "include_dir" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "indenter" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown 0.16.0", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "inherent" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "inotify" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ "bitflags 2.9.4", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] [[package]] name = "insta" version = "1.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" dependencies = [ "console", "once_cell", "pest", "pest_derive", "regex", "serde", "similar", ] [[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags 2.9.4", "cfg-if", "libc", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "ipnetwork" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" dependencies = [ "serde", ] [[package]] name = "iri-string" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "jsonwebtoken" version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ "base64", "js-sys", "pem", "ring", "serde", "serde_json", "simple_asn1", ] [[package]] name = "kqueue" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", ] [[package]] name = "kqueue-sys" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "lettre" version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" dependencies = [ "async-trait", "base64", "chumsky", "email-encoding", "email_address", "fastrand", "futures-io", "futures-util", "hostname", "httpdate", "idna", "mime", "nom 8.0.0", "percent-encoding", "quoted_printable", "rustls", "socket2 0.6.0", "tokio", "tokio-rustls", "url", "webpki-roots 1.0.2", ] [[package]] name = "libc" version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", "redox_syscall", ] [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "loco-gen" version = "0.16.3" source = "git+https://github.com/SeaQL/loco?branch=master#0e2860b7885e6c00e013b3618896a78095809ede" dependencies = [ "chrono", "clap", "colored 3.0.0", "cruet 0.14.0", "duct", "heck 0.4.1", "include_dir", "regex", "rrgen", "serde", "serde_json", "tera", "thiserror 1.0.69", "tracing", ] [[package]] name = "loco-rs" version = "0.16.3" source = "git+https://github.com/SeaQL/loco?branch=master#0e2860b7885e6c00e013b3618896a78095809ede" dependencies = [ "argon2", "async-trait", "axum", "axum-extra", "axum-test", "backtrace_printer", "byte-unit", "bytes", "chrono", "clap", "colored 3.0.0", "cruet 0.13.3", "dashmap 6.1.0", "duct", "duct_sh", "english-to-cron", "futures-util", "heck 0.4.1", "include_dir", "ipnetwork", "jsonwebtoken", "lettre", "loco-gen", "moka", "notify", "opendal", "rand 0.9.2", "redis", "regex", "scraper", "sea-orm", "sea-orm-migration", "semver", "serde", "serde_json", "serde_variant", "serde_yaml", "sqlx", "tera", "thiserror 1.0.69", "tokio", "tokio-cron-scheduler", "tokio-util", "toml", "tower 0.4.13", "tower-http", "tracing", "tracing-appender", "tracing-subscriber", "tree-fs", "ulid", "uuid", "validator", ] [[package]] name = "loco_starter" version = "0.1.0" dependencies = [ "async-trait", "axum", "chrono", "eyre", "include_dir", "insta", "loco-rs", "migration", "rstest", "sea-orm", "serde", "serde_json", "serial_test", "tokio", "tokio-util", "tracing", "tracing-subscriber", "uuid", "validator", ] [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "markup5ever" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", "phf", "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "match_token" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ "regex-automata", ] [[package]] name = "matchit" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "migration" version = "0.1.0" dependencies = [ "loco-rs", "sea-orm-migration", "tokio", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "moka" version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", "parking_lot", "portable-atomic", "rustc_version", "smallvec", "tagptr", "uuid", ] [[package]] name = "multer" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", "http", "httparse", "memchr", "mime", "spin", "version_check", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nom" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[package]] name = "notify" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ "bitflags 2.9.4", "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio", "notify-types", "walkdir", "windows-sys 0.60.2", ] [[package]] name = "notify-types" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand 0.8.5", "smallvec", "zeroize", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "object" version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "opendal" version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb9838d0575c6dbaf3fcec7255af8d5771996d4af900bbb6fa9a314dec00a1a" dependencies = [ "anyhow", "backon", "base64", "bytes", "chrono", "futures", "getrandom 0.2.16", "http", "http-body", "log", "md-5", "percent-encoding", "quick-xml", "reqwest", "serde", "serde_json", "tokio", "uuid", ] [[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "os_pipe" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "ouroboros" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" dependencies = [ "aliasable", "ouroboros_macro", "static_assertions", ] [[package]] name = "ouroboros_macro" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ "heck 0.4.1", "proc-macro2", "proc-macro2-diagnostics", "quote", "syn 2.0.106", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link 0.2.1", ] [[package]] name = "parse-zoneinfo" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", "subtle", ] [[package]] name = "pem" version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64", "serde", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "pest_meta" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" dependencies = [ "pest", "sha2", ] [[package]] name = "pgvector" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" dependencies = [ "serde", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-crate" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit 0.23.6", ] [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "proc-macro-error2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "version_check", "yansi", ] [[package]] name = "psm" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e66fcd288453b748497d8fb18bccc83a16b0518e3906d4b8df0a8d42d93dbb1c" dependencies = [ "cc", ] [[package]] name = "ptr_meta" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quick-xml" version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.4", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.3", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "redis" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bc1ea653e0b2e097db3ebb5b7f678be339620b8041f66b30a308c1d45d36a7f" dependencies = [ "bytes", "cfg-if", "combine", "futures-util", "itoa", "num-bigint", "percent-encoding", "pin-project-lite", "ryu", "sha1_smol", "socket2 0.5.10", "tokio", "tokio-util", "url", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] [[package]] name = "regex" version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "relative-path" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rend" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-util", "tower 0.5.2", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", ] [[package]] name = "reserve-port" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ "thiserror 2.0.17", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rkyv" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", "tinyvec", "uuid", ] [[package]] name = "rkyv_derive" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "rrgen" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee7a7ede035354391a37e42aa4935b3d8921f0ded896d2ce44bb1a3b6dd76bab" dependencies = [ "cruet 0.13.3", "fs-err", "glob", "heck 0.4.1", "regex", "serde", "serde_json", "serde_regex", "serde_yaml", "tera", "thiserror 1.0.69", ] [[package]] name = "rsa" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rstest" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ "futures", "futures-timer", "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", "syn 2.0.106", "unicode-ident", ] [[package]] name = "rust-multipart-rfc7578_2" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" dependencies = [ "bytes", "futures-core", "futures-util", "http", "mime", "rand 0.9.2", "thiserror 2.0.17", ] [[package]] name = "rust_decimal" version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand 0.8.5", "rkyv", "serde", "serde_json", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustls" version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0e749d29b2064585327af5038a5a8eb73aeebad4a3472e83531a436563f7208" dependencies = [ "ahash 0.8.12", "cssparser", "ego-tree", "getopts", "html5ever", "indexmap", "precomputed-hash", "selectors", "tendril", ] [[package]] name = "sea-bae" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ "heck 0.4.1", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "sea-orm" version = "2.0.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "296a092e16fbbcc3a9969eae3915ef11024b98a43f3b77ff5199bc626c462d7a" dependencies = [ "async-stream", "async-trait", "bigdecimal", "chrono", "derive_more 2.0.1", "futures-util", "itertools", "log", "ouroboros", "pgvector", "rust_decimal", "sea-orm-macros", "sea-query", "sea-query-sqlx", "sea-schema", "serde", "serde_json", "sqlx", "strum", "thiserror 2.0.17", "time", "tracing", "url", "uuid", ] [[package]] name = "sea-orm-cli" version = "2.0.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301f7ace977d940474b47a154a5fc453714d13e5d53bdb077330c6fc6212a31b" dependencies = [ "chrono", "clap", "dotenvy", "glob", "indoc", "regex", "sea-schema", "sqlx", "tokio", "tracing", "tracing-subscriber", "url", ] [[package]] name = "sea-orm-macros" version = "2.0.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e56ee8be52d15a801dc62a5a30d83dc718762401f7c7945b7f2730ef385b8b3d" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "sea-bae", "syn 2.0.106", "unicode-ident", ] [[package]] name = "sea-orm-migration" version = "2.0.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7444940e5b7db32f55dea6d04cfb312480e45ae07753b1391311eccd5753475" dependencies = [ "async-trait", "clap", "dotenvy", "sea-orm", "sea-orm-cli", "sea-schema", "tracing", "tracing-subscriber", ] [[package]] name = "sea-query" version = "1.0.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d190945ff6b2914ef0631c1933ef67ee142b15f00b72d40bf241147d24b522" dependencies = [ "bigdecimal", "chrono", "inherent", "ordered-float", "rust_decimal", "sea-query-derive", "serde_json", "time", "uuid", ] [[package]] name = "sea-query-derive" version = "1.0.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217e9422de35f26c16c5f671fce3c075a65e10322068dbc66078428634af6195" dependencies = [ "darling", "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.106", "thiserror 2.0.17", ] [[package]] name = "sea-query-sqlx" version = "0.8.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68873fa1776b4c25a26e7679f8ee22332978c721168ec1b0b32b6583d5a9381d" dependencies = [ "bigdecimal", "chrono", "rust_decimal", "sea-query", "serde_json", "sqlx", "time", "uuid", ] [[package]] name = "sea-schema" version = "0.17.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d00bab58be7fcc3a2dbc279099fcd5952d49e45d51444ebe8d5b7e7c4343df3" dependencies = [ "async-trait", "sea-query", "sea-query-sqlx", "sea-schema-derive", "sqlx", ] [[package]] name = "sea-schema-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "seahash" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "selectors" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ "bitflags 2.9.4", "cssparser", "derive_more 0.99.20", "fxhash", "log", "new_debug_unreachable", "phf", "phf_codegen", "precomputed-hash", "servo_arc", "smallvec", ] [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "serde_path_to_error" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", "serde_core", ] [[package]] name = "serde_regex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", "serde", ] [[package]] name = "serde_spanned" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "serde_variant" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a0068df419f9d9b6488fdded3f1c818522cdea328e02ce9d9f147380265a432" dependencies = [ "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "serial_test" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" dependencies = [ "dashmap 5.5.3", "futures", "lazy_static", "log", "parking_lot", "serial_test_derive", ] [[package]] name = "serial_test_derive" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "servo_arc" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4" dependencies = [ "stable_deref_trait", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shared_child" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ "libc", "sigchld", "windows-sys 0.60.2", ] [[package]] name = "shared_thread" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "sigchld" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" dependencies = [ "libc", "os_pipe", "signal-hook", ] [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core 0.6.4", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", "thiserror 2.0.17", "time", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slug" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ "deunicode", "wasm-bindgen", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "socket2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "sqlx" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", ] [[package]] name = "sqlx-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bigdecimal", "bytes", "chrono", "crc", "crossbeam-queue", "either", "event-listener", "futures-core", "futures-intrusive", "futures-io", "futures-util", "hashbrown 0.15.5", "hashlink", "indexmap", "log", "memchr", "once_cell", "percent-encoding", "rust_decimal", "rustls", "serde", "serde_json", "sha2", "smallvec", "thiserror 2.0.17", "time", "tokio", "tokio-stream", "tracing", "url", "uuid", "webpki-roots 0.26.11", ] [[package]] name = "sqlx-macros" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", "syn 2.0.106", ] [[package]] name = "sqlx-macros-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", "heck 0.5.0", "hex", "once_cell", "proc-macro2", "quote", "serde", "serde_json", "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.106", "tokio", "url", ] [[package]] name = "sqlx-mysql" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", "bigdecimal", "bitflags 2.9.4", "byteorder", "bytes", "chrono", "crc", "digest", "dotenvy", "either", "futures-channel", "futures-core", "futures-io", "futures-util", "generic-array", "hex", "hkdf", "hmac", "itoa", "log", "md-5", "memchr", "once_cell", "percent-encoding", "rand 0.8.5", "rsa", "rust_decimal", "serde", "sha1", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.17", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-postgres" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", "bigdecimal", "bitflags 2.9.4", "byteorder", "chrono", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", "futures-util", "hex", "hkdf", "hmac", "home", "itoa", "log", "md-5", "memchr", "num-bigint", "once_cell", "rand 0.8.5", "rust_decimal", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.17", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-sqlite" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", "flume", "futures-channel", "futures-core", "futures-executor", "futures-intrusive", "futures-util", "libsqlite3-sys", "log", "percent-encoding", "serde", "serde_urlencoded", "sqlx-core", "thiserror 2.0.17", "time", "tracing", "url", "uuid", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", "libc", "psm", "windows-sys 0.59.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", ] [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ "unicode-bidi", "unicode-normalization", "unicode-properties", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "tera" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ "chrono", "chrono-tz", "globwalk", "humansize", "lazy_static", "percent-encoding", "pest", "pest_derive", "rand 0.8.5", "regex", "serde", "serde_json", "slug", "unic-segment", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] [[package]] name = "time" version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", "slab", "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] [[package]] name = "tokio-cron-scheduler" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2594dd7c2abbbafbb1c78d167fd10860dc7bd75f814cb051a1e0d3e796b9702" dependencies = [ "chrono", "cron", "num-derive", "num-traits", "tokio", "tracing", "uuid", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-stream" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_datetime" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime 0.6.11", "toml_write", "winnow", ] [[package]] name = "toml_edit" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", "toml_datetime 0.7.2", "toml_parser", "winnow", ] [[package]] name = "toml_parser" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] [[package]] name = "toml_write" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-http" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", "bitflags 2.9.4", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "http-range-header", "httpdate", "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-appender" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "thiserror 1.0.69", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-serde" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex-automata", "serde", "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", "tracing-serde", ] [[package]] name = "tree-fs" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6115680fa5fdb99b4ff19c9c3217e75116d2bb0eae82458c4e1818be6a10c7" dependencies = [ "rand 0.9.2", "serde", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "ulid" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" dependencies = [ "rand 0.9.2", "web-time", ] [[package]] name = "unic-char-property" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" dependencies = [ "unic-char-range", ] [[package]] name = "unic-char-range" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" [[package]] name = "unic-common" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" [[package]] name = "unic-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" dependencies = [ "unic-ucd-segment", ] [[package]] name = "unic-ucd-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" dependencies = [ "unic-char-property", "unic-char-range", "unic-ucd-version", ] [[package]] name = "unic-ucd-version" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" dependencies = [ "unic-common", ] [[package]] name = "unicase" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", "rand 0.9.2", "serde", "wasm-bindgen", ] [[package]] name = "validator" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ "idna", "once_cell", "regex", "serde", "serde_derive", "serde_json", "url", "validator_derive", ] [[package]] name = "validator_derive" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" dependencies = [ "darling", "once_cell", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-streams" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "web-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "web-time" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ "webpki-roots 1.0.2", ] [[package]] name = "webpki-roots" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ "libredox", "wasite", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link 0.2.1", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.5", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "zstd" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", ] ================================================ FILE: examples/loco_starter/Cargo.toml ================================================ [workspace] [package] edition = "2024" name = "loco_starter" rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] loco-rs = { version = "0.16" } migration = { path = "migration" } async-trait = "0.1.74" axum = { version = "0.8", features = ["multipart"] } chrono = "0.4" eyre = "0.6" include_dir = "0.7" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1.33.0", default-features = false } tokio-util = "0.7.11" tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] } uuid = { version = "1.6.0", features = ["v4"] } validator = { version = "0.20" } [dependencies.sea-orm] features = [ "sqlx-sqlite", "sqlx-postgres", "runtime-tokio-rustls", "macros", ] version = "~2.0.0-rc.37" # sea-orm version [[bin]] name = "loco_starter-cli" path = "src/bin/main.rs" required-features = [] [dev-dependencies] insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } loco-rs = { version = "0.16", features = ["testing"] } rstest = "0.18.2" serial_test = "2.0.0" [patch.crates-io] loco-rs = { git = "https://github.com/SeaQL/loco", branch = "master" } ================================================ FILE: examples/loco_starter/README.md ================================================ # Getting Started with Loco & SeaORM This is an REST notepad backend based on the Loco starter template, with addition of a new REST endpoint to handle file uploads. Read The full tutorial [here](https://www.sea-ql.org/blog/2024-05-28-getting-started-with-loco-seaorm/). The documentation of the REST API is available [here](https://documenter.getpostman.com/view/34752358/2sA3QmEF5q). ![Screenshot App](Screenshot-App.png) ![Screenshot API](Screenshot-API.png) ================================================ FILE: examples/loco_starter/config/development.yaml ================================================ # Loco configuration file documentation # Application logging configuration logger: # Enable or disable logging. enable: true # Enable pretty backtrace (sets RUST_BACKTRACE=1) pretty_backtrace: true # Log level, options: trace, debug, info, warn or error. level: debug # Define the logging format. options: compact, pretty or Json format: compact # By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries # Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters. # override_filter: trace # Web server configuration server: # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} port: 3000 # The UI hostname or IP address that mailers will point to. host: http://localhost # Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block middlewares: # Enable Etag cache header middleware etag: enable: true # Allows to limit the payload size request. payload that bigger than this file will blocked the request. limit_payload: # Enable/Disable the middleware. enable: true # the limit size. can be b,kb,kib,mb,mib,gb,gib body_limit: 5mb # Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details. logger: # Enable/Disable the middleware. enable: true # when your code is panicked, the request still returns 500 status code. catch_panic: # Enable/Disable the middleware. enable: true # Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned. timeout_request: # Enable/Disable the middleware. enable: false # Duration time in milliseconds. timeout: 5000 cors: enable: true # Set the value of the [`Access-Control-Allow-Origin`][mdn] header # allow_origins: # - https://loco.rs # Set the value of the [`Access-Control-Allow-Headers`][mdn] header # allow_headers: # - Content-Type # Set the value of the [`Access-Control-Allow-Methods`][mdn] header # allow_methods: # - POST # Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds # max_age: 3600 # Worker Configuration workers: # specifies the worker mode. Options: # - BackgroundQueue - Workers operate asynchronously in the background, processing queued. # - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed. # - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities. mode: BackgroundQueue # Mailer Configuration. mailer: # SMTP mailer configuration. smtp: # Enable/Disable smtp mailer. enable: true # SMTP server host. e.x localhost, smtp.gmail.com host: {{ get_env(name="MAILER_HOST", default="localhost") }} # SMTP server port port: 1025 # Use secure connection (SSL/TLS). secure: false # auth: # user: # password: # Initializers Configuration # initializers: # oauth2: # authorization_code: # Authorization code grant type # - client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config. # ... other fields # Database Configuration database: # Database connection URI uri: {{ get_env(name="DATABASE_URL", default="postgres://loco:loco@localhost:5432/loco_starter_development") }} # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. connect_timeout: 500 # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. min_connections: 1 # Maximum number of connections for a pool. max_connections: 1 # Run migration up when application loaded auto_migrate: true # Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_truncate: false # Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_recreate: false # Redis Configuration redis: # Redis connection URI uri: {{ get_env(name="REDIS_URL", default="redis://127.0.0.1") }} # Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_flush: false # Authentication Configuration auth: # JWT authentication jwt: # Secret key for token generation and verification secret: pByQUgg4GmXKAqQQvAGo # Token expiration time in seconds expiration: 604800 # 7 days ================================================ FILE: examples/loco_starter/examples/playground.rs ================================================ use eyre::Context; #[allow(unused_imports)] use loco_rs::{cli::playground, prelude::*}; use loco_starter::app::App; #[tokio::main] async fn main() -> eyre::Result<()> { let _ctx = playground::().await.context("playground")?; // let active_model: articles::ActiveModel = ActiveModel { // title: Set(Some("how to build apps in 3 steps".to_string())), // content: Set(Some("use Loco: https://loco.rs".to_string())), // ..Default::default() // }; // active_model.insert(&ctx.db).await.unwrap(); // let res = articles::Entity::find().all(&ctx.db).await.unwrap(); // println!("{:?}", res); println!("welcome to playground. edit me at `examples/playground.rs`"); Ok(()) } ================================================ FILE: examples/loco_starter/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] loco-rs = { version = "0.16" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. # e.g. "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature ] version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/loco_starter/migration/README.md ================================================ # Running Migrator CLI - Generate a new migration file ```sh cargo run -- migrate generate MIGRATION_NAME ``` - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/loco_starter/migration/src/lib.rs ================================================ #![allow(elided_lifetimes_in_paths)] #![allow(clippy::wildcard_imports)] pub use sea_orm_migration::prelude::*; mod m20220101_000001_users; mod m20231103_114510_notes; mod m20240520_173001_files; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220101_000001_users::Migration), Box::new(m20231103_114510_notes::Migration), Box::new(m20240520_173001_files::Migration), ] } } ================================================ FILE: examples/loco_starter/migration/src/m20220101_000001_users.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let table = table_auto("users") .col(pk_auto("id")) .col(uuid("pid")) .col(string_uniq("email")) .col(string("password")) .col(string("api_key").unique_key()) .col(string("name")) .col(string_null("reset_token")) .col(timestamp_null("reset_sent_at")) .col(string_null("email_verification_token")) .col(timestamp_null("email_verification_sent_at")) .col(timestamp_null("email_verified_at")) .to_owned(); manager.create_table(table).await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("users").to_owned()) .await } } ================================================ FILE: examples/loco_starter/migration/src/m20231103_114510_notes.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto("notes") .col(pk_auto("id")) .col(string_null("title")) .col(string_null("content")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("notes").to_owned()) .await } } ================================================ FILE: examples/loco_starter/migration/src/m20240520_173001_files.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto("files") .col(pk_auto("id")) .col(integer("notes_id")) .col(string("file_path")) .foreign_key( ForeignKey::create() .name("FK_files_notes_id") .from("files", "notes_id") .to("notes", "id"), ) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("files").to_owned()) .await } } ================================================ FILE: examples/loco_starter/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/loco_starter/src/app.rs ================================================ use std::path::Path; use async_trait::async_trait; use loco_rs::{ Result, app::{AppContext, Hooks}, bgworker::{BackgroundWorker, Queue}, boot::{BootResult, StartMode, create_app}, config::Config, controller::AppRoutes, db::{self, truncate_table}, environment::Environment, task::Tasks, }; use migration::Migrator; use crate::{ controllers, models::_entities::{notes, users}, tasks, workers::downloader::DownloadWorker, }; pub struct App; #[async_trait] impl Hooks for App { fn app_name() -> &'static str { env!("CARGO_CRATE_NAME") } fn app_version() -> String { format!( "{} ({})", env!("CARGO_PKG_VERSION"), option_env!("BUILD_SHA") .or(option_env!("GITHUB_SHA")) .unwrap_or("dev") ) } async fn boot( mode: StartMode, environment: &Environment, config: Config, ) -> Result { create_app::(mode, environment, config).await } fn routes(_ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() .prefix("/api") .add_route(controllers::notes::routes()) .add_route(controllers::auth::routes()) .add_route(controllers::user::routes()) .add_route(controllers::files::routes()) } async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> { queue.register(DownloadWorker::build(ctx)).await?; Ok(()) } fn register_tasks(tasks: &mut Tasks) { tasks.register(tasks::seed::SeedData); } async fn truncate(ctx: &AppContext) -> Result<()> { let db = &ctx.db; truncate_table(db, users::Entity).await?; truncate_table(db, notes::Entity).await?; Ok(()) } async fn seed(ctx: &AppContext, base: &Path) -> Result<()> { let db = &ctx.db; db::seed::(db, &base.join("users.yaml").display().to_string()).await?; db::seed::(db, &base.join("notes.yaml").display().to_string()).await?; Ok(()) } } ================================================ FILE: examples/loco_starter/src/bin/main.rs ================================================ use loco_rs::cli; use loco_starter::app::App; use migration::Migrator; #[tokio::main] async fn main() -> eyre::Result<()> { cli::main::().await?; Ok(()) } ================================================ FILE: examples/loco_starter/src/controllers/auth.rs ================================================ use axum::debug_handler; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::{ mailers::auth::AuthMailer, models::{ _entities::users, users::{LoginParams, RegisterParams}, }, views::auth::LoginResponse, }; #[derive(Debug, Deserialize, Serialize)] pub struct VerifyParams { pub token: String, } #[derive(Debug, Deserialize, Serialize)] pub struct ForgotParams { pub email: String, } #[derive(Debug, Deserialize, Serialize)] pub struct ResetParams { pub token: String, pub password: String, } /// Register function creates a new user with the given parameters and sends a /// welcome email to the user #[debug_handler] async fn register( State(ctx): State, Json(params): Json, ) -> Result { let res = users::Model::create_with_password(&ctx.db, ¶ms).await; let user = match res { Ok(user) => user, Err(err) => { tracing::info!( message = err.to_string(), user_email = ¶ms.email, "could not register user", ); return format::json(()); } }; // Skip email verification, all new registrations are considered verified let _user = user.into_active_model().verified(&ctx.db).await?; // Skip sending verification email as we don't have a mail server /* let user = user .into_active_model() .set_email_verification_sent(&ctx.db) .await?; AuthMailer::send_welcome(&ctx, &user).await?; */ format::json(()) } /// Verify register user. if the user not verified his email, he can't login to /// the system. #[debug_handler] async fn verify( State(ctx): State, Json(params): Json, ) -> Result { let user = users::Model::find_by_verification_token(&ctx.db, ¶ms.token).await?; if user.email_verified_at.is_some() { tracing::info!(pid = user.pid.to_string(), "user already verified"); } else { let active_model = user.into_active_model(); let user = active_model.verified(&ctx.db).await?; tracing::info!(pid = user.pid.to_string(), "user verified"); } format::json(()) } /// In case the user forgot his password this endpoints generate a forgot token /// and send email to the user. In case the email not found in our DB, we are /// returning a valid request for for security reasons (not exposing users DB /// list). #[debug_handler] async fn forgot( State(ctx): State, Json(params): Json, ) -> Result { let Ok(user) = users::Model::find_by_email(&ctx.db, ¶ms.email).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller return format::json(()); }; let user = user .into_active_model() .set_forgot_password_sent(&ctx.db) .await?; AuthMailer::forgot_password(&ctx, &user).await?; format::json(()) } /// reset user password by the given parameters #[debug_handler] async fn reset(State(ctx): State, Json(params): Json) -> Result { let Ok(user) = users::Model::find_by_reset_token(&ctx.db, ¶ms.token).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller tracing::info!("reset token not found"); return format::json(()); }; user.into_active_model() .reset_password(&ctx.db, ¶ms.password) .await?; format::json(()) } /// Creates a user login and returns a token #[debug_handler] async fn login(State(ctx): State, Json(params): Json) -> Result { let user = users::Model::find_by_email(&ctx.db, ¶ms.email).await?; let valid = user.verify_password(¶ms.password); if !valid { return unauthorized("unauthorized!"); } let jwt_secret = ctx.config.get_jwt_config()?; let token = user .generate_jwt(&jwt_secret.secret, &jwt_secret.expiration) .or_else(|_| unauthorized("unauthorized!"))?; format::json(LoginResponse::new(&user, &token)) } pub fn routes() -> Routes { Routes::new() .prefix("auth") .add("/register", post(register)) .add("/verify", post(verify)) .add("/login", post(login)) .add("/forgot", post(forgot)) .add("/reset", post(reset)) } ================================================ FILE: examples/loco_starter/src/controllers/files.rs ================================================ #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use std::path::PathBuf; use axum::{body::Body, debug_handler, extract::Multipart}; use loco_rs::prelude::*; use sea_orm::QueryOrder; use tokio::{fs, io::AsyncWriteExt}; use tokio_util::io::ReaderStream; use crate::models::_entities::files; const UPLOAD_DIR: &str = "./uploads"; #[debug_handler] pub async fn upload( _auth: auth::JWT, Path(notes_id): Path, State(ctx): State, mut multipart: Multipart, ) -> Result { // Collect all uploaded files let mut files = Vec::new(); // Iterate all files in the POST body while let Some(field) = multipart.next_field().await.map_err(|err| { tracing::error!(error = ?err,"could not readd multipart"); Error::BadRequest("could not readd multipart".into()) })? { // Get the file name let file_name = match field.file_name() { Some(file_name) => file_name.to_string(), _ => return Err(Error::BadRequest("file name not found".into())), }; // Get the file content as bytes let content = field.bytes().await.map_err(|err| { tracing::error!(error = ?err,"could not readd bytes"); Error::BadRequest("could not readd bytes".into()) })?; // Create a folder to store the uploaded file let now = chrono::offset::Local::now() .format("%Y%m%d_%H%M%S") .to_string(); let uuid = uuid::Uuid::new_v4().to_string(); let folder = format!("{now}_{uuid}"); let upload_folder = PathBuf::from(UPLOAD_DIR).join(&folder); fs::create_dir_all(&upload_folder).await?; // Write the file into the newly created folder let path = upload_folder.join(file_name); let mut f = fs::OpenOptions::new() .create_new(true) .write(true) .open(&path) .await?; f.write_all(&content).await?; f.flush().await?; // Record the file upload in database let file = files::ActiveModel { notes_id: ActiveValue::Set(notes_id), file_path: ActiveValue::Set( path.strip_prefix(UPLOAD_DIR) .unwrap() .to_str() .unwrap() .to_string(), ), ..Default::default() } .insert(&ctx.db) .await?; files.push(file); } format::json(files) } #[debug_handler] pub async fn list( _auth: auth::JWT, Path(notes_id): Path, State(ctx): State, ) -> Result { // Fetch all files uploaded for a specific notes let files = files::Entity::find() .filter(files::Column::NotesId.eq(notes_id)) .order_by_asc(files::Column::Id) .all(&ctx.db) .await?; format::json(files) } #[debug_handler] pub async fn view( _auth: auth::JWT, Path(files_id): Path, State(ctx): State, ) -> Result { // Fetch the file info from database let file = files::Entity::find_by_id(files_id) .one(&ctx.db) .await? .expect("File not found"); // Stream the file let file = fs::File::open(format!("{UPLOAD_DIR}/{}", file.file_path)).await?; let stream = ReaderStream::new(file); let body = Body::from_stream(stream); Ok(format::render().response().body(body)?) } pub fn routes() -> Routes { // Bind the routes Routes::new() .prefix("files") .add("/upload/{notes_id}", post(upload)) .add("/list/{notes_id}", get(list)) .add("/view/{files_id}", get(view)) } ================================================ FILE: examples/loco_starter/src/controllers/mod.rs ================================================ pub mod auth; pub mod files; pub mod notes; pub mod user; ================================================ FILE: examples/loco_starter/src/controllers/notes.rs ================================================ #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use axum::debug_handler; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::models::_entities::notes::{ActiveModel, Entity, Model}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Params { pub title: Option, pub content: Option, } impl Params { fn update(&self, item: &mut ActiveModel) { item.title = Set(self.title.clone()); item.content = Set(self.content.clone()); } } async fn load_item(ctx: &AppContext, id: i32) -> Result { let item = Entity::find_by_id(id).one(&ctx.db).await?; item.ok_or_else(|| Error::NotFound) } #[debug_handler] pub async fn list(State(ctx): State) -> Result { format::json(Entity::find().all(&ctx.db).await?) } #[debug_handler] pub async fn add(State(ctx): State, Json(params): Json) -> Result { let mut item = ActiveModel { ..Default::default() }; params.update(&mut item); let item = item.insert(&ctx.db).await?; format::json(item) } #[debug_handler] pub async fn update( Path(id): Path, State(ctx): State, Json(params): Json, ) -> Result { let item = load_item(&ctx, id).await?; let mut item = item.into_active_model(); params.update(&mut item); let item = item.update(&ctx.db).await?; format::json(item) } #[debug_handler] pub async fn remove(Path(id): Path, State(ctx): State) -> Result { load_item(&ctx, id).await?.delete(&ctx.db).await?; format::empty() } #[debug_handler] pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { format::json(load_item(&ctx, id).await?) } pub fn routes() -> Routes { Routes::new() .prefix("notes") .add("/", get(list)) .add("/", post(add)) .add("/{id}", get(get_one)) .add("/{id}", delete(remove)) .add("/{id}", post(update)) } ================================================ FILE: examples/loco_starter/src/controllers/user.rs ================================================ use axum::debug_handler; use loco_rs::prelude::*; use crate::{models::_entities::users, views::user::CurrentResponse}; #[debug_handler] async fn current(auth: auth::JWT, State(ctx): State) -> Result { let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; format::json(CurrentResponse::new(&user)) } pub fn routes() -> Routes { Routes::new().prefix("user").add("/current", get(current)) } ================================================ FILE: examples/loco_starter/src/fixtures/notes.yaml ================================================ --- - id: 1 title: Loco note 1 content: Loco note 1 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" - id: 2 title: Loco note 2 content: Loco note 2 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" ================================================ FILE: examples/loco_starter/src/fixtures/users.yaml ================================================ --- - id: 1 pid: 11111111-1111-1111-1111-111111111111 email: user1@example.com password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" api_key: lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758 name: user1 created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" - id: 2 pid: 22222222-2222-2222-2222-222222222222 email: user2@example.com password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" api_key: lo-153561ca-fa84-4e1b-813a-c62526d0a77e name: user2 created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" ================================================ FILE: examples/loco_starter/src/lib.rs ================================================ pub mod app; pub mod controllers; pub mod mailers; pub mod models; pub mod tasks; pub mod views; pub mod workers; ================================================ FILE: examples/loco_starter/src/mailers/auth/forgot/html.t ================================================ ; Hey {{name}}, Forgot your password? No worries! You can reset it by clicking the link below: Reset Your Password If you didn't request a password reset, please ignore this email. Best regards,
The Loco Team
================================================ FILE: examples/loco_starter/src/mailers/auth/forgot/subject.t ================================================ Your reset password link ================================================ FILE: examples/loco_starter/src/mailers/auth/forgot/text.t ================================================ Reset your password with this link: http://localhost/reset#{{resetToken}} ================================================ FILE: examples/loco_starter/src/mailers/auth/welcome/html.t ================================================ ; Dear {{name}}, Welcome to Loco! You can now log in to your account. Before you get started, please verify your account by clicking the link below: Verify Your Account

Best regards,
The Loco Team

================================================ FILE: examples/loco_starter/src/mailers/auth/welcome/subject.t ================================================ Welcome {{name}} ================================================ FILE: examples/loco_starter/src/mailers/auth/welcome/text.t ================================================ Welcome {{name}}, you can now log in. Verify your account with the link below: http://localhost/verify#{{verifyToken}} ================================================ FILE: examples/loco_starter/src/mailers/auth.rs ================================================ // auth mailer #![allow(non_upper_case_globals)] use loco_rs::prelude::*; use serde_json::json; use crate::models::users; static welcome: Dir<'_> = include_dir!("src/mailers/auth/welcome"); static forgot: Dir<'_> = include_dir!("src/mailers/auth/forgot"); // #[derive(Mailer)] // -- disabled for faster build speed. it works. but lets // move on for now. #[allow(clippy::module_name_repetitions)] pub struct AuthMailer {} impl Mailer for AuthMailer {} impl AuthMailer { /// Sending welcome email the the given user /// /// # Errors /// /// When email sending is failed pub async fn send_welcome(ctx: &AppContext, user: &users::Model) -> Result<()> { Self::mail_template( ctx, &welcome, mailer::Args { to: user.email.to_string(), locals: json!({ "name": user.name, "verifyToken": user.email_verification_token, "domain": ctx.config.server.full_url() }), ..Default::default() }, ) .await?; Ok(()) } /// Sending forgot password email /// /// # Errors /// /// When email sending is failed pub async fn forgot_password(ctx: &AppContext, user: &users::Model) -> Result<()> { Self::mail_template( ctx, &forgot, mailer::Args { to: user.email.to_string(), locals: json!({ "name": user.name, "resetToken": user.reset_token, "domain": ctx.config.server.full_url() }), ..Default::default() }, ) .await?; Ok(()) } } ================================================ FILE: examples/loco_starter/src/mailers/mod.rs ================================================ pub mod auth; ================================================ FILE: examples/loco_starter/src/models/_entities/files.rs ================================================ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "files")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub notes_id: i32, pub file_path: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::notes::Entity", from = "Column::NotesId", to = "super::notes::Column::Id" )] Notes, } impl Related for Entity { fn to() -> RelationDef { Relation::Notes.def() } } ================================================ FILE: examples/loco_starter/src/models/_entities/mod.rs ================================================ pub mod prelude; pub mod files; pub mod notes; pub mod users; ================================================ FILE: examples/loco_starter/src/models/_entities/notes.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.10 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "notes")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub title: Option, pub content: Option, } ================================================ FILE: examples/loco_starter/src/models/_entities/prelude.rs ================================================ pub use super::{notes::Entity as Notes, users::Entity as Users}; ================================================ FILE: examples/loco_starter/src/models/_entities/users.rs ================================================ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "users")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, #[sea_orm(unique)] pub pid: Uuid, #[sea_orm(unique)] pub email: String, pub password: String, #[sea_orm(unique)] pub api_key: String, pub name: String, pub reset_token: Option, pub reset_sent_at: Option, pub email_verification_token: Option, pub email_verification_sent_at: Option, pub email_verified_at: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} ================================================ FILE: examples/loco_starter/src/models/files.rs ================================================ use sea_orm::entity::prelude::*; use super::_entities::files::ActiveModel; impl ActiveModelBehavior for ActiveModel { // extend activemodel below (keep comment for generators) } ================================================ FILE: examples/loco_starter/src/models/mod.rs ================================================ pub mod _entities; pub mod files; pub mod notes; pub mod users; ================================================ FILE: examples/loco_starter/src/models/notes.rs ================================================ use sea_orm::entity::prelude::*; use super::_entities::notes::ActiveModel; impl ActiveModelBehavior for ActiveModel { // extend activemodel below (keep comment for generators) } ================================================ FILE: examples/loco_starter/src/models/users.rs ================================================ use async_trait::async_trait; use chrono::offset::Local; use loco_rs::{auth::jwt, hash, prelude::*}; use serde::{Deserialize, Serialize}; use uuid::Uuid; pub use super::_entities::users::{self, ActiveModel, Entity, Model}; #[derive(Debug, Deserialize, Serialize)] pub struct LoginParams { pub email: String, pub password: String, } #[derive(Debug, Deserialize, Serialize)] pub struct RegisterParams { pub email: String, pub password: String, pub name: String, } #[derive(Debug, Validate, Deserialize)] pub struct Validator { #[validate(length(min = 2, message = "Name must be at least 2 characters long."))] pub name: String, #[validate(email(message = "invalid email"))] pub email: String, } impl Validatable for super::_entities::users::ActiveModel { fn validator(&self) -> Box { Box::new(Validator { name: self.name.as_ref().to_owned(), email: self.email.as_ref().to_owned(), }) } } #[async_trait::async_trait] impl ActiveModelBehavior for super::_entities::users::ActiveModel { async fn before_save(self, _db: &C, insert: bool) -> Result where C: ConnectionTrait, { self.validate()?; if insert { let mut this = self; this.pid = ActiveValue::Set(Uuid::new_v4()); this.api_key = ActiveValue::Set(format!("lo-{}", Uuid::new_v4())); Ok(this) } else { Ok(self) } } } #[async_trait] impl Authenticable for super::_entities::users::Model { async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ApiKey.eq(api_key)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult { Self::find_by_pid(db, claims_key).await } } impl super::_entities::users::Model { /// finds a user by the provided email /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::Email.eq(email)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided verification token /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_verification_token( db: &DatabaseConnection, token: &str, ) -> ModelResult { let user = users::Entity::find() .filter(users::Column::EmailVerificationToken.eq(token)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// /// finds a user by the provided reset token /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_reset_token(db: &DatabaseConnection, token: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ResetToken.eq(token)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided pid /// /// # Errors /// /// When could not find user or DB query error pub async fn find_by_pid(db: &DatabaseConnection, pid: &str) -> ModelResult { let parse_uuid = Uuid::parse_str(pid).map_err(|e| ModelError::Any(e.into()))?; let user = users::Entity::find() .filter(users::Column::Pid.eq(parse_uuid)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided api key /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ApiKey.eq(api_key)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// Verifies whether the provided plain password matches the hashed password /// /// # Errors /// /// when could not verify password #[must_use] pub fn verify_password(&self, password: &str) -> bool { hash::verify_password(password, &self.password) } /// Asynchronously creates a user with a password and saves it to the /// database. /// /// # Errors /// /// When could not save the user into the DB pub async fn create_with_password( db: &DatabaseConnection, params: &RegisterParams, ) -> ModelResult { let txn = db.begin().await?; if users::Entity::find() .filter(users::Column::Email.eq(¶ms.email)) .one(&txn) .await? .is_some() { return Err(ModelError::EntityAlreadyExists {}); } let password_hash = hash::hash_password(¶ms.password).map_err(|e| ModelError::Any(e.into()))?; let user = users::ActiveModel { email: ActiveValue::set(params.email.to_string()), password: ActiveValue::set(password_hash), name: ActiveValue::set(params.name.to_string()), ..Default::default() } .insert(&txn) .await?; txn.commit().await?; Ok(user) } /// Creates a JWT /// /// # Errors /// /// when could not convert user claims to jwt token pub fn generate_jwt(&self, secret: &str, expiration: &u64) -> ModelResult { Ok(jwt::JWT::new(secret).generate_token( *expiration, self.pid.to_string(), Default::default(), )?) } } impl super::_entities::users::ActiveModel { /// Sets the email verification information for the user and /// updates it in the database. /// /// This method is used to record the timestamp when the email verification /// was sent and generate a unique verification token for the user. /// /// # Errors /// /// when has DB query error pub async fn set_email_verification_sent( mut self, db: &DatabaseConnection, ) -> ModelResult { self.email_verification_sent_at = ActiveValue::set(Some(Local::now().naive_local())); self.email_verification_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); Ok(self.update(db).await?) } /// Sets the information for a reset password request, /// generates a unique reset password token, and updates it in the /// database. /// /// This method records the timestamp when the reset password token is sent /// and generates a unique token for the user. /// /// # Arguments /// /// # Errors /// /// when has DB query error pub async fn set_forgot_password_sent(mut self, db: &DatabaseConnection) -> ModelResult { self.reset_sent_at = ActiveValue::set(Some(Local::now().naive_local())); self.reset_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); Ok(self.update(db).await?) } /// Records the verification time when a user verifies their /// email and updates it in the database. /// /// This method sets the timestamp when the user successfully verifies their /// email. /// /// # Errors /// /// when has DB query error pub async fn verified(mut self, db: &DatabaseConnection) -> ModelResult { self.email_verified_at = ActiveValue::set(Some(Local::now().naive_local())); Ok(self.update(db).await?) } /// Resets the current user password with a new password and /// updates it in the database. /// /// This method hashes the provided password and sets it as the new password /// for the user. /// # Errors /// /// when has DB query error or could not hashed the given password pub async fn reset_password( mut self, db: &DatabaseConnection, password: &str, ) -> ModelResult { self.password = ActiveValue::set(hash::hash_password(password).map_err(|e| ModelError::Any(e.into()))?); Ok(self.update(db).await?) } } ================================================ FILE: examples/loco_starter/src/tasks/mod.rs ================================================ pub mod seed; ================================================ FILE: examples/loco_starter/src/tasks/seed.rs ================================================ //! This task implements data seeding functionality for initializing new //! development/demo environments. //! //! # Example //! //! Run the task with the following command: //! ```sh //! cargo run task //! ``` //! //! To override existing data and reset the data structure, use the following //! command with the `refresh:true` argument: //! ```sh //! cargo run task seed_data refresh:true //! ``` use loco_rs::{db, prelude::*}; use migration::Migrator; use crate::app::App; #[allow(clippy::module_name_repetitions)] pub struct SeedData; #[async_trait] impl Task for SeedData { fn task(&self) -> TaskInfo { TaskInfo { name: "seed_data".to_string(), detail: "Task for seeding data".to_string(), } } async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> { let refresh = vars .cli_arg("refresh") .is_ok_and(|refresh| refresh == "true"); if refresh { db::reset::(&app_context.db).await?; } let path = std::path::Path::new("src/fixtures"); db::run_app_seed::(app_context, path).await?; Ok(()) } } ================================================ FILE: examples/loco_starter/src/views/auth.rs ================================================ use serde::{Deserialize, Serialize}; use crate::models::_entities::users; #[derive(Debug, Deserialize, Serialize)] pub struct LoginResponse { pub token: String, pub pid: String, pub name: String, pub is_verified: bool, } impl LoginResponse { #[must_use] pub fn new(user: &users::Model, token: &String) -> Self { Self { token: token.to_string(), pid: user.pid.to_string(), name: user.name.clone(), is_verified: user.email_verified_at.is_some(), } } } ================================================ FILE: examples/loco_starter/src/views/mod.rs ================================================ pub mod auth; pub mod user; ================================================ FILE: examples/loco_starter/src/views/user.rs ================================================ use serde::{Deserialize, Serialize}; use crate::models::_entities::users; #[derive(Debug, Deserialize, Serialize)] pub struct CurrentResponse { pub pid: String, pub name: String, pub email: String, } impl CurrentResponse { #[must_use] pub fn new(user: &users::Model) -> Self { Self { pid: user.pid.to_string(), name: user.name.clone(), email: user.email.clone(), } } } ================================================ FILE: examples/loco_starter/src/workers/downloader.rs ================================================ use std::time::Duration; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use tokio::time::sleep; use crate::models::users; pub struct DownloadWorker { pub ctx: AppContext, } #[derive(Deserialize, Debug, Serialize)] pub struct DownloadWorkerArgs { pub user_guid: String, } #[async_trait] impl BackgroundWorker for DownloadWorker { fn build(ctx: &AppContext) -> Self { Self { ctx: ctx.clone() } } async fn perform(&self, args: DownloadWorkerArgs) -> Result<()> { // TODO: Some actual work goes here... println!("================================================"); println!("Sending payment report to user {}", args.user_guid); sleep(Duration::from_millis(2000)).await; let all = users::Entity::find() .all(&self.ctx.db) .await .map_err(Box::from)?; for user in &all { println!("user: {}", user.id); } println!("================================================"); Ok(()) } } ================================================ FILE: examples/loco_starter/src/workers/mod.rs ================================================ pub mod downloader; ================================================ FILE: examples/parquet_example/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-parquet-example" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] env_logger = { version = "0.11" } fastrand = "2" log = { version = "0.4" } parquet = { version = "58", default-features = false, features = ["arrow"] } tokio = { version = "1", features = ["full"] } [dependencies.sea-orm] features = [ "sqlx-sqlite", "runtime-tokio", "with-arrow", "with-chrono", "with-rust_decimal", ] path = "../../" ================================================ FILE: examples/parquet_example/src/main.rs ================================================ use sea_orm::{ ArrowSchema, entity::*, prelude::{ChronoUtc, Decimal}, sea_query::prelude::chrono::Timelike, }; use log::info; mod measurement { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "measurement", arrow_schema)] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub recorded_at: ChronoDateTimeUtc, pub sensor_id: i32, pub temperature: f64, #[sea_orm(column_type = "Decimal(Some((10, 4)))")] pub voltage: Decimal, } impl ActiveModelBehavior for ActiveModel {} } #[tokio::main] async fn main() -> Result<(), Box> { let env = env_logger::Env::default().filter_or("RUST_LOG", "info,sea_orm=info,sqlx=warn"); env_logger::Builder::from_env(env).init(); // ----------------------------------------------------------------------- // Step 1: Generate 100 random rows // ----------------------------------------------------------------------- let base_ts = ChronoUtc::now(); let base_ts = base_ts .with_nanosecond((base_ts.nanosecond() / 1000) * 1000) .unwrap(); // truncate to microsecond let mut rng = fastrand::Rng::new(); let models: Vec = (1..=100) .map(|i| { let offset = std::time::Duration::from_secs(rng.u64(0..86_400)); let ts = base_ts + offset; let sensor_id = rng.i32(100..110); let temperature = -10.0 + rng.f64() * 50.0; // -10 .. 40 °C let voltage_raw = 30000 + rng.i64(0..5000); // 3.0000 .. 3.5000 measurement::ActiveModel { id: Set(i), recorded_at: Set(ts), sensor_id: Set(sensor_id), temperature: Set(temperature), voltage: Set(Decimal::new(voltage_raw, 4)), } }) .collect(); let schema = measurement::Entity::arrow_schema(); info!("Arrow schema: {schema:?}"); // ----------------------------------------------------------------------- // Step 2: Convert to Arrow RecordBatch and write to Parquet // ----------------------------------------------------------------------- let batch = measurement::ActiveModel::to_arrow(&models, &schema)?; info!( "RecordBatch: {} rows, {} columns", batch.num_rows(), batch.num_columns() ); let parquet_path = "measurements.parquet"; { let file = std::fs::File::create(parquet_path)?; let mut writer = parquet::arrow::ArrowWriter::try_new(file, schema.into(), None)?; writer.write(&batch)?; writer.close()?; } info!("Wrote Parquet file: {parquet_path}"); // ----------------------------------------------------------------------- // Step 3: Read the Parquet file back into a RecordBatch // ----------------------------------------------------------------------- let file = std::fs::File::open(parquet_path)?; let reader = parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder::try_new(file)?.build()?; let batches: Vec<_> = reader.collect::>()?; info!("Read {} batch(es) from Parquet", batches.len()); let read_batch = &batches[0]; assert_eq!(read_batch.num_rows(), 100); // Convert back to ActiveModels let restored = measurement::ActiveModel::from_arrow(read_batch)?; info!("Restored {} ActiveModels from Parquet", restored.len()); for (original, restored) in models.iter().zip(restored.iter()) { assert_eq!(original, restored, "Roundtrip mismatch"); } info!("Parquet roundtrip verified: all rows match."); // ----------------------------------------------------------------------- // Step 4: Dump into SQLite // ----------------------------------------------------------------------- match std::fs::remove_file("measurements.sqlite") { Ok(_) => (), Err(e) if e.kind() == std::io::ErrorKind::NotFound => (), Err(e) => panic!("Failed to remove file: {e}"), } let db = &sea_orm::Database::connect("sqlite://measurements.sqlite?mode=rwc").await?; db.get_schema_builder() .register(measurement::Entity) .apply(db) .await?; info!("SQLite schema created."); measurement::Entity::insert_many(restored).exec(db).await?; info!("Inserted all rows into SQLite."); info!("Done!"); Ok(()) } ================================================ FILE: examples/poem_example/Cargo.toml ================================================ [package] edition = "2024" name = "sea-orm-poem-example" rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] members = [".", "api", "entity", "migration"] [dependencies] poem-example-api = { path = "api" } ================================================ FILE: examples/poem_example/README.md ================================================ ![screenshot](Screenshot.png) # Poem with SeaORM example app 1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database 1. Turn on the appropriate database feature for your chosen db in `api/Cargo.toml` (the `"sqlx-sqlite",` line) 1. Execute `cargo run` to start the server 1. Visit [localhost:8000](http://localhost:8000) in browser after seeing the `server started` line Run tests: ```bash cd api cargo test ``` Run migration: ```bash cargo run -p migration -- up ``` Regenerate entity: ```bash sea-orm-cli generate entity --output-dir ./entity/src --lib --entity-format dense --with-serde both ``` ================================================ FILE: examples/poem_example/api/Cargo.toml ================================================ [package] edition = "2024" name = "poem-example-api" rust-version = "1.85.0" version = "0.1.0" [dependencies] dotenvy = "0.15" entity = { path = "../entity" } migration = { path = "../migration" } poem = { version = "1.3.56", features = ["static-files"] } serde = { version = "1", features = ["derive"] } tera = "1.19.0" tokio = { version = "1.29.0", features = ["macros", "rt-multi-thread"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-native-tls", # "sqlx-postgres", # "sqlx-mysql", "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [dev-dependencies] poem-example-api = { path = ".", features = ["sqlite"] } [features] sqlite = ["sea-orm/sqlx-sqlite"] ================================================ FILE: examples/poem_example/api/src/lib.rs ================================================ pub mod service; use std::env; use entity::post; use migration::{Migrator, MigratorTrait}; use poem::endpoint::StaticFilesEndpoint; use poem::error::InternalServerError; use poem::http::StatusCode; use poem::listener::TcpListener; use poem::web::{Data, Form, Html, Path, Query}; use poem::{EndpointExt, Error, IntoResponse, Result, Route, Server, get, handler, post}; use sea_orm::{Database, DatabaseConnection}; use serde::Deserialize; use service::{Mutation, Query as QueryService}; use tera::Tera; const DEFAULT_POSTS_PER_PAGE: u64 = 5; #[derive(Debug, Clone)] struct AppState { templates: tera::Tera, conn: DatabaseConnection, } #[derive(Deserialize)] struct Params { page: Option, posts_per_page: Option, } #[handler] async fn create(state: Data<&AppState>, form: Form) -> Result { let form = form.0; let conn = &state.conn; Mutation::create_post(conn, form) .await .map_err(InternalServerError)?; Ok(StatusCode::FOUND.with_header("location", "/")) } #[handler] async fn list(state: Data<&AppState>, Query(params): Query) -> Result { let conn = &state.conn; let page = params.page.unwrap_or(1); let posts_per_page = params.posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); let (posts, num_pages) = QueryService::find_posts_in_page(conn, page, posts_per_page) .await .map_err(InternalServerError)?; let mut ctx = tera::Context::new(); ctx.insert("posts", &posts); ctx.insert("page", &page); ctx.insert("posts_per_page", &posts_per_page); ctx.insert("num_pages", &num_pages); let body = state .templates .render("index.html.tera", &ctx) .map_err(InternalServerError)?; Ok(Html(body)) } #[handler] async fn new(state: Data<&AppState>) -> Result { let ctx = tera::Context::new(); let body = state .templates .render("new.html.tera", &ctx) .map_err(InternalServerError)?; Ok(Html(body)) } #[handler] async fn edit(state: Data<&AppState>, Path(id): Path) -> Result { let conn = &state.conn; let post: post::Model = QueryService::find_post_by_id(conn, id) .await .map_err(InternalServerError)? .ok_or_else(|| Error::from_status(StatusCode::NOT_FOUND))?; let mut ctx = tera::Context::new(); ctx.insert("post", &post); let body = state .templates .render("edit.html.tera", &ctx) .map_err(InternalServerError)?; Ok(Html(body)) } #[handler] async fn update( state: Data<&AppState>, Path(id): Path, form: Form, ) -> Result { let conn = &state.conn; let form = form.0; Mutation::update_post_by_id(conn, id, form) .await .map_err(InternalServerError)?; Ok(StatusCode::FOUND.with_header("location", "/")) } #[handler] async fn delete(state: Data<&AppState>, Path(id): Path) -> Result { let conn = &state.conn; Mutation::delete_post(conn, id) .await .map_err(InternalServerError)?; Ok(StatusCode::FOUND.with_header("location", "/")) } #[tokio::main] async fn start() -> std::io::Result<()> { unsafe { std::env::set_var("RUST_LOG", "debug"); } tracing_subscriber::fmt::init(); // get env vars dotenvy::dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let host = env::var("HOST").expect("HOST is not set in .env file"); let port = env::var("PORT").expect("PORT is not set in .env file"); let server_url = format!("{host}:{port}"); // create post table if not exists let conn = Database::connect(&db_url).await.unwrap(); Migrator::up(&conn, None).await.unwrap(); let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap(); let state = AppState { templates, conn }; println!("Starting server at {server_url}"); let app = Route::new() .at("/", post(create).get(list)) .at("/new", new) .at("/:id", get(edit).post(update)) .at("/delete/:id", post(delete)) .nest( "/static", StaticFilesEndpoint::new(concat!(env!("CARGO_MANIFEST_DIR"), "/static")), ) .data(state); let server = Server::new(TcpListener::bind(format!("{host}:{port}"))); server.run(app).await } pub fn main() { let result = start(); if let Some(err) = result.err() { println!("Error: {err}"); } } ================================================ FILE: examples/poem_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/poem_example/api/src/service/mutation.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/poem_example/api/src/service/query.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/poem_example/api/static/css/normalize.css ================================================ /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined for any HTML5 element in IE 8/9. * Correct `block` display not defined for `details` or `summary` in IE 10/11 * and Firefox. * Correct `block` display not defined for `main` in IE 11. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } /** * 1. Correct `inline-block` display not defined in IE 8/9. * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. */ audio, canvas, progress, video { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9/10. * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background-color: transparent; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* Text-level semantics ========================================================================== */ /** * Address styling not present in IE 8/9/10/11, Safari, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari and Chrome. */ dfn { font-style: italic; } /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9/10. */ img { border: 0; } /** * Correct overflow not hidden in IE 9/10/11. */ svg:not(:root) { overflow: hidden; } /* Grouping content ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari. */ figure { margin: 1em 40px; } /** * Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Contain overflow in all browsers. */ pre { overflow: auto; } /** * Address odd `em`-unit font size rendering in all browsers. */ code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } /* Forms ========================================================================== */ /** * Known limitation: by default, Chrome and Safari on OS X allow very limited * styling of `select`, unless a `border` property is set. */ /** * 1. Correct color not being inherited. * Known issue: affects color of disabled elements. * 2. Correct font properties not being inherited. * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. */ button, input, optgroup, select, textarea { color: inherit; /* 1 */ font: inherit; /* 2 */ margin: 0; /* 3 */ } /** * Address `overflow` set to `hidden` in IE 8/9/10/11. */ button { overflow: visible; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. * Correct `select` style inheritance in Firefox. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ input { line-height: normal; } /** * It's recommended that you don't attempt to style these elements. * Firefox's implementation doesn't respect box-sizing, padding, or width. * * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Fix the cursor style for Chrome's increment/decrement buttons. For certain * `font-size` values of the `input`, it causes the cursor style of the * decrement button to change from `default` to `text`. */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Address `appearance` set to `searchfield` in Safari and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari and Chrome on OS X. * Safari (but not Chrome) clips the cancel button when the search input has * padding (and `textfield` appearance). */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9/10/11. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * Remove default vertical scrollbar in IE 8/9/10/11. */ textarea { overflow: auto; } /** * Don't inherit the `font-weight` (applied by a rule above). * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. */ optgroup { font-weight: bold; } /* Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } ================================================ FILE: examples/poem_example/api/static/css/skeleton.css ================================================ /* * Skeleton V2.0.4 * Copyright 2014, Dave Gamache * www.getskeleton.com * Free to use under the MIT license. * https://opensource.org/licenses/mit-license.php * 12/29/2014 */ /* Table of contents –––––––––––––––––––––––––––––––––––––––––––––––––– - Grid - Base Styles - Typography - Links - Buttons - Forms - Lists - Code - Tables - Spacing - Utilities - Clearing - Media Queries */ /* Grid –––––––––––––––––––––––––––––––––––––––––––––––––– */ .container { position: relative; width: 100%; max-width: 960px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } .column, .columns { width: 100%; float: left; box-sizing: border-box; } /* For devices larger than 400px */ @media (min-width: 400px) { .container { width: 85%; padding: 0; } } /* For devices larger than 550px */ @media (min-width: 550px) { .container { width: 80%; } .column, .columns { margin-left: 4%; } .column:first-child, .columns:first-child { margin-left: 0; } .one.column, .one.columns { width: 4.66666666667%; } .two.columns { width: 13.3333333333%; } .three.columns { width: 22%; } .four.columns { width: 30.6666666667%; } .five.columns { width: 39.3333333333%; } .six.columns { width: 48%; } .seven.columns { width: 56.6666666667%; } .eight.columns { width: 65.3333333333%; } .nine.columns { width: 74.0%; } .ten.columns { width: 82.6666666667%; } .eleven.columns { width: 91.3333333333%; } .twelve.columns { width: 100%; margin-left: 0; } .one-third.column { width: 30.6666666667%; } .two-thirds.column { width: 65.3333333333%; } .one-half.column { width: 48%; } /* Offsets */ .offset-by-one.column, .offset-by-one.columns { margin-left: 8.66666666667%; } .offset-by-two.column, .offset-by-two.columns { margin-left: 17.3333333333%; } .offset-by-three.column, .offset-by-three.columns { margin-left: 26%; } .offset-by-four.column, .offset-by-four.columns { margin-left: 34.6666666667%; } .offset-by-five.column, .offset-by-five.columns { margin-left: 43.3333333333%; } .offset-by-six.column, .offset-by-six.columns { margin-left: 52%; } .offset-by-seven.column, .offset-by-seven.columns { margin-left: 60.6666666667%; } .offset-by-eight.column, .offset-by-eight.columns { margin-left: 69.3333333333%; } .offset-by-nine.column, .offset-by-nine.columns { margin-left: 78.0%; } .offset-by-ten.column, .offset-by-ten.columns { margin-left: 86.6666666667%; } .offset-by-eleven.column, .offset-by-eleven.columns { margin-left: 95.3333333333%; } .offset-by-one-third.column, .offset-by-one-third.columns { margin-left: 34.6666666667%; } .offset-by-two-thirds.column, .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } .offset-by-one-half.column, .offset-by-one-half.columns { margin-left: 52%; } } /* Base Styles –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* NOTE html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */ html { font-size: 62.5%; } body { font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ line-height: 1.6; font-weight: 400; font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #222; } /* Typography –––––––––––––––––––––––––––––––––––––––––––––––––– */ h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 2rem; font-weight: 300; } h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } /* Larger than phablet */ @media (min-width: 550px) { h1 { font-size: 5.0rem; } h2 { font-size: 4.2rem; } h3 { font-size: 3.6rem; } h4 { font-size: 3.0rem; } h5 { font-size: 2.4rem; } h6 { font-size: 1.5rem; } } p { margin-top: 0; } /* Links –––––––––––––––––––––––––––––––––––––––––––––––––– */ a { color: #1EAEDB; } a:hover { color: #0FA0CE; } /* Buttons –––––––––––––––––––––––––––––––––––––––––––––––––– */ .button, button, input[type="submit"], input[type="reset"], input[type="button"] { display: inline-block; height: 38px; padding: 0 30px; color: #555; text-align: center; font-size: 11px; font-weight: 600; line-height: 38px; letter-spacing: .1rem; text-transform: uppercase; text-decoration: none; white-space: nowrap; background-color: transparent; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; box-sizing: border-box; } .button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover, .button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus { color: #333; border-color: #888; outline: 0; } .button.button-primary, button.button-primary, button.primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary { color: #FFF; background-color: #33C3F0; border-color: #33C3F0; } .button.button-primary:hover, button.button-primary:hover, button.primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, button.primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus { color: #FFF; background-color: #1EAEDB; border-color: #1EAEDB; } /* Forms –––––––––––––––––––––––––––––––––––––––––––––––––– */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select { height: 38px; padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ background-color: #fff; border: 1px solid #D1D1D1; border-radius: 4px; box-shadow: none; box-sizing: border-box; } /* Removes awkward default styles on some inputs for iOS */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; } textarea { min-height: 65px; padding-top: 6px; padding-bottom: 6px; } input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus { border: 1px solid #33C3F0; outline: 0; } label, legend { display: block; margin-bottom: .5rem; font-weight: 600; } fieldset { padding: 0; border-width: 0; } input[type="checkbox"], input[type="radio"] { display: inline; } label > .label-body { display: inline-block; margin-left: .5rem; font-weight: normal; } /* Lists –––––––––––––––––––––––––––––––––––––––––––––––––– */ ul { list-style: circle inside; } ol { list-style: decimal inside; } ol, ul { padding-left: 0; margin-top: 0; } ul ul, ul ol, ol ol, ol ul { margin: 1.5rem 0 1.5rem 3rem; font-size: 90%; } li { margin-bottom: 1rem; } /* Code –––––––––––––––––––––––––––––––––––––––––––––––––– */ code { padding: .2rem .5rem; margin: 0 .2rem; font-size: 90%; white-space: nowrap; background: #F1F1F1; border: 1px solid #E1E1E1; border-radius: 4px; } pre > code { display: block; padding: 1rem 1.5rem; white-space: pre; } /* Tables –––––––––––––––––––––––––––––––––––––––––––––––––– */ th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #E1E1E1; } th:first-child, td:first-child { padding-left: 0; } th:last-child, td:last-child { padding-right: 0; } /* Spacing –––––––––––––––––––––––––––––––––––––––––––––––––– */ button, .button { margin-bottom: 1rem; } input, textarea, select, fieldset { margin-bottom: 1.5rem; } pre, blockquote, dl, figure, table, p, ul, ol, form { margin-bottom: 2.5rem; } /* Utilities –––––––––––––––––––––––––––––––––––––––––––––––––– */ .u-full-width { width: 100%; box-sizing: border-box; } .u-max-full-width { max-width: 100%; box-sizing: border-box; } .u-pull-right { float: right; } .u-pull-left { float: left; } /* Misc –––––––––––––––––––––––––––––––––––––––––––––––––– */ hr { margin-top: 3rem; margin-bottom: 3.5rem; border-width: 0; border-top: 1px solid #E1E1E1; } /* Clearing –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Self Clearing Goodness */ .container:after, .row:after, .u-cf { content: ""; display: table; clear: both; } /* Media Queries –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Note: The best way to structure the use of media queries is to create the queries near the relevant code. For example, if you wanted to change the styles for buttons on small devices, paste the mobile query code up in the buttons section and style it there. */ /* Larger than mobile */ @media (min-width: 400px) {} /* Larger than phablet (also point when grid becomes active) */ @media (min-width: 550px) {} /* Larger than tablet */ @media (min-width: 750px) {} /* Larger than desktop */ @media (min-width: 1000px) {} /* Larger than Desktop HD */ @media (min-width: 1200px) {} ================================================ FILE: examples/poem_example/api/static/css/style.css ================================================ .field-error { border: 1px solid #ff0000 !important; } .field-error-flash { color: #ff0000; display: block; margin: -10px 0 10px 0; } .field-success { border: 1px solid #5ab953 !important; } .field-success-flash { color: #5ab953; display: block; margin: -10px 0 10px 0; } span.completed { text-decoration: line-through; } form.inline { display: inline; } form.link, button.link { display: inline; color: #1eaedb; border: none; outline: none; background: none; cursor: pointer; padding: 0; margin: 0 0 0 0; height: inherit; text-decoration: underline; font-size: inherit; text-transform: none; font-weight: normal; line-height: inherit; letter-spacing: inherit; } form.link:hover, button.link:hover { color: #0fa0ce; } button.small { height: 20px; padding: 0 10px; font-size: 10px; line-height: 20px; margin: 0 2.5px; } .post:hover { background-color: #bce2ee; } .post td { padding: 5px; width: 150px; } #delete-button { color: red; border-color: red; } ================================================ FILE: examples/poem_example/api/templates/edit.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Edit Post

{% endblock content %} ================================================ FILE: examples/poem_example/api/templates/error/404.html.tera ================================================ 404 - tera

404: Hey! There's nothing here.

The page at {{ uri }} does not exist! ================================================ FILE: examples/poem_example/api/templates/index.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Posts

{% if flash %} {{ flash.message }} {% endif %} {% for post in posts %} {% endfor %}
ID Title Text
{{ post.id }} {{ post.title }} {{ post.text }}
{% if page == 1 %} Previous {% else %} Previous {% endif %} | {% if page == num_pages %} Next {% else %} Next {% endif %}
{% endblock content %} ================================================ FILE: examples/poem_example/api/templates/layout.html.tera ================================================ Poem Example

{% block content %}{% endblock content %}
================================================ FILE: examples/poem_example/api/templates/new.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

New Post

{% endblock content %} ================================================ FILE: examples/poem_example/api/tests/crud_tests.rs ================================================ use entity::post; use poem_example_api::service::{Mutation, Query}; use sea_orm::Database; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/poem_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/poem_example/entity/src/lib.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub mod prelude; pub mod post; ================================================ FILE: examples/poem_example/entity/src/post.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/poem_example/entity/src/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub use super::post::Entity as Post; ================================================ FILE: examples/poem_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-sqlite", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/poem_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/poem_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; mod m20220120_000002_seed_posts; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } } ================================================ FILE: examples/poem_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/poem_example/migration/src/m20220120_000002_seed_posts.rs ================================================ use entity::post; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Posts table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Post", "Second Post"]; post::Entity::delete_many() .filter(post::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Posts seeded data removed."); Ok(()) } } ================================================ FILE: examples/poem_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/poem_example/src/main.rs ================================================ fn main() { poem_example_api::main(); } ================================================ FILE: examples/proxy_cloudflare_worker_example/.gitignore ================================================ target node_modules .wrangler build dist ================================================ FILE: examples/proxy_cloudflare_worker_example/Cargo.toml ================================================ [package] authors = ["Langyo "] edition = "2024" name = "sea-orm-proxy-cloudflare-worker-example" publish = false rust-version = "1.85.0" version = "0.1.0" [workspace] [package.metadata.release] release = false # https://github.com/rustwasm/wasm-pack/issues/1247 [package.metadata.wasm-pack.profile.release] wasm-opt = false [lib] crate-type = ["cdylib"] [dependencies] anyhow = "1" async-trait = "0.1" once_cell = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" axum = { version = "0.7", default-features = false, features = ["macros"] } tower-service = "0.3.2" worker = { version = "0.3.0", features = ['http', 'axum', "d1"] } worker-macros = { version = "0.3.0", features = ['http'] } chrono = "0.4" uuid = { version = "1", features = ["v4"] } console_error_panic_hook = { version = "0.1" } gloo = "0.11" oneshot = "0.1" wasm-bindgen = "0.2.92" wasm-bindgen-futures = { version = "0.4" } sea-orm = { path = "../../", default-features = false, features = [ "macros", "proxy", "with-uuid", "with-chrono", "with-json", "debug-print", ] } [patch.crates-io] # https://github.com/cloudflare/workers-rs/pull/591 worker = { git = "https://github.com/cloudflare/workers-rs.git", rev = "ff2e6a0fd58b7e7b4b7651aba46e04067597eb03" } ================================================ FILE: examples/proxy_cloudflare_worker_example/README.md ================================================ # SeaORM Proxy Demo for Cloudflare Workers This is a simple Cloudflare worker written in Rust. It uses the `sea-orm` ORM to interact with SQLite that is stored in the Cloudflare D1. It also uses `axum` as the server framework. It's inspired by the [Cloudflare Workers Demo with Rust](https://github.com/logankeenan/full-stack-rust-cloudflare-axum). ## Run Make sure you have `npm` and `cargo` installed. Be sure to use the latest version of `nodejs` and `rust`. ```bash npx wrangler dev ``` ================================================ FILE: examples/proxy_cloudflare_worker_example/Wrangler.toml ================================================ compatibility_date = "2024-07-08" main = "build/worker/shim.mjs" name = "axum" [[d1_databases]] binding = "test-d1" database_name = "axumtest" # Change it if you want to use your own database database_id = "00000000-0000-0000-0000-000000000000" [build] command = "cargo install -q worker-build && worker-build --release" ================================================ FILE: examples/proxy_cloudflare_worker_example/src/entity.rs ================================================ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] #[sea_orm(table_name = "posts")] pub struct Model { #[sea_orm(primary_key)] pub id: i64, pub title: String, pub text: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/proxy_cloudflare_worker_example/src/lib.rs ================================================ use anyhow::Result; use axum::{body::Body, response::Response}; use tower_service::Service; use worker::{Context, Env, HttpRequest, event}; pub(crate) mod entity; pub(crate) mod orm; pub(crate) mod route; // https://developers.cloudflare.com/workers/languages/rust #[event(fetch)] async fn fetch(req: HttpRequest, env: Env, _ctx: Context) -> Result> { console_error_panic_hook::set_once(); Ok(route::router(env).call(req).await?) } ================================================ FILE: examples/proxy_cloudflare_worker_example/src/orm.rs ================================================ use anyhow::{Context, Result, anyhow}; use std::{collections::BTreeMap, sync::Arc}; use wasm_bindgen::JsValue; use sea_orm::{ ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, RuntimeErr, Schema, Statement, Value, Values, }; use worker::{Env, console_log}; struct ProxyDb { env: Arc, } impl std::fmt::Debug for ProxyDb { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ProxyDb").finish() } } impl ProxyDb { async fn do_query(env: Arc, statement: Statement) -> Result> { let sql = statement.sql.clone(); let values = match statement.values { Some(Values(values)) => values .iter() .map(|val| match &val { Value::BigInt(Some(val)) => JsValue::from(val.to_string()), Value::BigUnsigned(Some(val)) => JsValue::from(val.to_string()), Value::Int(Some(val)) => JsValue::from(*val), Value::Unsigned(Some(val)) => JsValue::from(*val), Value::SmallInt(Some(val)) => JsValue::from(*val), Value::SmallUnsigned(Some(val)) => JsValue::from(*val), Value::TinyInt(Some(val)) => JsValue::from(*val), Value::TinyUnsigned(Some(val)) => JsValue::from(*val), Value::Float(Some(val)) => JsValue::from_f64(*val as f64), Value::Double(Some(val)) => JsValue::from_f64(*val), Value::Bool(Some(val)) => JsValue::from(*val), Value::Bytes(Some(val)) => JsValue::from(format!( "X'{}'", val.iter() .map(|byte| format!("{:02x}", byte)) .collect::() )), Value::Char(Some(val)) => JsValue::from(val.to_string()), Value::Json(Some(val)) => JsValue::from(val.to_string()), Value::String(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDate(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTime(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTimeLocal(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTimeUtc(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTimeWithTimeZone(Some(val)) => JsValue::from(val.to_string()), _ => JsValue::NULL, }) .collect(), None => Vec::new(), }; console_log!("SQL query values: {:?}", values); let ret = env.d1("test-d1")?.prepare(sql).bind(&values)?.all().await?; if let Some(message) = ret.error() { return Err(anyhow!(message.to_string())); } let ret = ret.results::()?; let ret = ret .iter() .map(|row| { let mut values = BTreeMap::new(); for (key, value) in row.as_object().unwrap() { values.insert( key.clone(), match &value { serde_json::Value::Bool(val) => Value::Bool(Some(*val)), serde_json::Value::Number(val) => { if val.is_i64() { Value::BigInt(Some(val.as_i64().unwrap())) } else if val.is_u64() { Value::BigUnsigned(Some(val.as_u64().unwrap())) } else { Value::Double(Some(val.as_f64().unwrap())) } } serde_json::Value::String(val) => { Value::String(Some(Box::new(val.clone()))) } _ => unreachable!("Unsupported JSON value"), }, ); } ProxyRow { values } }) .collect(); console_log!("SQL query result: {:?}", ret); Ok(ret) } async fn do_execute(env: Arc, statement: Statement) -> Result { let sql = statement.sql.clone(); let values = match statement.values { Some(Values(values)) => values .iter() .map(|val| match &val { Value::BigInt(Some(val)) => JsValue::from(val.to_string()), Value::BigUnsigned(Some(val)) => JsValue::from(val.to_string()), Value::Int(Some(val)) => JsValue::from(*val), Value::Unsigned(Some(val)) => JsValue::from(*val), Value::SmallInt(Some(val)) => JsValue::from(*val), Value::SmallUnsigned(Some(val)) => JsValue::from(*val), Value::TinyInt(Some(val)) => JsValue::from(*val), Value::TinyUnsigned(Some(val)) => JsValue::from(*val), Value::Float(Some(val)) => JsValue::from_f64(*val as f64), Value::Double(Some(val)) => JsValue::from_f64(*val), Value::Bool(Some(val)) => JsValue::from(*val), Value::Bytes(Some(val)) => JsValue::from(format!( "X'{}'", val.iter() .map(|byte| format!("{:02x}", byte)) .collect::() )), Value::Char(Some(val)) => JsValue::from(val.to_string()), Value::Json(Some(val)) => JsValue::from(val.to_string()), Value::String(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDate(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTime(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTimeLocal(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTimeUtc(Some(val)) => JsValue::from(val.to_string()), Value::ChronoDateTimeWithTimeZone(Some(val)) => JsValue::from(val.to_string()), _ => JsValue::NULL, }) .collect(), None => Vec::new(), }; let ret = env .d1("test-d1")? .prepare(sql) .bind(&values)? .run() .await? .meta()?; console_log!("SQL execute result: {:?}", ret); let last_insert_id = ret .as_ref() .map(|meta| meta.last_row_id.unwrap_or(0)) .unwrap_or(0) as u64; let rows_affected = ret .as_ref() .map(|meta| meta.rows_written.unwrap_or(0)) .unwrap_or(0) as u64; Ok(ProxyExecResult { last_insert_id, rows_affected, }) } } #[async_trait::async_trait] impl ProxyDatabaseTrait for ProxyDb { async fn query(&self, statement: Statement) -> Result, DbErr> { console_log!("SQL query: {:?}", statement); let env = self.env.clone(); let (tx, rx) = oneshot::channel(); wasm_bindgen_futures::spawn_local(async move { let ret = Self::do_query(env, statement).await; tx.send(ret).unwrap(); }); let ret = rx.await.unwrap(); ret.map_err(|err| DbErr::Conn(RuntimeErr::Internal(err.to_string()))) } async fn execute(&self, statement: Statement) -> Result { console_log!("SQL execute: {:?}", statement); let env = self.env.clone(); let (tx, rx) = oneshot::channel(); wasm_bindgen_futures::spawn_local(async move { let ret = Self::do_execute(env, statement).await; tx.send(ret).unwrap(); }); let ret = rx.await.unwrap(); ret.map_err(|err| DbErr::Conn(RuntimeErr::Internal(err.to_string()))) } } pub async fn init_db(env: Arc) -> Result { let db = Database::connect_proxy(DbBackend::Sqlite, Arc::new(Box::new(ProxyDb { env }))) .await .context("Failed to connect to database")?; let builder = db.get_database_backend(); console_log!("Connected to database"); db.execute( builder.build( Schema::new(builder) .create_table_from_entity(crate::entity::Entity) .if_not_exists(), ), ) .await?; Ok(db) } ================================================ FILE: examples/proxy_cloudflare_worker_example/src/route.rs ================================================ use anyhow::Result; use std::sync::Arc; use axum::{Router, extract::State, http::StatusCode, response::IntoResponse, routing::get}; use worker::{Env, console_error, console_log}; use sea_orm::{ ActiveModelTrait, ActiveValue::{NotSet, Set}, EntityTrait, }; #[derive(Clone)] struct CFEnv { pub env: Arc, } unsafe impl Send for CFEnv {} unsafe impl Sync for CFEnv {} pub fn router(env: Env) -> Router { let state = CFEnv { env: Arc::new(env) }; Router::new() .route("/", get(handler_get)) .route("/generate", get(handler_generate)) .with_state(state) } async fn handler_get( State(state): State, ) -> Result { let env = state.env.clone(); let db = crate::orm::init_db(env).await.map_err(|err| { console_log!("Failed to connect to database: {:?}", err); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to connect to database".to_string(), ) })?; let ret = crate::entity::Entity::find() .all(&db) .await .map_err(|err| { console_log!("Failed to query database: {:?}", err); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to query database".to_string(), ) })?; let ret = serde_json::to_string(&ret).map_err(|err| { console_error!("Failed to serialize response: {:?}", err); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to serialize response".to_string(), ) })?; Ok(ret.into_response()) } async fn handler_generate( State(state): State, ) -> Result { let env = state.env.clone(); let db = crate::orm::init_db(env).await.map_err(|err| { console_log!("Failed to connect to database: {:?}", err); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to connect to database".to_string(), ) })?; let ret = crate::entity::ActiveModel { id: NotSet, title: Set(chrono::Utc::now().to_rfc3339()), text: Set(uuid::Uuid::new_v4().to_string()), }; let ret = ret.insert(&db).await.map_err(|err| { console_log!("Failed to insert into database: {:?}", err); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to insert into database".to_string(), ) })?; Ok(format!("Inserted: {:?}", ret).into_response()) } ================================================ FILE: examples/proxy_gluesql_example/Cargo.toml ================================================ [package] authors = ["Langyo "] edition = "2024" name = "sea-orm-proxy-gluesql-example" publish = false rust-version = "1.85.0" version = "0.1.0" [workspace] [dependencies] async-std = { version = "1.12", features = ["attributes", "tokio1"] } async-stream = { version = "0.3" } async-trait = { version = "0.1" } bigdecimal = { version = "=0.4.9" } futures = { version = "0.3" } futures-util = { version = "0.3" } serde = { version = "1" } serde_json = { version = "1" } gluesql = { version = "0.15", default-features = false, features = [ "memory-storage", ] } sea-orm = { path = "../../", default-features = false, features = [ "macros", "proxy", "debug-print", ] } sqlparser = "0.40" [dev-dependencies] smol = { version = "1.2" } smol-potat = { version = "1.1" } ================================================ FILE: examples/proxy_gluesql_example/README.md ================================================ # SeaORM Proxy Demo for GlueSQL Run this demo for [GlueSQL](https://gluesql.org/) with the following command: ```bash cargo run ``` ================================================ FILE: examples/proxy_gluesql_example/src/entity/mod.rs ================================================ pub mod post; ================================================ FILE: examples/proxy_gluesql_example/src/entity/post.rs ================================================ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] #[sea_orm(table_name = "posts")] pub struct Model { #[sea_orm(primary_key)] pub id: i64, pub title: String, pub text: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/proxy_gluesql_example/src/main.rs ================================================ //! Proxy connection example. #![deny(missing_docs)] mod entity; use std::{ collections::BTreeMap, sync::{Arc, Mutex}, }; use gluesql::{memory_storage::MemoryStorage, prelude::Glue}; use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, }; use entity::post::{ActiveModel, Entity}; struct ProxyDb { mem: Mutex>, } impl std::fmt::Debug for ProxyDb { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ProxyDb").finish() } } #[async_trait::async_trait] impl ProxyDatabaseTrait for ProxyDb { async fn query(&self, statement: Statement) -> Result, DbErr> { println!("SQL query: {:?}", statement); let sql = statement.sql.clone(); let mut ret: Vec = vec![]; async_std::task::block_on(async { for payload in self.mem.lock().unwrap().execute(sql).await.unwrap().iter() { match payload { gluesql::prelude::Payload::Select { labels, rows } => { for row in rows.iter() { let mut map = BTreeMap::new(); for (label, column) in labels.iter().zip(row.iter()) { map.insert( label.to_owned(), match column { gluesql::prelude::Value::I64(val) => { sea_orm::Value::BigInt(Some(*val)) } gluesql::prelude::Value::Str(val) => { sea_orm::Value::String(Some(val.to_owned())) } _ => unreachable!("Unsupported value: {:?}", column), }, ); } ret.push(map.into()); } } _ => unreachable!("Unsupported payload: {:?}", payload), } } }); Ok(ret) } async fn execute(&self, statement: Statement) -> Result { let sql = if let Some(values) = statement.values { // Replace all the '?' with the statement values use sqlparser::ast::{Expr, Value}; use sqlparser::dialect::GenericDialect; use sqlparser::parser::Parser; let dialect = GenericDialect {}; let mut ast = Parser::parse_sql(&dialect, statement.sql.as_str()).unwrap(); match &mut ast[0] { sqlparser::ast::Statement::Insert { columns, source, .. } => { for item in columns.iter_mut() { item.quote_style = Some('"'); } if let Some(obj) = source { match &mut *obj.body { sqlparser::ast::SetExpr::Values(obj) => { for (mut item, val) in obj.rows[0].iter_mut().zip(values.0.iter()) { match &mut item { Expr::Value(item) => { *item = match val { sea_orm::Value::String(val) => { Value::SingleQuotedString(match val { Some(val) => val.to_string(), None => "".to_string(), }) } sea_orm::Value::BigInt(val) => Value::Number( val.unwrap_or(0).to_string(), false, ), _ => todo!(), }; } _ => todo!(), } } } _ => todo!(), } } } _ => todo!(), } let statement = &ast[0]; statement.to_string() } else { statement.sql }; println!("SQL execute: {}", sql); async_std::task::block_on(async { self.mem.lock().unwrap().execute(sql).await.unwrap(); }); Ok(ProxyExecResult { last_insert_id: 1, rows_affected: 1, }) } } #[async_std::main] async fn main() { let mem = MemoryStorage::default(); let mut glue = Glue::new(mem); glue.execute( r#" CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, text TEXT NOT NULL ) "#, ) .await .unwrap(); let db = Database::connect_proxy( DbBackend::Sqlite, Arc::new(Box::new(ProxyDb { mem: Mutex::new(glue), })), ) .await .unwrap(); println!("Initialized"); let data = ActiveModel { id: Set(11), title: Set("Homo".to_owned()), text: Set("いいよ、来いよ".to_owned()), }; Entity::insert(data).exec(&db).await.unwrap(); let data = ActiveModel { id: Set(45), title: Set("Homo".to_owned()), text: Set("そうだよ".to_owned()), }; Entity::insert(data).exec(&db).await.unwrap(); let data = ActiveModel { id: Set(14), title: Set("Homo".to_owned()), text: Set("悔い改めて".to_owned()), }; Entity::insert(data).exec(&db).await.unwrap(); let list = Entity::find().all(&db).await.unwrap().to_vec(); println!("Result: {:?}", list); } #[cfg(test)] mod tests { #[smol_potat::test] async fn try_run() { crate::main() } } ================================================ FILE: examples/quickstart/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-quickstart" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] env_logger = { version = "0.11" } log = { version = "0.4" } tokio = { version = "1", features = ["full"] } [dependencies.sea-orm] features = [ "sqlx-sqlite", "runtime-tokio", "debug-print", "entity-registry", "schema-sync", ] path = "../../" ================================================ FILE: examples/quickstart/README.md ================================================ # SeaORM Quick Start This is a single file app that strives to minimize dependency and code noise. It uses an in-memory SQLite database so there is no setup. Just do `cargo run`! ================================================ FILE: examples/quickstart/src/main.rs ================================================ use log::info; mod user { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(unique)] pub email: String, #[sea_orm(has_one)] pub profile: HasOne, #[sea_orm(has_many)] pub posts: HasMany, #[sea_orm(self_ref, via = "user_follower", from = "User", to = "Follower")] pub followers: HasMany, #[sea_orm(self_ref, via = "user_follower", reverse)] pub following: HasMany, } impl ActiveModelBehavior for ActiveModel {} } mod profile { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "profile")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub picture: String, #[sea_orm(unique)] pub user_id: i32, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub user: HasOne, } impl ActiveModelBehavior for ActiveModel {} } mod post { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: i32, pub title: String, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub author: HasOne, #[sea_orm(has_many)] pub comments: HasMany, #[sea_orm(has_many, via = "post_tag")] pub tags: HasMany, } impl ActiveModelBehavior for ActiveModel {} } mod comment { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "comment")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub comment: String, pub user_id: i32, pub post_id: i32, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub user: HasOne, #[sea_orm(belongs_to, from = "post_id", to = "id")] pub post: HasOne, } impl ActiveModelBehavior for ActiveModel {} } mod tag { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "tag")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(unique)] pub tag: String, #[sea_orm(has_many, via = "post_tag")] pub posts: HasMany, } impl ActiveModelBehavior for ActiveModel {} } mod post_tag { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "post_tag")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub post_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub tag_id: i32, #[sea_orm(belongs_to, from = "post_id", to = "id")] pub post: Option, #[sea_orm(belongs_to, from = "tag_id", to = "id")] pub tag: Option, } impl ActiveModelBehavior for ActiveModel {} } mod user_follower { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "user_follower")] pub struct Model { #[sea_orm(primary_key)] pub user_id: i32, #[sea_orm(primary_key)] pub follower_id: i32, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub user: Option, #[sea_orm( belongs_to, relation_enum = "Follower", from = "follower_id", to = "id" )] pub follower: Option, } impl ActiveModelBehavior for ActiveModel {} } #[tokio::main] async fn main() -> Result<(), sea_orm::DbErr> { ///// Part 0: Setup Environment ///// // This disables sqlx's logging and enables sea-orm's logging with parameter injection, // which is easier to debug. let env = env_logger::Env::default().filter_or("RUST_LOG", "info,sea_orm=debug,sqlx=warn"); env_logger::Builder::from_env(env).init(); use sea_orm::{entity::*, query::*}; // Use a SQLite in memory database so no setup needed. // SeaORM supports MySQL, Postgres, SQL Server as well. let db = &sea_orm::Database::connect("sqlite::memory:").await?; // Populate this fresh database with tables. // // All entities defined in this crate are automatically registered // into the schema registry, regardless of which module they live in. // // The registry may also include entities from upstream crates, // so here we restrict it to entities defined in this crate only. // // The order of entity definitions does not matter. // SeaORM resolves foreign key dependencies automatically // and creates the tables in the correct order with their keys. db.get_schema_registry("sea_orm_quickstart::*") .sync(db) .await?; info!("Schema created."); ///// Part 1: CRUD with nested 1-1 and 1-N relations ///// info!("Create user Bob with a profile:"); let bob = user::ActiveModel::builder() .set_name("Bob") .set_email("bob@sea-ql.org") .set_profile(profile::ActiveModel::builder().set_picture("Tennis")) .insert(db) .await?; info!("Find Bob by email:"); assert_eq!( bob, // this method is generated by #[sea_orm::model] on unique keys user::Entity::find_by_email("bob@sea-ql.org") .one(db) .await? .unwrap() ); info!("Query user with profile in a single query:"); let mut bob = user::Entity::load() .filter_by_id(bob.id) .with(profile::Entity) .one(db) .await? .expect("Not found"); assert_eq!(bob.name, "Bob"); assert_eq!(bob.profile.as_ref().unwrap().picture, "Tennis"); // Here we take ownership of the nested model, modify in place and save it info!("Update Bob's profile:"); bob.profile .take() .unwrap() .into_active_model() .set_picture("Landscape") .save(db) .await?; info!("Confirmed that it's been updated:"); assert_eq!( profile::Entity::find_by_user_id(bob.id).all(db).await?[0].picture, "Landscape" ); // we don't have to set the `user_id` of the posts, they're automatically set to Bob info!("Bob wrote some posts:"); let mut bob = bob.into_active_model(); bob.posts .push( post::ActiveModel::builder() .set_title("Lorem ipsum dolor sit amet, consectetur adipiscing elit"), ) .push( post::ActiveModel::builder() .set_title("Ut enim ad minim veniam, quis nostrud exercitation"), ); bob.save(db).await?; info!("Find Bob's profile and his posts:"); let bob = user::Entity::load() .filter(user::COLUMN.name.eq("Bob")) .with(profile::Entity) .with(post::Entity) .one(db) .await? .unwrap(); assert_eq!(bob.name, "Bob"); assert_eq!(bob.profile.as_ref().unwrap().picture, "Landscape"); assert!(bob.posts[0].title.starts_with("Lorem ipsum")); assert!(bob.posts[1].title.starts_with("Ut enim ad")); // It's actually fine to create user + profile the other way round. // SeaORM figures out the dependency and creates the user first. info!("Create a new user Alice:"); let alice = profile::ActiveModel::builder() .set_user( user::ActiveModel::builder() .set_name("Alice") .set_email("alice@rust-lang.org"), ) .set_picture("Park") .insert(db) .await? .user .unwrap(); // Not only can we insert new posts via the bob active model, // we can also add new comments to the posts. // SeaORM walks the document tree and figures out what's changed, // and perform the operation in one transaction. let mut bob = bob.into_active_model(); info!("Alice commented on Bob's post:"); bob.posts[0].comments.push( comment::ActiveModel::builder() .set_comment("nice post!") .set_user_id(alice.id), ); bob.posts[1].comments.push( comment::ActiveModel::builder() .set_comment("interesting!") .set_user_id(alice.id), ); let bob = bob.save(db).await?; info!("Find all posts with author along with comments and who commented:"); let posts = post::Entity::load() .with(user::Entity) .with((comment::Entity, user::Entity)) .all(db) .await?; assert!(posts[0].title.starts_with("Lorem ipsum")); assert_eq!(posts[0].author.as_ref().unwrap().name, "Bob"); assert_eq!(posts[0].comments.len(), 1); assert_eq!(posts[0].comments[0].comment, "nice post!"); assert_eq!(posts[0].comments[0].user.as_ref().unwrap().name, "Alice"); assert!(posts[1].title.starts_with("Ut enim ad")); assert_eq!(posts[1].author.as_ref().unwrap().name, "Bob"); assert_eq!(posts[1].comments.len(), 1); assert_eq!(posts[1].comments[0].comment, "interesting!"); // Again, we can apply multiple changes in one operation, // the queries are executed inside a transaction. info!("Update post title and comment on first post:"); let mut post = posts[0].clone().into_active_model(); post.title = Set("Lorem ipsum dolor sit amet".into()); // shorten it post.comments[0].comment = Set("nice post! I learnt a lot".into()); post.save(db).await?; info!("Confirm the post and comment is updated"); let post = post::Entity::load() .filter_by_id(posts[0].id) .with(comment::Entity) .one(db) .await? .unwrap(); assert_eq!(post.title, "Lorem ipsum dolor sit amet"); assert_eq!(post.comments[0].comment, "nice post! I learnt a lot"); // Comments belongs to post. They will be deleted first, otherwise the foreign key // would prevent the operation. info!("Delete the post along with all comments"); post.delete(db).await?; assert!( post::Entity::find_by_id(posts[0].id) .one(db) .await? .is_none() ); ///// Part 2: managing M-N relations ///// // A unique feature of SeaORM is modelling many-to-many relations in a high level way info!("Insert one tag for later use"); let sunny = tag::ActiveModel::builder() .set_tag("sunny") .save(db) .await?; info!("Insert a new post with 2 tags"); let mut post = post::ActiveModel::builder() .set_title("A perfect day out") .set_user_id(alice.id) .add_tag(sunny.clone()) // an existing tag .add_tag(tag::ActiveModel::builder().set_tag("foodie")) // a new tag .save(db) // new tag will be created and associcated to the new post .await?; let post_id = post.id.clone().unwrap(); { info!("get back the post and tags"); let post = post::Entity::load() .filter_by_id(post_id) .with(tag::Entity) .one(db) .await? .unwrap(); assert_eq!(post.title, "A perfect day out"); assert_eq!(post.tags.len(), 2); assert_eq!(post.tags[0].tag, "sunny"); assert_eq!(post.tags[1].tag, "foodie"); } info!("Add new tag to post"); post.tags .push(tag::ActiveModel::builder().set_tag("downtown")); let mut post = post.save(db).await?; { info!("get back the post and tags"); let post = post::Entity::load() .filter_by_id(post_id) .with(tag::Entity) .one(db) .await? .unwrap(); assert_eq!(post.tags.len(), 3); assert_eq!(post.tags[0].tag, "sunny"); assert_eq!(post.tags[1].tag, "foodie"); assert_eq!(post.tags[2].tag, "downtown"); } info!("Update post title and remove a tag"); let mut tags = post.tags.take(); tags.as_mut_vec().remove(0); // it actually rained post.title = Set("Almost a perfect day out".into()); post.tags.replace_all(tags); // converting the field from append to replace would delete associations not in this list let post = post.save(db).await?; { info!("get back the post and tags"); let post = post::Entity::load() .filter_by_id(post_id) .with(tag::Entity) .one(db) .await? .unwrap(); assert_eq!(post.tags.len(), 2); assert_eq!(post.title, "Almost a perfect day out"); assert_eq!(post.tags[0].tag, "foodie"); assert_eq!(post.tags[1].tag, "downtown"); } // only the association between post and tag is removed, // but the tag itself is not deleted info!("check that the tag sunny still exists"); assert!(tag::Entity::find_by_tag("sunny").one(db).await?.is_some()); info!("cascade delete post, remove tag associations"); post.delete(db).await?; ///// Part 3: self-referencing relations ///// info!("save a new user with a new profile"); let sam = user::ActiveModel::builder() .set_name("Sam") .set_email("sam@rustacean.net") .set_profile(profile::ActiveModel::builder().set_picture("Crab.jpg")) .save(db) .await?; // we can do it from left to right: user <- follower info!("Add follower to Alice"); let alice = alice.into_active_model().add_follower(bob).save(db).await?; // we can also do it in reverse: user -> following info!("Sam starts following Alice"); sam.add_following(alice).save(db).await?; info!("Query Alice's profile and followers"); let alice = user::Entity::load() .filter_by_email("alice@rust-lang.org") .with(profile::Entity) .with(user_follower::Entity) .with(user_follower::Entity::REVERSE) .one(db) .await? .unwrap(); assert_eq!(alice.name, "Alice"); assert_eq!(alice.profile.as_ref().unwrap().picture, "Park"); assert_eq!(alice.followers.len(), 2); assert_eq!(alice.followers[0].name, "Bob"); assert_eq!(alice.followers[1].name, "Sam"); Ok(()) } ================================================ FILE: examples/react_admin/README.md ================================================ # GraphQL based Admin Dashboard with Loco and Seaography In this tutorial, we would develop a GraphQL based admin dashboard with [Seaography](https://github.com/SeaQL/seaography) and Loco. Read The full tutorial [here](https://www.sea-ql.org/blog/2024-08-08-graphql-admin-dashboard-with-loco-seaography/). Read our first and second tutorial of the series, [Getting Started with Loco & SeaORM](https://www.sea-ql.org/blog/2024-05-28-getting-started-with-loco-seaorm/), [Adding GraphQL Support to Loco with Seaography](https://www.sea-ql.org/blog/2024-07-01-graphql-support-with-loco-seaography/), if you haven't. ![Screenshot List](Screenshot-List.png) ![Screenshot View](Screenshot-View.png) ================================================ FILE: examples/react_admin/backend/.cargo/config.toml ================================================ [alias] loco = "run --" playground = "run --example playground" ================================================ FILE: examples/react_admin/backend/.devcontainer/Dockerfile ================================================ FROM mcr.microsoft.com/devcontainers/rust:1 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends postgresql-client \ && cargo install sea-orm-cli cargo-insta \ && chown -R vscode /usr/local/cargo COPY .env /.env ================================================ FILE: examples/react_admin/backend/.devcontainer/devcontainer.json ================================================ { "name": "Loco", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "forwardPorts": [ 3000 ] } ================================================ FILE: examples/react_admin/backend/.devcontainer/docker-compose.yml ================================================ version: "3" services: app: build: context: . dockerfile: Dockerfile command: sleep infinity networks: - db - redis - mailer volumes: - ../..:/workspaces:cached env_file: - .env db: image: postgres:15.3-alpine restart: unless-stopped ports: - 5432:5432 networks: - db volumes: - postgres-data:/var/lib/postgresql/data env_file: - .env redis: image: redis:latest restart: unless-stopped ports: - 6379:6379 networks: - redis mailer: image: mailtutan/mailtutan:latest restart: unless-stopped ports: - 1080:1080 - 1025:1025 networks: - mailer volumes: postgres-data: networks: db: redis: mailer: ================================================ FILE: examples/react_admin/backend/.github/workflows/ci.yaml ================================================ name: CI on: push: branches: - master - main pull_request: env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: rustfmt: name: Check Style runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true components: rustfmt - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: Run Clippy runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo clippy uses: actions-rs/cargo@v1 with: command: clippy args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms test: name: Run Tests runs-on: ubuntu-latest permissions: contents: read services: redis: image: redis options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - "6379:6379" postgres: image: postgres env: POSTGRES_DB: postgres_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - "5432:5432" # Set health checks to wait until postgres has started options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout the code uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: ${{ env.TOOLCHAIN_PROFILE }} toolchain: ${{ env.RUST_TOOLCHAIN }} override: true - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test args: --all-features --all env: REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}} DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test ================================================ FILE: examples/react_admin/backend/.gitignore ================================================ **/config/local.yaml **/config/*.local.yaml **/config/production.yaml # Generated by Cargo # will have compiled files and executables debug/ target/ # include cargo lock !Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb uploads ================================================ FILE: examples/react_admin/backend/.rustfmt.toml ================================================ comment_width = 80 format_strings = true group_imports = "StdExternalCrate" imports_granularity = "Crate" max_width = 100 use_small_heuristics = "Default" wrap_comments = true ================================================ FILE: examples/react_admin/backend/Cargo.lock ================================================ # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "Inflector" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" dependencies = [ "lazy_static", "regex", ] [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.15", "once_cell", "version_check", ] [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom 0.2.15", "once_cell", "version_check", "zerocopy 0.7.35", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aliasable" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "alloc-no-stdlib" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "ascii_utils" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" [[package]] name = "assert-json-diff" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ "serde", "serde_json", ] [[package]] name = "async-attributes" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", "syn 1.0.109", ] [[package]] name = "async-channel" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener 2.5.3", "futures-core", ] [[package]] name = "async-channel" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-compression" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ "brotli", "flate2", "futures-core", "memchr", "pin-project-lite", "tokio", "zstd", "zstd-safe", ] [[package]] name = "async-executor" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "slab", ] [[package]] name = "async-global-executor" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", "async-io", "async-lock", "blocking", "futures-lite", "once_cell", "tokio", ] [[package]] name = "async-graphql" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "036618f842229ba0b89652ffe425f96c7c16a49f7e3cb23b56fca7f61fd74980" dependencies = [ "async-graphql-derive", "async-graphql-parser", "async-graphql-value", "async-stream", "async-trait", "base64 0.22.1", "bytes", "chrono", "fast_chemail", "fnv", "futures-channel", "futures-timer", "futures-util", "handlebars", "http", "indexmap", "lru", "mime", "multer", "num-traits", "pin-project-lite", "regex", "rust_decimal", "serde", "serde_json", "serde_urlencoded", "static_assertions_next", "tempfile", "thiserror 1.0.69", ] [[package]] name = "async-graphql-axum" version = "7.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0344482a065904d2595377810e0bb35ad445ca4712b459757436303251d81b65" dependencies = [ "async-graphql", "axum", "bytes", "futures-util", "serde_json", "tokio", "tokio-stream", "tokio-util", "tower-service", ] [[package]] name = "async-graphql-derive" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd45deb3dbe5da5cdb8d6a670a7736d735ba65b455328440f236dfb113727a3d" dependencies = [ "Inflector", "async-graphql-parser", "darling", "proc-macro-crate", "proc-macro2", "quote", "strum 0.26.3", "syn 2.0.98", "thiserror 1.0.69", ] [[package]] name = "async-graphql-parser" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b7607e59424a35dadbc085b0d513aa54ec28160ee640cf79ec3b634eba66d3" dependencies = [ "async-graphql-value", "pest", "serde", "serde_json", ] [[package]] name = "async-graphql-value" version = "7.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ecdaff7c9cffa3614a9f9999bf9ee4c3078fe3ce4d6a6e161736b56febf2de" dependencies = [ "bytes", "indexmap", "serde", "serde_json", ] [[package]] name = "async-io" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix", "slab", "tracing", "windows-sys 0.59.0", ] [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener 5.4.0", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-std" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", "async-io", "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", "log", "memchr", "once_cell", "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] [[package]] name = "async-stream" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", "pin-project-lite", ] [[package]] name = "async-stream-impl" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auto-future" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" dependencies = [ "axum-core", "axum-macros", "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "itoa", "matchit", "memchr", "mime", "multer", "percent-encoding", "pin-project-lite", "rustversion", "serde", "serde_json", "serde_path_to_error", "serde_urlencoded", "sha1", "sync_wrapper", "tokio", "tokio-tungstenite", "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-core" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" dependencies = [ "bytes", "futures-util", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-extra" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b" dependencies = [ "axum", "axum-core", "bytes", "cookie", "futures-util", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "serde", "tower 0.5.2", "tower-layer", "tower-service", ] [[package]] name = "axum-macros" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "axum-test" version = "17.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317c1f4ecc1e68e0ad5decb78478421055c963ce215e736ed97463fa609cd196" dependencies = [ "anyhow", "assert-json-diff", "auto-future", "axum", "bytes", "bytesize", "cookie", "http", "http-body-util", "hyper", "hyper-util", "mime", "pretty_assertions", "reserve-port", "rust-multipart-rfc7578_2", "serde", "serde_json", "serde_urlencoded", "smallvec", "tokio", "tower 0.5.2", "url", ] [[package]] name = "backon" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5289ec98f68f28dd809fd601059e6aa908bb8f6108620930828283d4ee23d7" dependencies = [ "fastrand", "gloo-timers", "tokio", ] [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "backtrace_printer" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d28de81c708c843640982b66573df0f0168d87e42854b563971f326745aab7" dependencies = [ "btparse-stable", "colored 2.2.0", "regex", "thiserror 1.0.69", ] [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bigdecimal" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" dependencies = [ "autocfg", "libm", "num-bigint", "num-integer", "num-traits", "serde", ] [[package]] name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", "futures-lite", "piper", ] [[package]] name = "borsh" version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" dependencies = [ "borsh-derive", "cfg_aliases", ] [[package]] name = "borsh-derive" version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "brotli" version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] name = "bstr" version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "serde", ] [[package]] name = "btparse-stable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d75b8252ed252f881d1dc4482ae3c3854df6ee8183c1906bac50ff358f4f89f" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-unit" version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" dependencies = [ "serde", "utf8-width", ] [[package]] name = "bytecheck" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", "simdutf8", ] [[package]] name = "bytecheck_derive" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] [[package]] name = "bytesize" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "cc" version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-targets 0.52.6", ] [[package]] name = "chrono-tz" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", "phf", "phf_codegen", ] [[package]] name = "chumsky" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ "hashbrown 0.14.5", "stacker", ] [[package]] name = "clap" version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "colored" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "futures-core", "memchr", "pin-project-lite", "tokio", "tokio-util", ] [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cookie" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time", "version_check", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "cron" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" dependencies = [ "chrono", "nom", "once_cell", ] [[package]] name = "crossbeam-channel" version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "cruet" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "113a9e83d8f614be76de8df1f25bf9d0ea6e85ea573710a3d3f3abe1438ae49c" dependencies = [ "once_cell", "regex", ] [[package]] name = "cruet" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6132609543972496bc97b1e01f1ce6586768870aeb4cabeb3385f4e05b5caead" dependencies = [ "once_cell", "regex", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "cssparser" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" dependencies = [ "cssparser-macros", "dtoa-short", "itoa", "phf", "smallvec", ] [[package]] name = "cssparser-macros" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", "syn 2.0.98", ] [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.98", ] [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", "syn 2.0.98", ] [[package]] name = "dashmap" version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "data-encoding" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "der" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", ] [[package]] name = "derive_more" version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "deunicode" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dtoa-short" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" dependencies = [ "dtoa", ] [[package]] name = "duct" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7478638a31d1f1f3d6c9f5e57c76b906a04ac4879d6fd0fb6245bc88f73fd0b" dependencies = [ "libc", "os_pipe", "shared_child", "shared_thread", ] [[package]] name = "duct_sh" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8139179d1d133ab7153920ba3812915b17c61e2514a6f98b1fd03f2c07668d1" dependencies = [ "duct", ] [[package]] name = "ego-tree" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c6ba7d4eec39eaa9ab24d44a0e73a7949a1095a8b3f3abb11eddf27dbb56a53" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] [[package]] name = "email-encoding" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea3d894bbbab314476b265f9b2d46bf24b123a36dd0e96b06a1b49545b9d9dcc" dependencies = [ "base64 0.22.1", "memchr", ] [[package]] name = "email_address" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "english-to-cron" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c16423ac933fee80f05a52b435a912d5b08edbbbfe936e0042ebb3accdf303da" dependencies = [ "lazy_static", "regex", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "etcetera" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", "windows-sys 0.48.0", ] [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener 5.4.0", "pin-project-lite", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fast_chemail" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" dependencies = [ "ascii_utils", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flagset" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" [[package]] name = "flate2" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", "spin", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-intrusive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", "parking_lot", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "fxhash" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ "byteorder", ] [[package]] name = "generator" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" dependencies = [ "cfg-if", "libc", "log", "rustversion", "windows 0.58.0", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", "windows-targets 0.52.6", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "globwalk" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ "bitflags", "ignore", "walkdir", ] [[package]] name = "gloo-timers" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", ] [[package]] name = "handlebars" version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" dependencies = [ "log", "pest", "pest_derive", "serde", "serde_json", "thiserror 1.0.69", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash 0.7.8", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown 0.15.2", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "hostname" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ "cfg-if", "libc", "windows 0.52.0", ] [[package]] name = "html5ever" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e15626aaf9c351bc696217cbe29cb9b5e86c43f8a46b5e2f5c6c5cf7cb904ce" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "http" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", "http", "http-body", "pin-project-lite", ] [[package]] name = "http-range-header" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humansize" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ "libm", ] [[package]] name = "hyper" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-util" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2 0.5.8", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core 0.52.0", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "ignore" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", ] [[package]] name = "include_dir" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "indenter" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", "serde", ] [[package]] name = "inherent" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "insta" version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" dependencies = [ "console", "linked-hash-map", "once_cell", "pest", "pest_derive", "pin-project", "regex", "serde", "similar", ] [[package]] name = "io-uring" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "ipnetwork" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" dependencies = [ "serde", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "jsonwebtoken" version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ "base64 0.21.7", "js-sys", "pem", "ring", "serde", "serde_json", "simple_asn1", ] [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ "log", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "lettre" version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e882e1489810a45919477602194312b1a7df0e5acc30a6188be7b520268f63f8" dependencies = [ "async-trait", "base64 0.22.1", "chumsky", "email-encoding", "email_address", "fastrand", "futures-io", "futures-util", "hostname", "httpdate", "idna", "mime", "nom", "percent-encoding", "quoted_printable", "rustls", "rustls-pemfile", "rustls-pki-types", "socket2 0.5.8", "tokio", "tokio-rustls", "url", "webpki-roots", ] [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "loco-gen" version = "0.16.1" source = "git+https://github.com/SeaQL/loco?branch=master#a5637bc645c5290597335b8f5032525ef9c727c7" dependencies = [ "chrono", "clap", "colored 3.0.0", "cruet 0.14.0", "duct", "heck 0.4.1", "include_dir", "regex", "rrgen", "serde", "serde_json", "tera", "thiserror 1.0.69", "tracing", ] [[package]] name = "loco-rs" version = "0.16.1" source = "git+https://github.com/SeaQL/loco?branch=master#a5637bc645c5290597335b8f5032525ef9c727c7" dependencies = [ "argon2", "async-trait", "axum", "axum-extra", "axum-test", "backtrace_printer", "byte-unit", "bytes", "chrono", "clap", "colored 3.0.0", "cruet 0.13.3", "dashmap 6.1.0", "duct", "duct_sh", "english-to-cron", "futures-util", "heck 0.4.1", "include_dir", "ipnetwork", "jsonwebtoken", "lettre", "loco-gen", "moka", "opendal", "rand 0.9.2", "redis", "regex", "scraper", "sea-orm", "sea-orm-migration", "semver", "serde", "serde_json", "serde_variant", "serde_yaml", "sqlx", "tera", "thiserror 1.0.69", "tokio", "tokio-cron-scheduler", "tokio-util", "toml", "tower 0.4.13", "tower-http", "tracing", "tracing-appender", "tracing-subscriber", "tree-fs", "ulid", "uuid", "validator", ] [[package]] name = "loco_seaography" version = "0.1.0" dependencies = [ "async-graphql", "async-graphql-axum", "async-trait", "axum", "chrono", "eyre", "include_dir", "insta", "lazy_static", "loco-rs", "migration", "rstest", "sea-orm", "seaography", "serde", "serde_json", "serial_test", "tokio", "tokio-util", "tower-service", "tracing", "tracing-subscriber", "uuid", "validator", ] [[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" dependencies = [ "value-bag", ] [[package]] name = "loom" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ "cfg-if", "generator", "scoped-tls", "tracing", "tracing-subscriber", ] [[package]] name = "lru" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ "hashbrown 0.15.2", ] [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "markup5ever" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82c88c6129bd24319e62a0359cb6b958fa7e8be6e19bb1663bc396b90883aca5" dependencies = [ "log", "phf", "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "matchit" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "migration" version = "0.1.0" dependencies = [ "async-std", "loco-rs", "sea-orm-migration", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "moka" version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "loom", "parking_lot", "portable-atomic", "rustc_version", "smallvec", "tagptr", "thiserror 1.0.69", "uuid", ] [[package]] name = "multer" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", "http", "httparse", "memchr", "mime", "spin", "version_check", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand 0.8.5", "smallvec", "zeroize", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opendal" version = "0.50.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb28bb6c64e116ceaf8dd4e87099d3cfea4a58e85e62b104fef74c91afba0f44" dependencies = [ "anyhow", "async-trait", "backon", "base64 0.22.1", "bytes", "chrono", "flagset", "futures", "getrandom 0.2.15", "http", "log", "md-5", "once_cell", "percent-encoding", "quick-xml", "reqwest", "serde", "serde_json", "tokio", "uuid", ] [[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "os_pipe" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "ouroboros" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" dependencies = [ "aliasable", "ouroboros_macro", "static_assertions", ] [[package]] name = "ouroboros_macro" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ "heck 0.4.1", "proc-macro2", "proc-macro2-diagnostics", "quote", "syn 2.0.98", ] [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "parse-zoneinfo" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", "subtle", ] [[package]] name = "pem" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ "base64 0.22.1", "serde", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", "thiserror 2.0.11", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "pest_meta" version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", "sha2", ] [[package]] name = "pgvector" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" dependencies = [ "serde", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polling" version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", "tracing", "windows-sys 0.59.0", ] [[package]] name = "portable-atomic" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy 0.7.35", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "proc-macro-error2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", "version_check", "yansi", ] [[package]] name = "psm" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] [[package]] name = "ptr_meta" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quick-xml" version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.0", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.4", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.0", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.15", ] [[package]] name = "rand_core" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" dependencies = [ "getrandom 0.3.1", "zerocopy 0.8.14", ] [[package]] name = "redis" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bc1ea653e0b2e097db3ebb5b7f678be339620b8041f66b30a308c1d45d36a7f" dependencies = [ "bytes", "cfg-if", "combine", "futures-util", "itoa", "num-bigint", "percent-encoding", "pin-project-lite", "ryu", "sha1_smol", "socket2 0.5.8", "tokio", "tokio-util", "url", ] [[package]] name = "redox_syscall" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rend" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64 0.22.1", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "ipnet", "js-sys", "log", "mime", "once_cell", "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-util", "tower 0.5.2", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", "windows-registry", ] [[package]] name = "reserve-port" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "359fc315ed556eb0e42ce74e76f4b1cd807b50fa6307f3de4e51f92dbe86e2d5" dependencies = [ "lazy_static", "thiserror 2.0.11", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rkyv" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", "tinyvec", "uuid", ] [[package]] name = "rkyv_derive" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "rrgen" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee7a7ede035354391a37e42aa4935b3d8921f0ded896d2ce44bb1a3b6dd76bab" dependencies = [ "cruet 0.13.3", "fs-err", "glob", "heck 0.4.1", "regex", "serde", "serde_json", "serde_regex", "serde_yaml", "tera", "thiserror 1.0.69", ] [[package]] name = "rsa" version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rstest" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ "futures", "futures-timer", "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", "syn 2.0.98", "unicode-ident", ] [[package]] name = "rust-multipart-rfc7578_2" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc4bb9e7c9abe5fa5f30c2d8f8fefb9e0080a2c1e3c2e567318d2907054b35d3" dependencies = [ "bytes", "futures-core", "futures-util", "http", "mime", "mime_guess", "rand 0.9.2", "thiserror 2.0.11", ] [[package]] name = "rust_decimal" version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand 0.8.5", "rkyv", "serde", "serde_json", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustls" version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0e749d29b2064585327af5038a5a8eb73aeebad4a3472e83531a436563f7208" dependencies = [ "ahash 0.8.11", "cssparser", "ego-tree", "getopts", "html5ever", "indexmap", "precomputed-hash", "selectors", "tendril", ] [[package]] name = "sea-bae" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ "heck 0.4.1", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "sea-orm" version = "2.0.0-rc.1" source = "git+https://github.com/SeaQL/sea-orm?branch=master#4d29a8a841fc17fe7ad19cfadf497f90113e2bc6" dependencies = [ "async-stream", "async-trait", "bigdecimal", "chrono", "futures-util", "log", "ouroboros", "pgvector", "rust_decimal", "sea-orm-macros", "sea-query", "sea-query-sqlx", "serde", "serde_json", "sqlx", "strum 0.27.2", "thiserror 2.0.11", "time", "tracing", "url", "uuid", ] [[package]] name = "sea-orm-cli" version = "2.0.0-rc.1" source = "git+https://github.com/SeaQL/sea-orm?branch=master#4d29a8a841fc17fe7ad19cfadf497f90113e2bc6" dependencies = [ "chrono", "clap", "dotenvy", "glob", "regex", "sea-schema", "sqlx", "tracing", "tracing-subscriber", "url", ] [[package]] name = "sea-orm-macros" version = "2.0.0-rc.1" source = "git+https://github.com/SeaQL/sea-orm?branch=master#4d29a8a841fc17fe7ad19cfadf497f90113e2bc6" dependencies = [ "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", "sea-bae", "syn 2.0.98", "unicode-ident", ] [[package]] name = "sea-orm-migration" version = "2.0.0-rc.1" source = "git+https://github.com/SeaQL/sea-orm?branch=master#4d29a8a841fc17fe7ad19cfadf497f90113e2bc6" dependencies = [ "async-trait", "clap", "dotenvy", "sea-orm", "sea-orm-cli", "sea-schema", "tracing", "tracing-subscriber", ] [[package]] name = "sea-query" version = "1.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a95b5b42aa664f4ee19707b63194ba28f82165bf52468450968d530e5c462c" dependencies = [ "bigdecimal", "chrono", "inherent", "ordered-float", "rust_decimal", "sea-query-derive", "serde_json", "time", "uuid", ] [[package]] name = "sea-query-binder" version = "0.8.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04db61539fefbddb68409949d73d51f73e62eb7306fe1d8478a867ff3be8c9dd" dependencies = [ "sea-query", "sqlx", ] [[package]] name = "sea-query-derive" version = "1.0.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd199905d6c50267689c4625ee390b365b4bbac2230e8f180767c6511a13d7f" dependencies = [ "darling", "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.98", "thiserror 2.0.11", ] [[package]] name = "sea-query-sqlx" version = "0.8.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5eb19495858d8ae3663387a4f5298516c6f0171a7ca5681055450f190236b8" dependencies = [ "bigdecimal", "chrono", "rust_decimal", "sea-query", "serde_json", "sqlx", "time", "uuid", ] [[package]] name = "sea-schema" version = "0.17.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ff014f61e9db0aeb1ce1fd7c3be630e4d3d87bc9ba37beaee8c899d14cb3ab8" dependencies = [ "futures", "sea-query", "sea-query-binder", "sea-schema-derive", "sqlx", ] [[package]] name = "sea-schema-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "seahash" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "seaography" version = "2.0.0-rc.1" source = "git+https://github.com/SeaQL/seaography.git?branch=main#82c023a607fd235bfb822b2cf48a790caea618ed" dependencies = [ "async-graphql", "fnv", "heck 0.4.1", "itertools", "lazy_static", "sea-orm", "serde_json", "thiserror 1.0.69", ] [[package]] name = "selectors" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ "bitflags", "cssparser", "derive_more", "fxhash", "log", "new_debug_unreachable", "phf", "phf_codegen", "precomputed-hash", "servo_arc", "smallvec", ] [[package]] name = "semver" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "serde_json" version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_path_to_error" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", ] [[package]] name = "serde_regex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "serde_variant" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a0068df419f9d9b6488fdded3f1c818522cdea328e02ce9d9f147380265a432" dependencies = [ "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "serial_test" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" dependencies = [ "dashmap 5.5.3", "futures", "lazy_static", "log", "parking_lot", "serial_test_derive", ] [[package]] name = "serial_test_derive" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "servo_arc" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae65c4249478a2647db249fb43e23cec56a2c8974a427e7bd8cb5a1d0964921a" dependencies = [ "stable_deref_trait", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shared_child" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ "libc", "sigchld", "windows-sys 0.60.2", ] [[package]] name = "shared_thread" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "sigchld" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" dependencies = [ "libc", "os_pipe", "signal-hook", ] [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core 0.6.4", ] [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", "thiserror 2.0.11", "time", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "slug" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ "deunicode", "wasm-bindgen", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] [[package]] name = "socket2" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "sqlx" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", ] [[package]] name = "sqlx-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64 0.22.1", "bigdecimal", "bytes", "chrono", "crc", "crossbeam-queue", "either", "event-listener 5.4.0", "futures-core", "futures-intrusive", "futures-io", "futures-util", "hashbrown 0.15.2", "hashlink", "indexmap", "log", "memchr", "once_cell", "percent-encoding", "rust_decimal", "rustls", "serde", "serde_json", "sha2", "smallvec", "thiserror 2.0.11", "time", "tokio", "tokio-stream", "tracing", "url", "uuid", "webpki-roots", ] [[package]] name = "sqlx-macros" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", "syn 2.0.98", ] [[package]] name = "sqlx-macros-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", "heck 0.5.0", "hex", "once_cell", "proc-macro2", "quote", "serde", "serde_json", "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.98", "tokio", "url", ] [[package]] name = "sqlx-mysql" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", "bitflags", "byteorder", "bytes", "chrono", "crc", "digest", "dotenvy", "either", "futures-channel", "futures-core", "futures-io", "futures-util", "generic-array", "hex", "hkdf", "hmac", "itoa", "log", "md-5", "memchr", "once_cell", "percent-encoding", "rand 0.8.5", "rsa", "rust_decimal", "serde", "sha1", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.11", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-postgres" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", "bitflags", "byteorder", "chrono", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", "futures-util", "hex", "hkdf", "hmac", "home", "itoa", "log", "md-5", "memchr", "num-bigint", "once_cell", "rand 0.8.5", "rust_decimal", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.11", "time", "tracing", "uuid", "whoami", ] [[package]] name = "sqlx-sqlite" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", "flume", "futures-channel", "futures-core", "futures-executor", "futures-intrusive", "futures-util", "libsqlite3-sys", "log", "percent-encoding", "serde", "serde_urlencoded", "sqlx-core", "thiserror 2.0.11", "time", "tracing", "url", "uuid", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ "cc", "cfg-if", "libc", "psm", "windows-sys 0.59.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "static_assertions_next" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" [[package]] name = "string_cache" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "244292f3441c89febe5b5bdfbb6863aeaf4f64da810ea3050fd927b27b8d92ce" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", ] [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ "unicode-bidi", "unicode-normalization", "unicode-properties", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "strum_macros" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "rustversion", "syn 2.0.98", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "tera" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ "chrono", "chrono-tz", "globwalk", "humansize", "lazy_static", "percent-encoding", "pest", "pest_derive", "rand 0.8.5", "regex", "serde", "serde_json", "slug", "unic-segment", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl 2.0.11", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "thiserror-impl" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "time" version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", "slab", "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] [[package]] name = "tokio-cron-scheduler" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2594dd7c2abbbafbb1c78d167fd10860dc7bd75f814cb051a1e0d3e796b9702" dependencies = [ "chrono", "cron", "num-derive", "num-traits", "tokio", "tracing", "uuid", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "tokio-rustls" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-stream" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-tungstenite" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" dependencies = [ "futures-util", "log", "tokio", "tungstenite", ] [[package]] name = "tokio-util" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-http" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ "async-compression", "bitflags", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "http-range-header", "httpdate", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-appender" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "thiserror 1.0.69", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-serde" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "serde", "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", "tracing-serde", ] [[package]] name = "tree-fs" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6115680fa5fdb99b4ff19c9c3217e75116d2bb0eae82458c4e1818be6a10c7" dependencies = [ "rand 0.9.2", "serde", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" dependencies = [ "byteorder", "bytes", "data-encoding", "http", "httparse", "log", "rand 0.8.5", "sha1", "thiserror 2.0.11", "utf-8", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "ulid" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f294bff79170ed1c5633812aff1e565c35d993a36e757f9bc0accf5eec4e6045" dependencies = [ "rand 0.8.5", "web-time", ] [[package]] name = "unic-char-property" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" dependencies = [ "unic-char-range", ] [[package]] name = "unic-char-range" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" [[package]] name = "unic-common" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" [[package]] name = "unic-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" dependencies = [ "unic-ucd-segment", ] [[package]] name = "unic-ucd-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" dependencies = [ "unic-char-property", "unic-char-range", "unic-ucd-version", ] [[package]] name = "unic-ucd-version" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" dependencies = [ "unic-common", ] [[package]] name = "unicase" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "getrandom 0.2.15", "rand 0.8.5", "serde", ] [[package]] name = "validator" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ "idna", "once_cell", "regex", "serde", "serde_derive", "serde_json", "url", "validator_derive", ] [[package]] name = "validator_derive" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" dependencies = [ "darling", "once_cell", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-streams" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "web-time" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ "redox_syscall", "wasite", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core 0.52.0", "windows-targets 0.52.6", ] [[package]] name = "windows" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ "windows-core 0.58.0", "windows-targets 0.52.6", ] [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ "windows-implement", "windows-interface", "windows-result", "windows-strings", "windows-targets 0.52.6", ] [[package]] name = "windows-implement" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "windows-interface" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", "windows-targets 0.52.6", ] [[package]] name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.3", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ "bitflags", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", "synstructure", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" dependencies = [ "zerocopy-derive 0.8.14", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "zerocopy-derive" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "zerofrom" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] [[package]] name = "zstd" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", ] ================================================ FILE: examples/react_admin/backend/Cargo.toml ================================================ [workspace] [package] edition = "2024" name = "loco_seaography" rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] loco-rs = { version = "0.16" } migration = { path = "migration" } async-trait = "0.1.74" axum = { version = "0.8", features = ["multipart"] } chrono = "0.4" eyre = "0.6" include_dir = "0.7" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1.33.0", default-features = false } tokio-util = "0.7.11" tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] } uuid = { version = "1.6.0", features = ["v4"] } validator = { version = "0.20" } async-graphql = { version = "7.0.17", features = [ "decimal", "chrono", "dataloader", "dynamic-schema", ] } async-graphql-axum = { version = "7.0" } lazy_static = { version = "1.4" } tower-service = { version = "0.3" } [dependencies.sea-orm] features = [ "sqlx-sqlite", "sqlx-postgres", "runtime-tokio-rustls", "macros", ] version = "~2.0.0-rc.37" # sea-orm version [dependencies.seaography] branch = "main" features = ["with-decimal", "with-chrono"] git = "https://github.com/SeaQL/seaography.git" version = "~2.0.0-rc.1" # seaography version [[bin]] name = "loco_seaography-cli" path = "src/bin/main.rs" required-features = [] [dev-dependencies] insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } loco-rs = { version = "0.16", features = ["testing"] } rstest = "0.18.2" serial_test = "2.0.0" [patch.crates-io] loco-rs = { git = "https://github.com/SeaQL/loco", branch = "master" } ================================================ FILE: examples/react_admin/backend/config/development.yaml ================================================ # Loco configuration file documentation # Application logging configuration logger: # Enable or disable logging. enable: true # Enable pretty backtrace (sets RUST_BACKTRACE=1) pretty_backtrace: true # Log level, options: trace, debug, info, warn or error. level: debug # Define the logging format. options: compact, pretty or Json format: compact # By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries # Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters. # override_filter: trace # Web server configuration server: # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} port: 3000 # The UI hostname or IP address that mailers will point to. host: http://localhost # Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block middlewares: # Enable Etag cache header middleware etag: enable: true # Allows to limit the payload size request. payload that bigger than this file will blocked the request. limit_payload: # Enable/Disable the middleware. enable: true # the limit size. can be b,kb,kib,mb,mib,gb,gib body_limit: 5mb # Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details. logger: # Enable/Disable the middleware. enable: true # when your code is panicked, the request still returns 500 status code. catch_panic: # Enable/Disable the middleware. enable: true # Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned. timeout_request: # Enable/Disable the middleware. enable: false # Duration time in milliseconds. timeout: 5000 cors: enable: true # Set the value of the [`Access-Control-Allow-Origin`][mdn] header # allow_origins: # - https://loco.rs # Set the value of the [`Access-Control-Allow-Headers`][mdn] header # allow_headers: # - Content-Type # Set the value of the [`Access-Control-Allow-Methods`][mdn] header # allow_methods: # - POST # Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds # max_age: 3600 # Worker Configuration workers: # specifies the worker mode. Options: # - BackgroundQueue - Workers operate asynchronously in the background, processing queued. # - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed. # - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities. mode: BackgroundQueue # Mailer Configuration. mailer: # SMTP mailer configuration. smtp: # Enable/Disable smtp mailer. enable: true # SMTP server host. e.x localhost, smtp.gmail.com host: {{ get_env(name="MAILER_HOST", default="localhost") }} # SMTP server port port: 1025 # Use secure connection (SSL/TLS). secure: false # auth: # user: # password: # Initializers Configuration # initializers: # oauth2: # authorization_code: # Authorization code grant type # - client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config. # ... other fields # Database Configuration database: # Database connection URI uri: {{ get_env(name="DATABASE_URL", default="postgres://loco:loco@localhost:5432/loco_seaography_development") }} # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. connect_timeout: 500 # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. min_connections: 1 # Maximum number of connections for a pool. max_connections: 1 # Run migration up when application loaded auto_migrate: true # Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_truncate: false # Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_recreate: false # Redis Configuration redis: # Redis connection URI uri: {{ get_env(name="REDIS_URL", default="redis://127.0.0.1") }} # Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode dangerously_flush: false # Authentication Configuration auth: # JWT authentication jwt: # Secret key for token generation and verification secret: pByQUgg4GmXKAqQQvAGo # Token expiration time in seconds expiration: 604800 # 7 days ================================================ FILE: examples/react_admin/backend/examples/playground.rs ================================================ use eyre::Context; #[allow(unused_imports)] use loco_rs::{cli::playground, prelude::*}; use loco_seaography::app::App; #[tokio::main] async fn main() -> eyre::Result<()> { let _ctx = playground::().await.context("playground")?; // let active_model: articles::ActiveModel = ActiveModel { // title: Set(Some("how to build apps in 3 steps".to_string())), // content: Set(Some("use Loco: https://loco.rs".to_string())), // ..Default::default() // }; // active_model.insert(&ctx.db).await.unwrap(); // let res = articles::Entity::find().all(&ctx.db).await.unwrap(); // println!("{:?}", res); println!("welcome to playground. edit me at `examples/playground.rs`"); Ok(()) } ================================================ FILE: examples/react_admin/backend/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] loco-rs = { version = "0.16" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. # e.g. "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature ] version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/react_admin/backend/migration/README.md ================================================ # Running Migrator CLI - Generate a new migration file ```sh cargo run -- migrate generate MIGRATION_NAME ``` - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/react_admin/backend/migration/src/lib.rs ================================================ #![allow(elided_lifetimes_in_paths)] #![allow(clippy::wildcard_imports)] pub use sea_orm_migration::prelude::*; mod m20220101_000001_users; mod m20231103_114510_notes; mod m20240520_173001_files; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220101_000001_users::Migration), Box::new(m20231103_114510_notes::Migration), Box::new(m20240520_173001_files::Migration), ] } } ================================================ FILE: examples/react_admin/backend/migration/src/m20220101_000001_users.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let table = table_auto("users") .col(pk_auto("id")) .col(uuid("pid")) .col(string_uniq("email")) .col(string("password")) .col(string("api_key").unique_key()) .col(string("name")) .col(string_null("reset_token")) .col(timestamp_null("reset_sent_at")) .col(string_null("email_verification_token")) .col(timestamp_null("email_verification_sent_at")) .col(timestamp_null("email_verified_at")) .to_owned(); manager.create_table(table).await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("users").to_owned()) .await } } ================================================ FILE: examples/react_admin/backend/migration/src/m20231103_114510_notes.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto("notes") .col(pk_auto("id")) .col(string_null("title")) .col(string_null("content")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("notes").to_owned()) .await } } ================================================ FILE: examples/react_admin/backend/migration/src/m20240520_173001_files.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto("files") .col(pk_auto("id")) .col(integer("notes_id")) .col(string("file_path")) .foreign_key( ForeignKey::create() .name("FK_files_notes_id") .from("files", "notes_id") .to("notes", "id"), ) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("files").to_owned()) .await } } ================================================ FILE: examples/react_admin/backend/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/react_admin/backend/src/app.rs ================================================ use std::path::Path; use async_trait::async_trait; use loco_rs::{ Result, app::{AppContext, Hooks}, bgworker::{BackgroundWorker, Queue}, boot::{BootResult, StartMode, create_app}, config::Config, controller::AppRoutes, db::{self, truncate_table}, environment::Environment, task::Tasks, }; use migration::Migrator; use crate::{ controllers, models::_entities::{notes, users}, tasks, workers::downloader::DownloadWorker, }; pub struct App; #[async_trait] impl Hooks for App { fn app_name() -> &'static str { env!("CARGO_CRATE_NAME") } fn app_version() -> String { format!( "{} ({})", env!("CARGO_PKG_VERSION"), option_env!("BUILD_SHA") .or(option_env!("GITHUB_SHA")) .unwrap_or("dev") ) } async fn boot( mode: StartMode, environment: &Environment, config: Config, ) -> Result { create_app::(mode, environment, config).await } fn routes(_ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() .prefix("/api") .add_route(controllers::notes::routes()) .add_route(controllers::auth::routes()) .add_route(controllers::user::routes()) .add_route(controllers::files::routes()) .add_route(controllers::graphql::routes()) } async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> { queue.register(DownloadWorker::build(ctx)).await?; Ok(()) } fn register_tasks(tasks: &mut Tasks) { tasks.register(tasks::seed::SeedData); } async fn truncate(ctx: &AppContext) -> Result<()> { let db = &ctx.db; truncate_table(db, users::Entity).await?; truncate_table(db, notes::Entity).await?; Ok(()) } async fn seed(ctx: &AppContext, base: &Path) -> Result<()> { let db = &ctx.db; db::seed::(db, &base.join("users.yaml").display().to_string()).await?; db::seed::(db, &base.join("notes.yaml").display().to_string()).await?; Ok(()) } } ================================================ FILE: examples/react_admin/backend/src/bin/main.rs ================================================ use loco_rs::cli; use loco_seaography::app::App; use migration::Migrator; #[tokio::main] async fn main() -> eyre::Result<()> { cli::main::().await?; Ok(()) } ================================================ FILE: examples/react_admin/backend/src/controllers/auth.rs ================================================ use axum::debug_handler; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::{ mailers::auth::AuthMailer, models::{ _entities::users, users::{LoginParams, RegisterParams}, }, views::auth::LoginResponse, }; #[derive(Debug, Deserialize, Serialize)] pub struct VerifyParams { pub token: String, } #[derive(Debug, Deserialize, Serialize)] pub struct ForgotParams { pub email: String, } #[derive(Debug, Deserialize, Serialize)] pub struct ResetParams { pub token: String, pub password: String, } /// Register function creates a new user with the given parameters and sends a /// welcome email to the user #[debug_handler] async fn register( State(ctx): State, Json(params): Json, ) -> Result { let res = users::Model::create_with_password(&ctx.db, ¶ms).await; let user = match res { Ok(user) => user, Err(err) => { tracing::info!( message = err.to_string(), user_email = ¶ms.email, "could not register user", ); return format::json(()); } }; // Skip email verification, all new registrations are considered verified let _user = user.into_active_model().verified(&ctx.db).await?; // Skip sending verification email as we don't have a mail server /* let user = user .into_active_model() .set_email_verification_sent(&ctx.db) .await?; AuthMailer::send_welcome(&ctx, &user).await?; */ format::json(()) } /// Verify register user. if the user not verified his email, he can't login to /// the system. #[debug_handler] async fn verify( State(ctx): State, Json(params): Json, ) -> Result { let user = users::Model::find_by_verification_token(&ctx.db, ¶ms.token).await?; if user.email_verified_at.is_some() { tracing::info!(pid = user.pid.to_string(), "user already verified"); } else { let active_model = user.into_active_model(); let user = active_model.verified(&ctx.db).await?; tracing::info!(pid = user.pid.to_string(), "user verified"); } format::json(()) } /// In case the user forgot his password this endpoints generate a forgot token /// and send email to the user. In case the email not found in our DB, we are /// returning a valid request for for security reasons (not exposing users DB /// list). #[debug_handler] async fn forgot( State(ctx): State, Json(params): Json, ) -> Result { let Ok(user) = users::Model::find_by_email(&ctx.db, ¶ms.email).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller return format::json(()); }; let user = user .into_active_model() .set_forgot_password_sent(&ctx.db) .await?; AuthMailer::forgot_password(&ctx, &user).await?; format::json(()) } /// reset user password by the given parameters #[debug_handler] async fn reset(State(ctx): State, Json(params): Json) -> Result { let Ok(user) = users::Model::find_by_reset_token(&ctx.db, ¶ms.token).await else { // we don't want to expose our users email. if the email is invalid we still // returning success to the caller tracing::info!("reset token not found"); return format::json(()); }; user.into_active_model() .reset_password(&ctx.db, ¶ms.password) .await?; format::json(()) } /// Creates a user login and returns a token #[debug_handler] async fn login(State(ctx): State, Json(params): Json) -> Result { let user = users::Model::find_by_email(&ctx.db, ¶ms.email).await?; let valid = user.verify_password(¶ms.password); if !valid { return unauthorized("unauthorized!"); } let jwt_secret = ctx.config.get_jwt_config()?; let token = user .generate_jwt(&jwt_secret.secret, &jwt_secret.expiration) .or_else(|_| unauthorized("unauthorized!"))?; format::json(LoginResponse::new(&user, &token)) } pub fn routes() -> Routes { Routes::new() .prefix("auth") .add("/register", post(register)) .add("/verify", post(verify)) .add("/login", post(login)) .add("/forgot", post(forgot)) .add("/reset", post(reset)) } ================================================ FILE: examples/react_admin/backend/src/controllers/files.rs ================================================ #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use std::path::PathBuf; use axum::{body::Body, debug_handler, extract::Multipart}; use loco_rs::prelude::*; use sea_orm::QueryOrder; use tokio::{fs, io::AsyncWriteExt}; use tokio_util::io::ReaderStream; use crate::models::_entities::files; const UPLOAD_DIR: &str = "./uploads"; #[debug_handler] pub async fn upload( _auth: auth::JWT, Path(notes_id): Path, State(ctx): State, mut multipart: Multipart, ) -> Result { // Collect all uploaded files let mut files = Vec::new(); // Iterate all files in the POST body while let Some(field) = multipart.next_field().await.map_err(|err| { tracing::error!(error = ?err,"could not readd multipart"); Error::BadRequest("could not readd multipart".into()) })? { // Get the file name let file_name = match field.file_name() { Some(file_name) => file_name.to_string(), _ => return Err(Error::BadRequest("file name not found".into())), }; // Get the file content as bytes let content = field.bytes().await.map_err(|err| { tracing::error!(error = ?err,"could not readd bytes"); Error::BadRequest("could not readd bytes".into()) })?; // Create a folder to store the uploaded file let now = chrono::offset::Local::now() .format("%Y%m%d_%H%M%S") .to_string(); let uuid = uuid::Uuid::new_v4().to_string(); let folder = format!("{now}_{uuid}"); let upload_folder = PathBuf::from(UPLOAD_DIR).join(&folder); fs::create_dir_all(&upload_folder).await?; // Write the file into the newly created folder let path = upload_folder.join(file_name); let mut f = fs::OpenOptions::new() .create_new(true) .write(true) .open(&path) .await?; f.write_all(&content).await?; f.flush().await?; // Record the file upload in database let file = files::ActiveModel { notes_id: ActiveValue::Set(notes_id), file_path: ActiveValue::Set( path.strip_prefix(UPLOAD_DIR) .unwrap() .to_str() .unwrap() .to_string(), ), ..Default::default() } .insert(&ctx.db) .await?; files.push(file); } format::json(files) } #[debug_handler] pub async fn list( _auth: auth::JWT, Path(notes_id): Path, State(ctx): State, ) -> Result { // Fetch all files uploaded for a specific notes let files = files::Entity::find() .filter(files::Column::NotesId.eq(notes_id)) .order_by_asc(files::Column::Id) .all(&ctx.db) .await?; format::json(files) } #[debug_handler] pub async fn view( _auth: auth::JWT, Path(files_id): Path, State(ctx): State, ) -> Result { // Fetch the file info from database let file = files::Entity::find_by_id(files_id) .one(&ctx.db) .await? .expect("File not found"); // Stream the file let file = fs::File::open(format!("{UPLOAD_DIR}/{}", file.file_path)).await?; let stream = ReaderStream::new(file); let body = Body::from_stream(stream); Ok(format::render().response().body(body)?) } pub fn routes() -> Routes { // Bind the routes Routes::new() .prefix("files") .add("/upload/{notes_id}", post(upload)) .add("/list/{notes_id}", get(list)) .add("/view/{files_id}", get(view)) } ================================================ FILE: examples/react_admin/backend/src/controllers/graphql.rs ================================================ use async_graphql::http::{GraphQLPlaygroundConfig, playground_source}; use axum::{body::Body, extract::Request}; use loco_rs::prelude::*; use tower_service::Service; use crate::graphql::query_root; // GraphQL playground UI async fn graphql_playground() -> Result { // The `GraphQLPlaygroundConfig` take one parameter // which is the URL of the GraphQL handler: `/api/graphql` let res = playground_source(GraphQLPlaygroundConfig::new("/api/graphql")); Ok(Response::new(res.into())) } async fn graphql_handler(State(ctx): State, req: Request) -> Result { const DEPTH: usize = 1_000; const COMPLEXITY: usize = 1_000; // Construct the the GraphQL query root let schema = query_root::schema(ctx.db.clone(), DEPTH, COMPLEXITY).unwrap(); // GraphQL handler let mut graphql_handler = async_graphql_axum::GraphQL::new(schema); // Execute GraphQL request and fetch the results let res = graphql_handler.call(req).await.unwrap(); Ok(res) } pub fn routes() -> Routes { // Define route Routes::new() // We put all GraphQL route behind `graphql` prefix .prefix("graphql") // GraphQL playground page is a GET request .add("/", get(graphql_playground)) // GraphQL handler is a POST request .add("/", post(graphql_handler)) } ================================================ FILE: examples/react_admin/backend/src/controllers/mod.rs ================================================ pub mod auth; pub mod files; pub mod graphql; pub mod notes; pub mod user; ================================================ FILE: examples/react_admin/backend/src/controllers/notes.rs ================================================ #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use axum::debug_handler; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::models::_entities::notes::{ActiveModel, Entity, Model}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Params { pub title: Option, pub content: Option, } impl Params { fn update(&self, item: &mut ActiveModel) { item.title = Set(self.title.clone()); item.content = Set(self.content.clone()); } } async fn load_item(ctx: &AppContext, id: i32) -> Result { let item = Entity::find_by_id(id).one(&ctx.db).await?; item.ok_or_else(|| Error::NotFound) } #[debug_handler] pub async fn list(State(ctx): State) -> Result { format::json(Entity::find().all(&ctx.db).await?) } #[debug_handler] pub async fn add(State(ctx): State, Json(params): Json) -> Result { let mut item = ActiveModel { ..Default::default() }; params.update(&mut item); let item = item.insert(&ctx.db).await?; format::json(item) } #[debug_handler] pub async fn update( Path(id): Path, State(ctx): State, Json(params): Json, ) -> Result { let item = load_item(&ctx, id).await?; let mut item = item.into_active_model(); params.update(&mut item); let item = item.update(&ctx.db).await?; format::json(item) } #[debug_handler] pub async fn remove(Path(id): Path, State(ctx): State) -> Result { load_item(&ctx, id).await?.delete(&ctx.db).await?; format::empty() } #[debug_handler] pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { format::json(load_item(&ctx, id).await?) } pub fn routes() -> Routes { Routes::new() .prefix("notes") .add("/", get(list)) .add("/", post(add)) .add("/{id}", get(get_one)) .add("/{id}", delete(remove)) .add("/{id}", post(update)) } ================================================ FILE: examples/react_admin/backend/src/controllers/user.rs ================================================ use axum::debug_handler; use loco_rs::prelude::*; use crate::{models::_entities::users, views::user::CurrentResponse}; #[debug_handler] async fn current(auth: auth::JWT, State(ctx): State) -> Result { let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; format::json(CurrentResponse::new(&user)) } pub fn routes() -> Routes { Routes::new().prefix("user").add("/current", get(current)) } ================================================ FILE: examples/react_admin/backend/src/fixtures/notes.yaml ================================================ --- - id: 1 title: Loco note 1 content: Loco note 1 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" - id: 2 title: Loco note 2 content: Loco note 2 content created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" ================================================ FILE: examples/react_admin/backend/src/fixtures/users.yaml ================================================ --- - id: 1 pid: 11111111-1111-1111-1111-111111111111 email: user1@example.com password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" api_key: lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758 name: user1 created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" - id: 2 pid: 22222222-2222-2222-2222-222222222222 email: user2@example.com password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc" api_key: lo-153561ca-fa84-4e1b-813a-c62526d0a77e name: user2 created_at: "2023-11-12T12:34:56.789" updated_at: "2023-11-12T12:34:56.789" ================================================ FILE: examples/react_admin/backend/src/graphql/mod.rs ================================================ pub mod query_root; ================================================ FILE: examples/react_admin/backend/src/graphql/query_root.rs ================================================ use async_graphql::dynamic::*; use sea_orm::DatabaseConnection; use seaography::{Builder, BuilderContext}; lazy_static::lazy_static! { static ref CONTEXT: BuilderContext = BuilderContext::default(); } pub fn schema( database: DatabaseConnection, depth: usize, complexity: usize, ) -> Result { // Builder of Seaography query root let builder = Builder::new(&CONTEXT, database.clone()); // Register SeaORM entities let builder = crate::models::_entities::register_entity_modules(builder); // Configure async GraphQL limits let schema = builder .schema_builder() // The depth is the number of nesting levels of the field .limit_depth(depth) // The complexity is the number of fields in the query .limit_complexity(complexity); // Finish up with including SeaORM database connection schema.data(database).finish() } ================================================ FILE: examples/react_admin/backend/src/lib.rs ================================================ pub mod app; pub mod controllers; pub mod graphql; pub mod mailers; pub mod models; pub mod tasks; pub mod views; pub mod workers; ================================================ FILE: examples/react_admin/backend/src/mailers/auth/forgot/html.t ================================================ ; Hey {{name}}, Forgot your password? No worries! You can reset it by clicking the link below: Reset Your Password If you didn't request a password reset, please ignore this email. Best regards,
The Loco Team
================================================ FILE: examples/react_admin/backend/src/mailers/auth/forgot/subject.t ================================================ Your reset password link ================================================ FILE: examples/react_admin/backend/src/mailers/auth/forgot/text.t ================================================ Reset your password with this link: http://localhost/reset#{{resetToken}} ================================================ FILE: examples/react_admin/backend/src/mailers/auth/welcome/html.t ================================================ ; Dear {{name}}, Welcome to Loco! You can now log in to your account. Before you get started, please verify your account by clicking the link below: Verify Your Account

Best regards,
The Loco Team

================================================ FILE: examples/react_admin/backend/src/mailers/auth/welcome/subject.t ================================================ Welcome {{name}} ================================================ FILE: examples/react_admin/backend/src/mailers/auth/welcome/text.t ================================================ Welcome {{name}}, you can now log in. Verify your account with the link below: http://localhost/verify#{{verifyToken}} ================================================ FILE: examples/react_admin/backend/src/mailers/auth.rs ================================================ // auth mailer #![allow(non_upper_case_globals)] use loco_rs::prelude::*; use serde_json::json; use crate::models::users; static welcome: Dir<'_> = include_dir!("src/mailers/auth/welcome"); static forgot: Dir<'_> = include_dir!("src/mailers/auth/forgot"); // #[derive(Mailer)] // -- disabled for faster build speed. it works. but lets // move on for now. #[allow(clippy::module_name_repetitions)] pub struct AuthMailer {} impl Mailer for AuthMailer {} impl AuthMailer { /// Sending welcome email the the given user /// /// # Errors /// /// When email sending is failed pub async fn send_welcome(ctx: &AppContext, user: &users::Model) -> Result<()> { Self::mail_template( ctx, &welcome, mailer::Args { to: user.email.to_string(), locals: json!({ "name": user.name, "verifyToken": user.email_verification_token, "domain": ctx.config.server.full_url() }), ..Default::default() }, ) .await?; Ok(()) } /// Sending forgot password email /// /// # Errors /// /// When email sending is failed pub async fn forgot_password(ctx: &AppContext, user: &users::Model) -> Result<()> { Self::mail_template( ctx, &forgot, mailer::Args { to: user.email.to_string(), locals: json!({ "name": user.name, "resetToken": user.reset_token, "domain": ctx.config.server.full_url() }), ..Default::default() }, ) .await?; Ok(()) } } ================================================ FILE: examples/react_admin/backend/src/mailers/mod.rs ================================================ pub mod auth; ================================================ FILE: examples/react_admin/backend/src/models/_entities/files.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.10 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "files")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub notes_id: i32, pub file_path: String, #[sea_orm(belongs_to, from = "notes_id", to = "id")] pub notes: HasOne, } ================================================ FILE: examples/react_admin/backend/src/models/_entities/mod.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0-rc.5 pub mod prelude; pub mod files; pub mod notes; pub mod users; seaography::register_entity_modules!([files, notes, users]); ================================================ FILE: examples/react_admin/backend/src/models/_entities/notes.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.10 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "notes")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub title: Option, pub content: Option, #[sea_orm(has_many)] pub files: HasMany, } ================================================ FILE: examples/react_admin/backend/src/models/_entities/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0-rc.5 pub use super::{files::Entity as Files, notes::Entity as Notes, users::Entity as Users}; ================================================ FILE: examples/react_admin/backend/src/models/_entities/users.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0-rc.5 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "users")] pub struct Model { pub created_at: DateTime, pub updated_at: DateTime, #[sea_orm(primary_key)] pub id: i32, pub pid: Uuid, #[sea_orm(unique)] pub email: String, pub password: String, #[sea_orm(unique)] pub api_key: String, pub name: String, pub reset_token: Option, pub reset_sent_at: Option, pub email_verification_token: Option, pub email_verification_sent_at: Option, pub email_verified_at: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity {} ================================================ FILE: examples/react_admin/backend/src/models/files.rs ================================================ use sea_orm::entity::prelude::*; use super::_entities::files::ActiveModel; impl ActiveModelBehavior for ActiveModel { // extend activemodel below (keep comment for generators) } ================================================ FILE: examples/react_admin/backend/src/models/mod.rs ================================================ pub mod _entities; pub mod files; pub mod notes; pub mod users; ================================================ FILE: examples/react_admin/backend/src/models/notes.rs ================================================ use sea_orm::entity::prelude::*; use super::_entities::notes::ActiveModel; impl ActiveModelBehavior for ActiveModel { // extend activemodel below (keep comment for generators) } ================================================ FILE: examples/react_admin/backend/src/models/users.rs ================================================ use async_trait::async_trait; use chrono::offset::Local; use loco_rs::{auth::jwt, hash, prelude::*}; use serde::{Deserialize, Serialize}; use uuid::Uuid; pub use super::_entities::users::{self, ActiveModel, Entity, Model}; #[derive(Debug, Deserialize, Serialize)] pub struct LoginParams { pub email: String, pub password: String, } #[derive(Debug, Deserialize, Serialize)] pub struct RegisterParams { pub email: String, pub password: String, pub name: String, } #[derive(Debug, Validate, Deserialize)] pub struct Validator { #[validate(length(min = 2, message = "Name must be at least 2 characters long."))] pub name: String, #[validate(email(message = "invalid email"))] pub email: String, } impl Validatable for super::_entities::users::ActiveModel { fn validator(&self) -> Box { Box::new(Validator { name: self.name.as_ref().to_owned(), email: self.email.as_ref().to_owned(), }) } } #[async_trait::async_trait] impl ActiveModelBehavior for super::_entities::users::ActiveModel { async fn before_save(self, _db: &C, insert: bool) -> Result where C: ConnectionTrait, { self.validate()?; if insert { let mut this = self; this.pid = ActiveValue::Set(Uuid::new_v4()); this.api_key = ActiveValue::Set(format!("lo-{}", Uuid::new_v4())); Ok(this) } else { Ok(self) } } } #[async_trait] impl Authenticable for super::_entities::users::Model { async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ApiKey.eq(api_key)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult { Self::find_by_pid(db, claims_key).await } } impl super::_entities::users::Model { /// finds a user by the provided email /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::Email.eq(email)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided verification token /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_verification_token( db: &DatabaseConnection, token: &str, ) -> ModelResult { let user = users::Entity::find() .filter(users::Column::EmailVerificationToken.eq(token)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// /// finds a user by the provided reset token /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_reset_token(db: &DatabaseConnection, token: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ResetToken.eq(token)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided pid /// /// # Errors /// /// When could not find user or DB query error pub async fn find_by_pid(db: &DatabaseConnection, pid: &str) -> ModelResult { let parse_uuid = Uuid::parse_str(pid).map_err(|e| ModelError::Any(e.into()))?; let user = users::Entity::find() .filter(users::Column::Pid.eq(parse_uuid)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// finds a user by the provided api key /// /// # Errors /// /// When could not find user by the given token or DB query error pub async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ApiKey.eq(api_key)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } /// Verifies whether the provided plain password matches the hashed password /// /// # Errors /// /// when could not verify password #[must_use] pub fn verify_password(&self, password: &str) -> bool { hash::verify_password(password, &self.password) } /// Asynchronously creates a user with a password and saves it to the /// database. /// /// # Errors /// /// When could not save the user into the DB pub async fn create_with_password( db: &DatabaseConnection, params: &RegisterParams, ) -> ModelResult { let txn = db.begin().await?; if users::Entity::find() .filter(users::Column::Email.eq(¶ms.email)) .one(&txn) .await? .is_some() { return Err(ModelError::EntityAlreadyExists {}); } let password_hash = hash::hash_password(¶ms.password).map_err(|e| ModelError::Any(e.into()))?; let user = users::ActiveModel { email: ActiveValue::set(params.email.to_string()), password: ActiveValue::set(password_hash), name: ActiveValue::set(params.name.to_string()), ..Default::default() } .insert(&txn) .await?; txn.commit().await?; Ok(user) } /// Creates a JWT /// /// # Errors /// /// when could not convert user claims to jwt token pub fn generate_jwt(&self, secret: &str, expiration: &u64) -> ModelResult { Ok(jwt::JWT::new(secret).generate_token( *expiration, self.pid.to_string(), Default::default(), )?) } } impl super::_entities::users::ActiveModel { /// Sets the email verification information for the user and /// updates it in the database. /// /// This method is used to record the timestamp when the email verification /// was sent and generate a unique verification token for the user. /// /// # Errors /// /// when has DB query error pub async fn set_email_verification_sent( mut self, db: &DatabaseConnection, ) -> ModelResult { self.email_verification_sent_at = ActiveValue::set(Some(Local::now().naive_local())); self.email_verification_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); Ok(self.update(db).await?) } /// Sets the information for a reset password request, /// generates a unique reset password token, and updates it in the /// database. /// /// This method records the timestamp when the reset password token is sent /// and generates a unique token for the user. /// /// # Arguments /// /// # Errors /// /// when has DB query error pub async fn set_forgot_password_sent(mut self, db: &DatabaseConnection) -> ModelResult { self.reset_sent_at = ActiveValue::set(Some(Local::now().naive_local())); self.reset_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); Ok(self.update(db).await?) } /// Records the verification time when a user verifies their /// email and updates it in the database. /// /// This method sets the timestamp when the user successfully verifies their /// email. /// /// # Errors /// /// when has DB query error pub async fn verified(mut self, db: &DatabaseConnection) -> ModelResult { self.email_verified_at = ActiveValue::set(Some(Local::now().naive_local())); Ok(self.update(db).await?) } /// Resets the current user password with a new password and /// updates it in the database. /// /// This method hashes the provided password and sets it as the new password /// for the user. /// # Errors /// /// when has DB query error or could not hashed the given password pub async fn reset_password( mut self, db: &DatabaseConnection, password: &str, ) -> ModelResult { self.password = ActiveValue::set(hash::hash_password(password).map_err(|e| ModelError::Any(e.into()))?); Ok(self.update(db).await?) } } ================================================ FILE: examples/react_admin/backend/src/tasks/mod.rs ================================================ pub mod seed; ================================================ FILE: examples/react_admin/backend/src/tasks/seed.rs ================================================ //! This task implements data seeding functionality for initializing new //! development/demo environments. //! //! # Example //! //! Run the task with the following command: //! ```sh //! cargo run task //! ``` //! //! To override existing data and reset the data structure, use the following //! command with the `refresh:true` argument: //! ```sh //! cargo run task seed_data refresh:true //! ``` use loco_rs::{db, prelude::*}; use migration::Migrator; use crate::app::App; #[allow(clippy::module_name_repetitions)] pub struct SeedData; #[async_trait] impl Task for SeedData { fn task(&self) -> TaskInfo { TaskInfo { name: "seed_data".to_string(), detail: "Task for seeding data".to_string(), } } async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> { let refresh = vars .cli_arg("refresh") .is_ok_and(|refresh| refresh == "true"); if refresh { db::reset::(&app_context.db).await?; } let path = std::path::Path::new("src/fixtures"); db::run_app_seed::(app_context, path).await?; Ok(()) } } ================================================ FILE: examples/react_admin/backend/src/views/auth.rs ================================================ use serde::{Deserialize, Serialize}; use crate::models::_entities::users; #[derive(Debug, Deserialize, Serialize)] pub struct LoginResponse { pub token: String, pub pid: String, pub name: String, pub is_verified: bool, } impl LoginResponse { #[must_use] pub fn new(user: &users::Model, token: &String) -> Self { Self { token: token.to_string(), pid: user.pid.to_string(), name: user.name.clone(), is_verified: user.email_verified_at.is_some(), } } } ================================================ FILE: examples/react_admin/backend/src/views/mod.rs ================================================ pub mod auth; pub mod user; ================================================ FILE: examples/react_admin/backend/src/views/user.rs ================================================ use serde::{Deserialize, Serialize}; use crate::models::_entities::users; #[derive(Debug, Deserialize, Serialize)] pub struct CurrentResponse { pub pid: String, pub name: String, pub email: String, } impl CurrentResponse { #[must_use] pub fn new(user: &users::Model) -> Self { Self { pid: user.pid.to_string(), name: user.name.clone(), email: user.email.clone(), } } } ================================================ FILE: examples/react_admin/backend/src/workers/downloader.rs ================================================ use std::time::Duration; use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use tokio::time::sleep; use crate::models::users; pub struct DownloadWorker { pub ctx: AppContext, } #[derive(Deserialize, Debug, Serialize)] pub struct DownloadWorkerArgs { pub user_guid: String, } #[async_trait] impl BackgroundWorker for DownloadWorker { fn build(ctx: &AppContext) -> Self { Self { ctx: ctx.clone() } } async fn perform(&self, args: DownloadWorkerArgs) -> Result<()> { // TODO: Some actual work goes here... println!("================================================"); println!("Sending payment report to user {}", args.user_guid); sleep(Duration::from_millis(2000)).await; let all = users::Entity::find() .all(&self.ctx.db) .await .map_err(Box::from)?; for user in &all { println!("user: {}", user.id); } println!("================================================"); Ok(()) } } ================================================ FILE: examples/react_admin/backend/src/workers/mod.rs ================================================ pub mod downloader; ================================================ FILE: examples/react_admin/frontend/.eslintrc.js ================================================ export default { "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:react/jsx-runtime", "plugin:react-hooks/recommended", "prettier" ], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], "env": { "browser": true, "es2021": true, "node": true }, "settings": { "react": { "version": "detect" } } } ================================================ FILE: examples/react_admin/frontend/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: examples/react_admin/frontend/README.md ================================================ # react-admin ## Installation Install the application dependencies by running: ```sh npm install ``` ## Development Start the application in development mode by running: ```sh npm run dev ``` ## Production Build the application in production mode by running: ```sh npm run build ``` ## Authentication The included auth provider should only be used for development and test purposes. You'll find a `users.json` file in the `src` directory that includes the users you can use. You can sign in to the application with the following usernames and password: - janedoe / password - johndoe / password ================================================ FILE: examples/react_admin/frontend/index.html ================================================ react-admin
Loading...
================================================ FILE: examples/react_admin/frontend/package.json ================================================ { "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", "type-check": "tsc --noEmit", "lint": "eslint --fix --ext .js,.jsx,.ts,.tsx ./src", "format": "prettier --write ./src" }, "dependencies": { "axios": "^1.7.2", "ra-data-json-server": "^5.0.5", "react": "^18.3.0", "react-admin": "^5.0.0", "react-dom": "^18.3.0" }, "devDependencies": { "@types/node": "^20.10.7", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", "@vitejs/plugin-react": "^4.0.1", "eslint": "^8.43.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "prettier": "^2.8.8", "typescript": "^5.1.6", "vite": "^4.3.9" }, "name": "react-admin" } ================================================ FILE: examples/react_admin/frontend/prettier.config.js ================================================ module.exports = {} ================================================ FILE: examples/react_admin/frontend/public/manifest.json ================================================ { "short_name": "react-admin", "name": "{{name}}", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": "./index.html", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: examples/react_admin/frontend/src/App.tsx ================================================ import { Admin } from 'react-admin'; import { Layout } from './Layout'; import { authProvider } from './authProvider'; export const App = () => ( ); ================================================ FILE: examples/react_admin/frontend/src/Layout.tsx ================================================ import type { ReactNode } from 'react'; import { Layout as RALayout, CheckForApplicationUpdate, } from "react-admin"; export const Layout = ({ children }: { children: ReactNode }) => ( {children} ); ================================================ FILE: examples/react_admin/frontend/src/authProvider.ts ================================================ import { AuthProvider, HttpError } from 'react-admin'; import data from './users.json'; /** * This authProvider is only for test purposes. Don't use it in production. */ export const authProvider: AuthProvider = { login: ({ username, password }) => { const user = data.users.find( u => u.username === username && u.password === password ); if (user) { // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars let { password, ...userToPersist } = user; localStorage.setItem('user', JSON.stringify(userToPersist)); return Promise.resolve(); } return Promise.reject( new HttpError('Unauthorized', 401, { message: 'Invalid username or password', }) ); }, logout: () => { localStorage.removeItem('user'); return Promise.resolve(); }, checkError: () => Promise.resolve(), checkAuth: () => localStorage.getItem('user') ? Promise.resolve() : Promise.reject(), getPermissions: () => { return Promise.resolve(undefined); }, getIdentity: () => { const persistedUser = localStorage.getItem('user'); const user = persistedUser ? JSON.parse(persistedUser) : null; return Promise.resolve(user); }, }; export default authProvider; ================================================ FILE: examples/react_admin/frontend/src/dataProvider.ts ================================================ import { DataProvider } from "react-admin"; import axios from 'axios'; const apiUrl = 'http://localhost:3000/api/graphql'; export const dataProvider: DataProvider = { // Fetch data for post listing getList: (resource, params) => { // Paginator status const { page, perPage } = params.pagination; // Sorter status const { field, order } = params.sort; // POST request to GraphQL endpoint return axios.post(apiUrl, { query: ` query { notes ( orderBy: { ${field}: ${order} }, pagination: { page: { limit: ${perPage}, page: ${page - 1} }} ) { nodes { id title createdAt updatedAt } paginationInfo { pages current offset total } } } ` }) .then((response) => { // Unwrap the response const { nodes, paginationInfo } = response.data.data.notes; // Return the data array and total number of pages return { data: nodes, total: paginationInfo.total, }; }); }, // Fetch data for a single post getOne: (resource, params) => { // POST request to GraphQL endpoint return axios.post(apiUrl, { query: ` query { notes(filters: {id: {eq: ${params.id}}}) { nodes { id title content createdAt updatedAt } } } ` }) .then((response) => { // Unwrap the response const { nodes } = response.data.data.notes; // Return the one and only data return { data: nodes[0], }; }); }, getMany: (resource, params) => { }, getManyReference: (resource, params) => { }, update: (resource, params) => { }, updateMany: (resource, params) => { }, create: (resource, params) => { }, delete: (resource, params) => { }, deleteMany: (resource, params) => { }, }; ================================================ FILE: examples/react_admin/frontend/src/index.tsx ================================================ import ReactDOM from 'react-dom/client'; import { Admin, Resource, List, Datagrid, TextField, Show, SimpleShowLayout } from 'react-admin'; import { dataProvider } from './dataProvider'; const PostList = () => ( ); const PostShow = () => ( ); ReactDOM.createRoot(document.getElementById('root')!).render( ); ================================================ FILE: examples/react_admin/frontend/src/users.json ================================================ { "users": [ { "id": 1, "username": "janedoe", "password": "password", "fullName": "Jane Doe", "avatar": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4gKgSUNDX1BST0ZJTEUAAQEAAAKQbGNtcwQwAABtbnRyUkdCIFhZWiAH3wAIABMAEgAWADFhY3NwQVBQTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWxjbXMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAAADhjcHJ0AAABQAAAAE53dHB0AAABkAAAABRjaGFkAAABpAAAACxyWFlaAAAB0AAAABRiWFlaAAAB5AAAABRnWFlaAAAB+AAAABRyVFJDAAACDAAAACBnVFJDAAACLAAAACBiVFJDAAACTAAAACBjaHJtAAACbAAAACRtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABwAAAAcAHMAUgBHAEIAIABiAHUAaQBsAHQALQBpAG4AAG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAMgAAABwATgBvACAAYwBvAHAAeQByAGkAZwBoAHQALAAgAHUAcwBlACAAZgByAGUAZQBsAHkAAAAAWFlaIAAAAAAAAPbWAAEAAAAA0y1zZjMyAAAAAAABDEoAAAXj///zKgAAB5sAAP2H///7ov///aMAAAPYAADAlFhZWiAAAAAAAABvlAAAOO4AAAOQWFlaIAAAAAAAACSdAAAPgwAAtr5YWVogAAAAAAAAYqUAALeQAAAY3nBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbcGFyYQAAAAAAAwAAAAJmZgAA8qcAAA1ZAAAT0AAACltwYXJhAAAAAAADAAAAAmZmAADypwAADVkAABPQAAAKW2Nocm0AAAAAAAMAAAAAo9cAAFR7AABMzQAAmZoAACZmAAAPXP/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAkLicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/bAEMBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAIAAgAMBIgACEQEDEQH/xAAcAAABBAMBAAAAAAAAAAAAAAABAAUGBwIDBAj/xAA2EAABAwMBBgMHAwQDAQAAAAABAAIDBAUREgYTITFBUSJxgRQyYZGxwdEHQqEkUmLhFRYjkv/EABoBAAIDAQEAAAAAAAAAAAAAAAIDAAEEBQb/xAAkEQACAgIDAAEEAwAAAAAAAAAAAQIRAyEEEjETBSJRcTJBYf/aAAwDAQACEQMRAD8AuIIoIogAooIqygorB72xsc97g1rRkk9FFrlfnVD3Rwu0Qj5uQyko+hRi5eEjlr6aEkOlBI6N4rSbpCG6j4R0zzPoFEIpZpn+Ahv+TuJ9E9Qtipodc0haD15ud5BI+Vsb8aXoq/a6GgJ1UsrgBknTj6rgp/1JtEkgjnbNTk9Xxkt/+m5Cbrvbam9sc2XeU9G0jTFry6Q9M/hddk/T6kpId7UwNmqJCOBOGxjoD3KLvIroiX0VxprhCJaeVr2njlpyutR+k2dlttQZ4KwMZpwIGjwD5lbI785leaSppZWHmJG+JpHfKYppgOLQ9pIAhwBHIpIwBJFJBQhgiEAiFRYQkgmzaCuNDaZHsOJHeBvmVG6VkSt0Me0l93spoqY5Y0+N3Qn8JgjdrcA3xOPU9f8AS485Jy4c8ucep/CzirNBIh4D9zz1WGU7dmyMElRIIXspeBw+fGeJ4N8/wtsEstTUtHFzz1Pb7BNVHvJQA0EAnOo83fH/AGpBR7qniw14yfekz9+qFMNxHiljYwjhqc3kTxPxwnaIZA+6ZoJWDGhjnZ68gnaEvewftHw4BOixTVGc2GsOdI4c3KFXmSuq75Rw0kQ3EWZKieQaQG8g0D4n7KauaGgiNu8eeJJPBN89JLUN8btWg5HDAL++OwUZSMbTWxV9vZNFqxyIcMEELuTdTBtNUbiP3A0D5BOC0QdozzVMWUksoIwTBFBIKEMlFdtJSIKaMHHEuKlKg+3Ly6rgizhugl3wHVLyv7WMxK5Ih8k2+IAJEecNGeLvj5LvoossDsZHl9E2x6ZZNTvDGPp2WFde45P6WlqI4tPAZIGT6rAb0vwSiOox4RgA+84nn6ruiuAhLdA1n+5x5Kt6SsrYagtqqkyuJ8Jxj+FLpIql9qM0b9GW8Ceioaoa2TigrXStDsMb8SOKeYtUgGpxLe5+wVQ2eouftIabs52T7ugHPqrOtNNX7gb+r3zSPE1zNJPljknQaehGSFDo6piax5Em7iZ78h+gXJV1OId7lzIgzIYOePyue4QvxGGs8OsAN6D/ACPfH1QinirgZmODoi4Mb5BFYlqjfSsc58kjxgkN4dua61wWWqFbb2VGCDM4uAPMN6LsJ4p+LwRlM8pZWGUU0UBFYhFUWFV/t0S67xR8muhGT8MqwFDNvKJz4IqxgyWjQ49uyXl3EbidSKxrp31VWaKnJaxoySO6aX7INZG9swlcHuD3OyMkjlxKdLWA2uqJH/3NH8KRVNWz2XJ7LCm1tHSUIyWyL0NvdC6CnBeQHjRqOSB2VwVdnd/1YwRt/wDQx8Pkq4sX9XdoXubpZrGCeo7q7i3eW0OjbqLW8B3RRjdkm+tJHn6usdbV1JY6rnpmBww5jT4QPLn6q09jLdcKBsRgv0lZSFgaaaoYXBuBza4kuB7g5HwCa6y50tRWvi3ZjeHYc1wwQVMdm42NaC3lhXBu6JkikuzHS4QuNuqCPf3biPPCqj9OdonubHa6txLyXkPJ65JH1x6K27tKYrXVPAyRE/A7nBVE7OUEtJtDSs0k6JwHO8zghMemZ6bjZcdqjZT0jo2DAY4tC68rVBHuosY4klx81sWnGqiYcjuRkllAIhGCBFBFUWFc9dSR11FLTyDLXtI8l0BJUXZRt4tNRZ7hPFI3Trw5p6HHBcEk0jgwO93l6q3tsrJ/y1oL4WZqYTqZ8R1H0VRVEDKqikgkaQRkdiCsOWHWR0+Pk7RFRzVNHXQmNwc1pHAHBVq2m+19QIzTgMhaMFsjclx+ap2wW6gn3dNXvqIpA7G+DstcB9CrVtVqsVDa4Zpa2eUmPIDS4knIzgD4FUou9Dvtqpe/oZ9qrfUR1Elw3ZDy7UeGAVKtibgKi3skzwPDj0KiV7ornc6xk8EtdTW+QhraSZ+S49SW8cAeamWzlsFupBG39ztXkotSKl/CmO21N3pbLs7U3GtL/Z4tOsMGScuAwB6qK7JUrbs1t6ex+5kOuESBoceJ4uDeGfJP+1NpO0NLSWyRgNE6cS1Rzza3iGjzOPknKCnipadkEEbY4o2hrWtGAAFojj7O2YJ5eq6oyKCywlhaTIBEJJKEMQigEVRYUViioQJAIIPVVrtzYW0FY2507cQVLtMoH7X9/X6hWVlcl0pKWvtlRTVuPZ3sOs5xpxx1A9COaDJDtGhuKbhKyjW2uT2newSuZq544g+in2ytGYpGzTPMj28WgNAwofRV0cFU6F7w5oOGudw1DoVPbRdKCBrdU0eojg1pyT6BYbr+zsd5dKRIvZN6/ey4yPhyXRTx+PDeXfssaZz6wBzssj6N6nzTiyJrAABgI1vaMrdaZp06SQTniktkvv5WtbIO4o5+RVJgwkikjAAkigVCGsJZWuSaOGMySvaxg5uccAJiqtqqeNxbSxOmP9x8I/KCU4x9DjCUvESIJuvG0Fp2fp2zXWvhpWPzo3h4uxzwBxKi1XtTcJGnS5sDe7Bx+ZVD7WX+p2gvs1TNM+VkZ3cOp2fCD9+amOayPReTG4LZaF2/XVjKmRlotTZIG5DZal5Bce+kch5lcLNqL5erSJrhXPd7SNRiYA2No6AAKocHGOqs21NJs1K3HERgfwg5T6xSQzixTk2zAtEziCn6wwbmoa4DHHoE1tgO9BA9FJLfTua0ODeK5sjpw0WFaagua0Ek+akAcC3KiNnbI0AkKTsfiLJTsb0IyrZue3U3yULuW39stV+fbKlkmmMAPmZ4sPPTCeNoL9HZbPUVbiCWN8I7novPE9TLVVktTM4ukkLnuJ6kldPg4fkk2/DncyfSKr09C2zaizXZzI6SvidM/lE7wv8AkU7rznsdUvG1Nvbk5FWz6r0O2XuPkj5EYYpJJ+isPfIm6NqCQcDySSk0/A2mvStrpepLtWu0kimYcRt+5+K1sZlqa6LmMp13ga1cuUnJ2zrQioqkM21VULfs7VzA4foLWeZ4fdUnjAHmrI/Uiv8A6OmpQffeXkfAD8lVyBlkY75K6XDhWO/yc7ly++vwGMZljGM5I+qvWlpab2eMNiDQGjgAqNb4J2EftwR6cV6Et0Daihp5WjwyRtcPUIebGkhnCabZjTWykmOHsHDkU7U9BHF7ucLXFTljuSc4W8srnUb26OmmeI2Dgt7qzIw44C1BgwmLaq7w2a0S1Dj4sYYM8S7oEyKb0hcmvWRD9S9pI6qaCz0rsiM7ydw79B6c/kq/Mni7cM/RYmd9XUyVEpJe4l7s9StD3nWT105+y9NxsXw4lE8/nyfLkch32Lk07X21zjw3+r5L0HHUa25yvPWxzc7YW9nYk/wVdrKgsAHRcn6lKpx/R0+BG4N/6P8AFL4hxTg2IvZkc1HqSoy8EqUUUgfGFjxt3o0ZUq2f/9k=" }, { "id": 2, "username": "johndoe", "password": "password", "fullName": "John Doe", "avatar": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t////2wCEAAIDAwMEAwQFBQQGBgYGBggIBwcICA0JCgkKCQ0TDA4MDA4MExEUEQ8RFBEeGBUVGB4jHRwdIyolJSo1MjVFRVwBAgMDAwQDBAUFBAYGBgYGCAgHBwgIDQkKCQoJDRMMDgwMDgwTERQRDxEUER4YFRUYHiMdHB0jKiUlKjUyNUVFXP/CABEIAMgAyAMBIgACEQEDEQH/xAAeAAAABwEBAQEAAAAAAAAAAAACAwQFBgcICQEACv/aAAgBAQAAAADX6pYoNMMEMQvRe+eB+D4EASSyFak00wYxC+b86V6li1h6xe/ghAUWWpUGmDGKO5qp3BTO6zOKQy5O1ct+8AWAB5xpgxQDkw+xsqAXM4QKo2OX9s7MF8Arw84Znv3MzFNrkP8AnHRLBUzSgY9dbbUageywnHGC+h3COtrbrVDNrLiyWIIUBPTCz73tiorePGZ77z55KntLoGw5+4qq4Y08L6XaFu6N4a2od99H3Xmnk5Jc2wWnSVjZnzTDIi61ruyXT6vaL3TmjT1fZc2dlmhdrdM5S/rfsY4BjVnveRbXediVlgXvIuWYDDoq1cR6QtGfu7kKvK9Xkn8pM/P+pFeVun+FdU0tDOnFh5FsWTXTIz/CTE7QXkTnpJrpIqrrtHkda0pVWh6Csywc3pJLrHQUwRs1Ut+WRzWPQLROT4toepGy94vzV6PT/n3ojfGkBs5aCIZh9d1rVL46iuoml5WsyXrDZUah83WoG5gm2aa7PbgjkCdwWnEqRZq6ETiJrVLYpj8DQSGdoGQ5XQ4I4rkTf8pYbnhdokWEfD6jbrwoaUv57o+Q2JVlL7Nhb3Im7jTrqz/tgHZkrC3tJxFc5LlBTT8rWrPTlXuG+du4rImJFT5P6xSB8k3oiyUKk44Y/ffE/EBd1KaMp4t0d0vT1rO594WQ+fBF8X8UTzD5GdB9sZ/yHc/VactaPiTYG/tL+J/Bm/ehhPFTA31i35a2ibu01YTS5R782Ee171cspT8MY+fXKKrGn65+hdo7alYJOTKpxxw5QEG/pSNOHRPI6o/E9bsct/TZbbarZ1rvOl8W/M9TRH6TVKblDgqRoXVC0JD/ANV9mEtrU1zByLKwtwCS/o/a+H9HKZfFXAI2j3uF0rMH7594EJPBDCH/xAAbAQACAwEBAQAAAAAAAAAAAAAEBQECAwYAB//aAAgBAhAAAAD7PjSlfZYkXm1hs6V51HkM46smbD51V81FIMOLcbc9lGQq1fVuWz9vyCpc/wCJaHrjDHIXVcGvGe8u2wIupayPqSrG6kLDYcVHnM4xt1URULr0MHyJqQQkW3JYstIJ9PrruQch27bGtZk1t7hubMN7M4FZu43rKXnxnJnQ5890cTFFGXvea4//xAAbAQABBQEBAAAAAAAAAAAAAAAEAAIDBQYBB//aAAgBAxAAAADwyeR7+yzjxJrSJXP1OgmKo8YMmESPuNUnyBV+cGi0syIMt7PlIHmF3WFcM9Goay2BBxNpkvRHgzeq5x1fFpfMSxbaBti60lqj+1j+Hu5G4tMPzA7BXkRQN9OEHErM8xQcSj1Ooop+YORySCqH+m31WFjawywhqY+N0t0XSBU8N7nHu6ribqQMf//EACUQAAAHAAIDAAIDAQAAAAAAAAECAwQFBgcSEwAIERAUFRYXGP/aAAgBAQABAgAqZUipAl19fX19fDhw6+vhwEgkEnWKYpimKYplSKkCfWBATBPrAnDg/eON8/6WH2ZZe0Fb9g42T4cOsSdYpimVMqYJgQCAThwm56U3+7bdJPwjYuCPW35TNM70au2ICcBIKYk4FIUgEAgE4cLnaLRPQLGThywrpSMQm2DpB1JnQzq/0W5D4HgkEgkAgEAnACceO9Wdu8ippq5lp9KRoaMxYE1khcuBJgV0mNMhNEjXwkEgEAgEAgF42uct08zCQtLuYWkoNjKRr1mBFEAaLCh5HoNazWGKyktcgEB5Afnz9n7W9EHXaqxMFeeup1nT5+SWRFqo2VLljuCtEe/ttkeSH9sUvCF0Pcj6KF13qbWL+rXs0Jh6PrNWvU+QwGzZZKwzt8qkdg+SZ1yGPCy9ydO3kwwuaWfUq5xhiRbXWaDVqfQ8Xj2iaSQpmWHUHL+vjnjPJnmcS0K5spZXIJHZ137ljXArL9adm1qzVqvZatHwzN0z8QMVNMpxmK4hmS9PY150w1ZNo6dusSca2c8bfJdlqNbC4QrCSrHiUPORhq7DuI9UqpVVQKPxYnF3EWqnvaUlVMwi9QTNFFjgqxHdwAxUr1XNI1PVajrku+iNOX3KEs8AjFvgBfxDy02K/Z5aISOgq9G3iAXgZuQslvqBnUbK11jXmMDZ6M9jMEJaM3n84qWGVXKkWCgrHaGn4GXcIOgeoKuTD4qLmIaQsYnMokYNwMlq6vr1CHM/jC1lqkZdRVY8Yo2Rvi6NZVhhXM7L4dXmmAKiINgbGHU42hQyvkjIoOxVMsU8i4iZKLvEIANHEYSvDXU6/wDx6rQ0g3cFaKeF8FCVjaVfpTcmhmzVPwyEg+l5ehrmZG8QlkpQH6TpN4urILfxUTGuEVWsU1kWEXHad5TqyzX/ANGr8vJO7FYJm0Yyi8FaCSgCQpIn9EWoRYQ5IgsaLYGJWQNgab7mlRpsFk3+AQ538zOS5FavHPDRQdHUJBIJAaFa/r9AI9YF+fVyXRagScJD2WNs87ZrRjlJYkceaFbKVfhX7zLir3i5/aByLj9oXAuDL7l7SwEtWtQgNmsu4Sc/mWYx7VApiyTLSM7p/tLTd7+iIOBWBUFe0VBUtNy1/wBnfoDDSEZXY2kQ0LCtYhqUqAdOhZlLRSw5f7BU/UwW7e0FOZ19Q9obPMP2HgeZYtI0it1muVVSGQbFFsm2bgX2wyUDgimcEyh4BtD2W/bRDNHyqhHMcZKruqjQ29KO1BJ0ikZig1Q8nIbV6IUwj2kPJSuo+yxUY1FBRZQBcAJEwz+eAVURj3UYhCtmf59lsxfMxADzE7quvoopHQJ9bEMRM6iHz02uwG+8vvn38qF9jssg47//xAA/EAACAQMCBAQDBQYFAgcAAAABAgMABBEFIQYSMUETIlFhFDJxBxBCUoEjMFNigpEgJKGxwRWyNEBDcqLD0f/aAAgBAQADPwD2ofvhQH/kR99lYWzz3dxHBEgJZ5WCKB9TX2XRSSoNXaQpneOCRlYj0OK4TGrTwGzuGtV5BHdJuGJ65TY1wwJLrGl3johIiIwGfA2znZc14s/LJww/JnqlxuE+jCuGNS1KeO9AsY+YiIyNk8vYEAHLVp+o24mtZhJGejYI/en79D0SzN1qWoQWkI/HK/Ln2A6muF0mSLTLO6v+Z+UShfDT+nm3ate+FeGyUWTMDl1YSSgfy/hX61rOqSNLdXE83OdvFkaRnP8AVTjBZs5GcCrm/eXkISKADxZOy57D1Y1Etkk7xsFkGYE6ZUHHNjqebsa5IxDAMLv4kijdyNsKfSpBOsKoTJJ8qgZ9q4g4L1yBjPK9lz8tzaFwysvflG4DCtF1/To7zTryO4hcAhkYEjPZh1DDuD9wofu7ThrQLm/mAYphIkP45H2Va1TXLmDUb29iuLlsmGPAZIFyfPJ2B/KtSWuny3CIwjkID3cqhpJC3RY1PT2AFeFA17qUiwRsQILXPNLKTuCwG5J9KaOCWe6TwGVA7oVyY0b5QR/FbsOwp3kWxtYB8VcEc/rGn5N+nqx6k1ZWtpY6Rb+HK6I094cll5mbdpCOwAAx+LoKnMlpbIzLNcN5S48ybZLEey9B2yK0ZDKsPhg28jq5LcwRIV83132HqavLe8uJbacWqTKijHnkbC4IX/8Aaj+CxDpzlc5aSRcPk9ixI/sBVzwXxCt1Ekj28mEu7dScSJ6r6OvVTWjcV6Il9YT86hikgI5XVuoDr2JFKOpFKeh/dGvj9fj00TFbKwTmmZepkfY/r2FKW55SIoQfKvQCtT1NxLGn+XtkJ52B5UQ7Zb2PYdWqBb06nqErSP5vBMm5X3x606aSGhQm4dyyhhkQAjJmmJ2Mp7D8Io28cgh3eZiDKd2Pq2TUWm2z39wQ6Kwc87BA8g+UEn8AqwvNQur/AOIubl90VIE5UGW5zzStgZZvSlmR40VYA3znmLN6+c9BUETElM425+bdqjZWwhiUn5FJwf1bJJoDDFjv03oaFxRNaz3gS2voCgRzgeKp5kpYZnSJHkOe1NPOiTRPHn1qK6iUg/4Tj/BbaHw/f38rACGI8v8AM7bKo9yamvdTuGkZSfEZ3brlj/uFq41K9jQABATyqTtgdWb2HUmrW2sI9PsSWgUh3cjlM0ndz6IOwqaVg7y7qMA9lA9B7dhV7qCRW6F0hBPKmc5zuSfVid2NWpvrZEVLufvzE+Ch9yPmx6DarvUyzvcOLG2AMk7+UNj8ESbBR6mtLtiGuf2ZbzR2zMQ5VvxnPyj/AFNSSQGFIJI4kw7KVKIue5+vvuaOQyg8pJ834mP/ABWeZ2ZQR6/8etMBkMPMcep/02FFJo3ByUdWOAMgg5qG/toLhAD4kaNsNskU80asBhquraNFkbJFCKLmara1YjnApB3pPWk9aT1qP1qPHWo7XT9H0pd2mlFy49oztTOVUk5PzH6bmmhiMKeXnA8Qj8vZfp607u2e9TcscjghXOI1x1x3p+UoCFGwY9z7HH+1C0Y7BlA86nuPQ4riLUZbcW0It4o2/wAtGAMI38Ug7F/Q9Fq1hZ7q+vGknzzTXDEkJ7KTks59asBbRQRW3gwqfJboSHlb+JO+Tj2Ub+tM6AsoHYKBgD6AbAU6ly7F8dcDAx+gNINgSS24HrTK+ASF/KajtOCLOWW6SRGVjGuMMm+4J71ZXBHIahJG+1R2tmx5+1G7vDJK5wzf2FRCTHK1WMK+ckfU1bXKlogWH1qBFOQR+tWCNgn260DEH8NuWpNW4ytJMgRQ2aIB+pY0PiN8/Mc/qalNy45cksK4i1S7gVLOQK7ABipxWp6lPp8Aj8NbW1YEkVxNdTeHbWzci/8AqtsG96t7OFHv7oyuCDyJgLWmLbP8PZM8jDZnlK1fWKtJcSGBY+gEmd/0FQ2vNyO4Xl3YrvVvJbRgRsAqnnbO59SRUg5uR2IzgMM7igsQYxhwvYHB33zkdPUUGO0fmw2fqN6bRuHtOsjIGcQRmT052UZxRsVJUDFcRX9xL4EWYU2yK1cSrHcKyjqB60QcZoxvEUlQsQc1qUsqRQOSxI6CrkaBC8rsHKb8wq5tryUJP5QemKvF1RGaR+RZFJB9M1a3VlnxcgocL65qSy0cak7nBAUAg718eY3ljLAjKA9cVp0t9baheRq0SOD4Z/FWmRW6JFbRqqgABVAwKiXcRgZ64FAAVsctk0ChzUiwyr4fNkEFa+MdJVjZImAYqTjGTiryG4jSNCVkkZA5GAAKjmjPIckYLe6mpVicRxEKA8fKfmr4W/VJYlyJMt/fGaku7tmLbFsADsBUawS56gCtMuNI5UC8w+f61bfFQ8gGQDTeMuM9RWk+AheME471w+JObwI8/QVp1nZsF5VAFafqWsSqFK8r4znqK0Wcx83ffZq020ELiPcDvWka9w3dWMsSMrgEKduhzitJ0fjrUbL4UrDC2IY/mwuAVrWQ6xwaIFj6rI7Ba1HlUzXUKn8ibgfrTFMZzTlRmtutA4FWl/zMQobp9RVgtskTtlVYlQO2TmrMlRLErlOhqG0duRQANh9DUDKxZAah0/iVliU8w53yRkDmNHxV+tOUOCRtXlvEycls1zXdsvoKMhDcpriex0yJ7GYBsHqCa+0+5vJYDc4KuQcIa4o1rTEe9uHOeYEcpFXOl380kQdQAD8ta9I0bq7hQ4OeWo57WJmkySoqNl8szjPvUVh9pc4lmZFeCN0b2I3q2mBdtQuWU7gLJy7VZWgWFIJQNyruS4JHvReMkEHAp2xivPymRQ2Omd6Yd6bI2o0SooOSAvU0GjzJKsKAFmY9gK+xniG/eKe8u4b+cLDFd5cIW/DgHykVNpWs3dlOoMlrM0bEdDynqPY0k0JIWhZXV3Hy9TQe6tVxuDWIkfHQVYT2kayIpHuK4dgZpFtogx3zgVpdvJ4aNGuO2RWn3ERLhCD/ALVpENs2DGEANavYyBLWUgb4rV3tl8fBI6mrO04vspZY3BSyTzcuVYMxyAa4f1FreLKqDtvXw1nMIJcLLC/gsp2WROn+hr7T9JmcR6et3H1y2cj6GvtQecg6e0S7jypy/wDca421O6hl+IdZlO4MihmzXGaxBp7ieQls8xk7dxUk45ZEZXA3zWwpcCsXBPXArh9NZsNI1s3UcV2S4MQIjYofKkrDcA1o+p6NA2lgW8wkjKOCSCOYU2p8T6rdIfK8uAfXkAXP64oxZBFLb3chK/Oa+NeLkG4Oc1N8KFA6VrkVlCbebFcdxGaNbphvsQla5eXplvLiVjk7nIq8uIlRVkbC1rKStmCQITUks8ZZelKsAHLWkatYlb2wFz4YYx/hYEjsa4s0TV3jGm/CxByU5Yy2V7HLHerrjLStUt78SI1rKgU+7LXFmnJILe38aMEkPF6VxBq2tKtwJ3RT58q2QCf9K4OZ7eGeW5Zw4aRTIUG3staBpgSSxvtTtTknlivZCmD+Eo5YUkSKOcsQOp6msbVzNSeN1BA3rSddtHS4thKyAGMjZ0cdCpFJpPC8iCUFok5Ac5w77AfUdagRMYpA5wK8wIFF9yKiKEctKyBD0FWsysWVTk71aQybIuM1ZqozyjarF4SPKTioVkLBe9IFxikJAxSQ6lFHyDEluhBx3yRUFjwdFOsWJL6eSaRu+AeRaUnbatMuseLAjb5Bx3FaZHKZEiUk9yASKSFQBsewrfrWc0FRmPYU3j3ChuZR09jSxF1EeARuCc5/Wnv76OztVIgt2JJHRpDUxALE14G+DXIOUCm2BU0WOcGiaz1FKOpoDvSv1akNInekA61JcxWU6LkgmH9WOVqPTOH9PtE3EFsiZ9wN6YNtS2ssayjAbYNmk9iKUUS2DWAKVYm33IqaOPUFGC8cReP32yK1nXnSGK3ECRKDcSZyT7L9as7gMTgkEg/pVrjYCrWQbqDWn5P7Ja08naNash0jWrhTipYxmlU4p5zhRV0BmrpW2q+l+WrqIZepr/TbhVXLIPEX/wB0fmFaZdabGyTqXWMB07itHg1XwbTT7u+cAoDCmxYnoM9an1uzWe/txbMyn/KswZkB/PjYN7Vd6bMqFzNbsf2bHcr/ACmkkUUgHTpQht2dGBKkgj/ipU8Ns7S25KqT3P4TTXF5dgksoIxtsParO1jKwQJEGOWCjGTVxYarcLExCl2JH13q8IGWqUjd6lY/Mal75rbcmoMghRUTRsoQ5NPPJkhgPYU8U+6HFfsRyoSauGOfCYGpA/mjNPLFgRjNTwA5TNcSWn2karY6bPc2UAmJREkKZVvMSSK13Qo45JOIraW0urKPxJVlDZlc9B3PKKvbf42S34gtpZ+dTGjyhWfm6g59gK1O0uWtpWyYgCoZwWBA3yPxA1FqVhDOIzGWQEqTTOGEUrIwXIxv+hq5t7TUw/OghClwPyucZB9vSreW0lSaVTEYFdG6cpjqebSb/UWJ5by8keHP8MbVlUpLi8lm/Ngf2qNe1RjqtIv4aUdq6DlpKhY7qKgXooqEHOAKX0pPSlHah6UD2pr+z/6zY25a4t0JnA6yJXG2rXpl02aI2SENI11LyooP/wAqh1RzHq2qQeAQpZreDlnc9eUSOWIWvs33mtLaaO4G6SNM7kH1OTT6BBHYXDr4YyqNnt2zmkMR8OXmZd25SedQP9cZq0FqXnkGHi5hK2CF7Mp9VPcVdarqjafCXAmeWB4QcqCcHKGodO0ays4gAkEKR/2Fftox6mlZ5lPsRSVGKSo6i+4ChS+lJSCk9KWlFQSxOkiqysCGB6EGm4Y+0PWtD0zURlQZORG5lUvuUf8AmAO9fahxBrAs7e5l+FQ4eVVCqMduarm1tIxJJlgu7Fs1zoSxicqpIVupxvtVhYacjSRRqUm5Ww2CF9QOuKn1bU2tLeR5ELFYyTnIk9xTyXUF7IsoSLDhz+N8+UD2A606oebOc0Wu0rVOFOHbnWrO1W6+DaJ7mBtvEg5sScp7MAcg1oPF+hRalpVx4kTbSRnaWB+6Sr2YU3rTU3rRpaWloGgO/wBw9fuNJoVxc6Jws8U98mUn1HIeK3b0i7O4qZtfF1d3EsjvMZJZC+XcvuxJPc0NLtY5LZjsA/hAnAA2rSbu2ieSYLmDLKTgBm7CoYp5xzlkXK8udz6Fa13ibVDIbWRw4OOoHm7invr2NtQYoGfcA+YotWdraQwW8SoqjZVGwrCk4A2rNznHSre8tJ4Jo1kilRkdGGQysMEH61xX9lms213p+oXEVjfSS/BXMMrI48M58J8dWUGuNtKEaavDFq9uNi7YiuB/Wuxr7O+JnjhS/awum6W96BCSfRX+VqH3H1/xNXD3C+kSajrGoxWlsnRnO7t+VFG7N7CuJuK3uNP0R5tL0k5RsHFzcj+dh8q/yD7sEGr65ZoTI+yDJX0B71eTW+PjnCnzLgnrWmeOZZS7kgfMckfr71bwRr4cSYFXDSr4SNkEYbPT61KqhpGy1ARmszMaLsNq03jjgh9HuiEfnE9tN3imXoavdJ1i+sLqIxT2k8kMyEbgxkr/AGNGML5vI2wJ35T6N7VxPwm8VnfmS/01Nmt3bMkS+sLt/wBprgviuNTpmqRvMRlraT9nOv1Q0KHr/gSON3dlVVBZmJwAB3JPQVpGjmax4aSLUbwZDXbf+FiPt3kNcT8V6g2o6zq1xeT74Z/kjH5Y16AfSntfCJbmEicwOMffEnHGmRyorxXBeCRGGQyuvQ1NonLPADLYOchurQ5/C/t6GptQK+DGSG6ntWnafZqjwo8hUB2Izk1ahcxxKv02oxEb0TCa8oNc7KMdTQArwtTs+JrCHa5d4rwAdHbcMfZqjdWVhld1dTQRVVmyB8kncexqa2kjYOUIbKOrEFD7EbitutY+4VwXwTbst3c/EXxXKWMBDS/19kH1rjjjqc2s0wtLBjlbC3JVMesrdXq1eTzAMwzjPQU3xEqEdByrUbqInUFVKggjPasZ5EA5SQR9KkU7qakseI9Jugp/Y3cLkeoDDNSzWoa9AETLtHswZT61pGnQ8un2yQqOiD/ipYnOVIpmTpUqmv8AJnJ3rKKaCDmI3P3afrGlXdhdxCSGdCrD69CPcVf8HcXX1lMh5RO+DjZlbdXFA+X8wGM9KXlwclOhB6qfQ0R92laXp9xe311FbW0CF5ZpG5VUVqmqNPYcMmSxs91a8IxcTD+T+GtTTzM8khLOeZmY8zMT3JNKruwG2eWniuJGQ/KTSXM0DDYj5q5pZwdsv1opKHwfN1z+YdaVs5UUBuoGcbVb67wRw7qkLKVu9Pt5Djs3IFcfow+6KQYdQaQfKf0NSupwmau/ACPyg533qOFVHUj7t68x+lJxJwudUtoS15py5cDq8Hf9Vp7S5kjbYA7U5PMoBOMOPzCkrStG0u6v7+6SC2t4y8sjHoB/uT2Fatx1qrBTJBpdu5Ntaf8A2SermsouPmLCmWVhQVUA9zQWWT6mmVwfUGi3xYHXIxS3EMbY5uceYd8+1FD6qTsa22o6jwfqnD8z5l0qcSwevgXP/CvXmIoj9wrKykAgjBB3BFHh3iW4ltocWV4DLbnGy+qf0mr/AFDVoLCDkNxO4jhV3CB3Y7LltgTX/8QALxEAAgIBAgQFAgYDAQAAAAAAAQIAAxEEEgUhMVETIkFhcRQyEBUgQpGxIzNDUv/aAAgBAgEBPwA2Q2TfN83zdDdUP+i/zG1enXrasS1HGVYETdN8DzfC8LzfC/c4mr4qa+VYB+Y2uuswXcn+o97hfcxLmOM85Vc6MGBxKdSLF7H1E3mbzN8LTfN811p8MIP3dfgS2vJAxk/1Pp+YJloDcs4B6xQqj1ikdZXYVIIMFXEPE3eMCO3pAxwMjnPlgIeI0+8/MqfefmVPYx7d7Bu45R7aqxliBF4jQfWXa0BiRUx98SrVCwjJOe0Sw/EqfMNuorYAkYAlfE6TYU6vCpJzmU8RAqwahn5h4j4V3JQc9Y/Ene5SFlvECKGbbzAzLNXdY+WyZRacchNx2xC6McDOTPH1BHXE0V7mwqTPGsLHdNKVXXEnuYNbpu8retCS1KtnuY2NzMFHxK3zbkVjlDemorapFO7b0xGoZGwVwYiYGNw9/MI96jI6n2OZXtYZBhbAhuvDO1JQbfuBJ3fxDrSR93OJcC+4nrNw55aV6upc5RTK9VXaxHJRLnFb4DAwXKpDBiCPURyt9FZasDOPNEpNBJqIB7kQDUWnFrbhDsUYE3DdLkUKbMAOQRNjrzniN3m8954V0ZbBMWHqDPDcftM0Woxp61forGX2Fh/jzn45Su5mBBGCIxK9ZklpwfRafU1602P56qw657DrPqaHyNkU6foKhFr0+PsEbTlhBw/zA5j6EOfui6RQm3rKKBTkgAjHQy+jbdzuXJ7CO4qsXzlgesFociFQmB6maG569Q4B/wBiFD8dYNJV/wCRPAQdAJ4Mws5TImREBc4AnENIFHKsMzAc8T6UsCiIxPvK9NVpU32HL9puNj7jEJSwN1wYtW+sOhyCMj0MKkHBBmBNom0TAlelLc25CKiKMKMQ1iwhe55GcQOqotZHysNbt5sykYESotNIcAV/xHRWGCIdL2aZlVbWHkPkyuhK/c9/xDEEGaxzqmzYPj2h0RB5HIn09ofkpPKU1bFGesBIOREIsqDj4P4KBkCABVAAwIf0WjFjfp4ex8fb6MDmazUPVaAoHSf/xAAwEQACAgIBAwMBBwMFAAAAAAABAgADBBESEyFBBTFRYRAUICIycZEVQrFSYoGS0f/aAAgBAwEBPwBaoK4EnCcJwgx7j7Vv/wBTFwclvalv41HodG0ykGcJ05wnCCuBJxgQkgATD9HWwbtYj5AiYNFfZEA/zEoQt9BLKlAIHaW0VurKRuX4zVPr3HgzhOBnCBIFnAz0+odRnI/T7fuZXb2J3of5hyPcASklfza2fAh2T4jbl1QdSp8zeKBrgdzj7zfwpg9Ms+RP6XZ8if0yz5Erq6aFfr3ldF1rAKpMb0rIXx/J1Mf0/koDXIP+ZfhmrYAGvn33HrA+stTtOmlnJgO5MbCu47I0vzAyr2lvprNYGFpEuxwaVBJB8SnEtUMCQRML09WyEUntFwaaqvyaEvrBPcwr+aP0nRdsBoToY4JmfTWtHMDWogCr2l7FsCdGz4llbsBxtZZbQGrALHYHuZyNCkFy25g2rVkpYzaXcrtFtew3Jf3lyAsTxbX1QxcI9m5aGv7hqW8kbRE6ZPeY+FhstVeWLdW/oYAcP5MOERa4HdQxAMNB+78dQ43+2Pju2tORGxyieSZXiq3czoV61oTENtDWFLG0Ae3iNmjJ0L0JXt2U6Eusx0QdBOB+girYzbaFd1mfeXWk1c2KKdoD4OoCJsTtD0vBgRDOmnzAib950Gau0J3btMfH4EG4ALrvo99y+itSCp5KfMXT6AnECueqOQ1ArXSsxEOLav8AdDXYB+qHqfJi3KDDm9taiZnHxGyWL8o2QzkbYjuN6Mxqi+HzGNYFAB5EjX/sesOrAAbB8QVdMGG0udj2WZq7xg3+lg0OTb8zrP8AM6p/C7qg2TPSPVcg1BGyrErTekDHUbI4t1GtULLci3JbjWp4b94QK6wojgPWU3ra6jv07GRxog67dxAQR2P4bMlRsL3Md2Y7Yyq0owPjyJ6dVjXVqwAP7z7xUh46l52dgx7QuzMleZLeYGZT2MGT8r9llqIO8e57PoPj7CAZwmO70j8jEb9/rBnBgNjRgyaTWNsAQRL7uZOvaGWrxb7GOlJ+BCxZiTB9olRJrX8OQAaifiY1K2ISSfef/9k=" } ] } ================================================ FILE: examples/react_admin/frontend/src/vite-env.d.ts ================================================ /// ================================================ FILE: examples/react_admin/frontend/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": [ "src" ] } ================================================ FILE: examples/react_admin/frontend/vite.config.ts ================================================ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], define: { 'process.env': process.env, }, server: { host: true, }, base: './', }); ================================================ FILE: examples/rocket_example/Cargo.toml ================================================ [package] authors = ["Sam Samai "] edition = "2024" name = "sea-orm-rocket-example" publish = false rust-version = "1.85.0" version = "0.1.0" [workspace] members = [".", "api", "entity", "migration"] [dependencies] rocket-example-api = { path = "api" } ================================================ FILE: examples/rocket_example/README.md ================================================ ![screenshot](Screenshot.png) # Rocket with SeaORM example app 1. Modify the `url` var in `api/Rocket.toml` to point to your chosen database 1. Turn on the appropriate database feature for your chosen db in `api/Cargo.toml` (the `"sqlx-postgres",` line) 1. Execute `cargo run` to start the server 1. Visit [localhost:8000](http://localhost:8000) in browser after seeing the `🚀 Rocket has launched from http://localhost:8000` line Run tests: ```bash cd api cargo test ``` ================================================ FILE: examples/rocket_example/Rocket.toml ================================================ [default] template_dir = "api/templates/" [default.databases.sea_orm] # Mysql # make sure to enable "sqlx-mysql" feature in Cargo.toml, i.e default = ["sqlx-mysql"] # url = "mysql://root:@localhost/rocket_example" # Postgres # make sure to enable "sqlx-postgres" feature in Cargo.toml, i.e default = ["sqlx-postgres"] # url = "postgres://root:root@localhost/rocket_example" # SQLite # make sure to enable "sqlx-sqlite" feature in Cargo.toml, i.e default = ["sqlx-sqlite"] url = "sqlite::memory:" ================================================ FILE: examples/rocket_example/api/Cargo.toml ================================================ [package] authors = ["Sam Samai "] edition = "2024" name = "rocket-example-api" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] async-stream = { version = "0.3" } async-trait = { version = "0.1" } entity = { path = "../entity" } futures = { version = "0.3" } futures-util = { version = "0.3" } migration = { path = "../migration" } rocket = { version = "0.5", features = ["json"] } rocket_db_pools = "0.2" rocket_dyn_templates = { version = "0.2", features = ["tera"] } serde_json = { version = "1" } tokio = "1.41" [dependencies.sea-orm-rocket] path = "../../../sea-orm-rocket/lib" # remove this line in your own project version = "0.6" [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-native-tls", "sqlx-postgres", # "sqlx-mysql", # "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [dev-dependencies] rocket-example-api = { path = ".", features = ["sqlite"] } [features] sqlite = ["sea-orm/sqlx-sqlite"] ================================================ FILE: examples/rocket_example/api/src/lib.rs ================================================ #[macro_use] extern crate rocket; use rocket::fairing::{self, AdHoc}; use rocket::form::{Context, Form}; use rocket::fs::{FileServer, relative}; use rocket::request::FlashMessage; use rocket::response::{Flash, Redirect}; use rocket::{Build, Request, Rocket}; use rocket_dyn_templates::Template; use serde_json::json; use migration::MigratorTrait; use rocket_db_pools::{Connection, Database}; use service::{Mutation, Query}; mod pool; pub mod service; use pool::Db; pub use entity::post; pub use entity::post::Entity as Post; const DEFAULT_POSTS_PER_PAGE: u64 = 5; #[get("/new")] async fn new() -> Template { Template::render("new", &Context::default()) } #[post("/", data = "")] async fn create(conn: Connection, post_form: Form) -> Flash { let db = conn.into_inner(); let form = post_form.into_inner(); Mutation::create_post(&db, form) .await .expect("could not insert post"); Flash::success(Redirect::to("/"), "Post successfully added.") } #[post("/", data = "")] async fn update(conn: Connection, id: i32, post_form: Form) -> Flash { let db = conn.into_inner(); let form = post_form.into_inner(); Mutation::update_post_by_id(&db, id, form) .await .expect("could not update post"); Flash::success(Redirect::to("/"), "Post successfully edited.") } #[get("/?&")] async fn list( conn: Connection, page: Option, posts_per_page: Option, flash: Option>, ) -> Template { let db = conn.into_inner(); // Set page number and items per page let page = page.unwrap_or(1); let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); if page == 0 { panic!("Page number cannot be zero"); } let (posts, num_pages) = Query::find_posts_in_page(&db, page, posts_per_page) .await .expect("Cannot find posts in page"); Template::render( "index", json! ({ "page": page, "posts_per_page": posts_per_page, "num_pages": num_pages, "posts": posts, "flash": flash.map(FlashMessage::into_inner), }), ) } #[get("/")] async fn edit(conn: Connection, id: i32) -> Template { let db = conn.into_inner(); let post: Option = Query::find_post_by_id(&db, id) .await .expect("could not find post"); Template::render( "edit", json! ({ "post": post, }), ) } #[delete("/")] async fn delete(conn: Connection, id: i32) -> Flash { let db = conn.into_inner(); Mutation::delete_post(&db, id) .await .expect("could not delete post"); Flash::success(Redirect::to("/"), "Post successfully deleted.") } #[delete("/")] async fn destroy(conn: Connection) -> Result<(), rocket::response::Debug> { let db = conn.into_inner(); Mutation::delete_all_posts(&db) .await .map_err(|e| e.to_string())?; Ok(()) } #[catch(404)] pub fn not_found(req: &Request<'_>) -> Template { Template::render( "error/404", json! ({ "uri": req.uri() }), ) } async fn run_migrations(rocket: Rocket) -> fairing::Result { let conn = &Db::fetch(&rocket).unwrap().conn; let _ = migration::Migrator::up(conn, None).await; Ok(rocket) } #[tokio::main] async fn start() -> Result<(), rocket::Error> { rocket::build() .attach(Db::init()) .attach(AdHoc::try_on_ignite("Migrations", run_migrations)) .mount("/", FileServer::from(relative!("/static"))) .mount( "/", routes![new, create, delete, destroy, list, edit, update], ) .register("/", catchers![not_found]) .attach(Template::fairing()) .launch() .await .map(|_| ()) } pub fn main() { let result = start(); println!("Rocket: deorbit."); if let Some(err) = result.err() { println!("Error: {err}"); } } ================================================ FILE: examples/rocket_example/api/src/pool.rs ================================================ use async_trait::async_trait; use rocket_db_pools::Database; use sea_orm::ConnectOptions; use sea_orm_rocket::{Config, rocket::figment::Figment}; use std::time::Duration; #[derive(Database, Debug)] #[database("sea_orm")] pub struct Db(SeaOrmPool); #[derive(Debug, Clone)] pub struct SeaOrmPool { pub conn: sea_orm::DatabaseConnection, } #[async_trait] impl rocket_db_pools::Pool for SeaOrmPool { type Error = sea_orm::DbErr; type Connection = sea_orm::DatabaseConnection; async fn init(figment: &Figment) -> Result { let config = figment.extract::().unwrap(); let mut options: ConnectOptions = config.url.into(); options .max_connections(config.max_connections as u32) .min_connections(config.min_connections.unwrap_or_default()) .connect_timeout(Duration::from_secs(config.connect_timeout)) .sqlx_logging(config.sqlx_logging); if let Some(idle_timeout) = config.idle_timeout { options.idle_timeout(Duration::from_secs(idle_timeout)); } let conn = sea_orm::Database::connect(options).await?; Ok(SeaOrmPool { conn }) } async fn get(&self) -> Result { Ok(self.conn.clone()) } // DatabaseConnection automatically closes on drop async fn close(&self) {} } ================================================ FILE: examples/rocket_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/rocket_example/api/src/service/mutation.rs ================================================ use entity::{post, post::Entity as Post}; use sea_orm::{DbConn, DbErr, DeleteResult, entity::*}; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/rocket_example/api/src/service/query.rs ================================================ use entity::{post, post::Entity as Post}; use sea_orm::{DbConn, DbErr, EntityTrait, PaginatorTrait, QueryOrder}; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/rocket_example/api/static/css/normalize.css ================================================ /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined for any HTML5 element in IE 8/9. * Correct `block` display not defined for `details` or `summary` in IE 10/11 * and Firefox. * Correct `block` display not defined for `main` in IE 11. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } /** * 1. Correct `inline-block` display not defined in IE 8/9. * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. */ audio, canvas, progress, video { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9/10. * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background-color: transparent; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* Text-level semantics ========================================================================== */ /** * Address styling not present in IE 8/9/10/11, Safari, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari and Chrome. */ dfn { font-style: italic; } /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9/10. */ img { border: 0; } /** * Correct overflow not hidden in IE 9/10/11. */ svg:not(:root) { overflow: hidden; } /* Grouping content ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari. */ figure { margin: 1em 40px; } /** * Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Contain overflow in all browsers. */ pre { overflow: auto; } /** * Address odd `em`-unit font size rendering in all browsers. */ code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } /* Forms ========================================================================== */ /** * Known limitation: by default, Chrome and Safari on OS X allow very limited * styling of `select`, unless a `border` property is set. */ /** * 1. Correct color not being inherited. * Known issue: affects color of disabled elements. * 2. Correct font properties not being inherited. * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. */ button, input, optgroup, select, textarea { color: inherit; /* 1 */ font: inherit; /* 2 */ margin: 0; /* 3 */ } /** * Address `overflow` set to `hidden` in IE 8/9/10/11. */ button { overflow: visible; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. * Correct `select` style inheritance in Firefox. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ input { line-height: normal; } /** * It's recommended that you don't attempt to style these elements. * Firefox's implementation doesn't respect box-sizing, padding, or width. * * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Fix the cursor style for Chrome's increment/decrement buttons. For certain * `font-size` values of the `input`, it causes the cursor style of the * decrement button to change from `default` to `text`. */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Address `appearance` set to `searchfield` in Safari and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari and Chrome on OS X. * Safari (but not Chrome) clips the cancel button when the search input has * padding (and `textfield` appearance). */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9/10/11. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * Remove default vertical scrollbar in IE 8/9/10/11. */ textarea { overflow: auto; } /** * Don't inherit the `font-weight` (applied by a rule above). * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. */ optgroup { font-weight: bold; } /* Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } ================================================ FILE: examples/rocket_example/api/static/css/skeleton.css ================================================ /* * Skeleton V2.0.4 * Copyright 2014, Dave Gamache * www.getskeleton.com * Free to use under the MIT license. * https://opensource.org/licenses/mit-license.php * 12/29/2014 */ /* Table of contents –––––––––––––––––––––––––––––––––––––––––––––––––– - Grid - Base Styles - Typography - Links - Buttons - Forms - Lists - Code - Tables - Spacing - Utilities - Clearing - Media Queries */ /* Grid –––––––––––––––––––––––––––––––––––––––––––––––––– */ .container { position: relative; width: 100%; max-width: 960px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } .column, .columns { width: 100%; float: left; box-sizing: border-box; } /* For devices larger than 400px */ @media (min-width: 400px) { .container { width: 85%; padding: 0; } } /* For devices larger than 550px */ @media (min-width: 550px) { .container { width: 80%; } .column, .columns { margin-left: 4%; } .column:first-child, .columns:first-child { margin-left: 0; } .one.column, .one.columns { width: 4.66666666667%; } .two.columns { width: 13.3333333333%; } .three.columns { width: 22%; } .four.columns { width: 30.6666666667%; } .five.columns { width: 39.3333333333%; } .six.columns { width: 48%; } .seven.columns { width: 56.6666666667%; } .eight.columns { width: 65.3333333333%; } .nine.columns { width: 74.0%; } .ten.columns { width: 82.6666666667%; } .eleven.columns { width: 91.3333333333%; } .twelve.columns { width: 100%; margin-left: 0; } .one-third.column { width: 30.6666666667%; } .two-thirds.column { width: 65.3333333333%; } .one-half.column { width: 48%; } /* Offsets */ .offset-by-one.column, .offset-by-one.columns { margin-left: 8.66666666667%; } .offset-by-two.column, .offset-by-two.columns { margin-left: 17.3333333333%; } .offset-by-three.column, .offset-by-three.columns { margin-left: 26%; } .offset-by-four.column, .offset-by-four.columns { margin-left: 34.6666666667%; } .offset-by-five.column, .offset-by-five.columns { margin-left: 43.3333333333%; } .offset-by-six.column, .offset-by-six.columns { margin-left: 52%; } .offset-by-seven.column, .offset-by-seven.columns { margin-left: 60.6666666667%; } .offset-by-eight.column, .offset-by-eight.columns { margin-left: 69.3333333333%; } .offset-by-nine.column, .offset-by-nine.columns { margin-left: 78.0%; } .offset-by-ten.column, .offset-by-ten.columns { margin-left: 86.6666666667%; } .offset-by-eleven.column, .offset-by-eleven.columns { margin-left: 95.3333333333%; } .offset-by-one-third.column, .offset-by-one-third.columns { margin-left: 34.6666666667%; } .offset-by-two-thirds.column, .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } .offset-by-one-half.column, .offset-by-one-half.columns { margin-left: 52%; } } /* Base Styles –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* NOTE html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */ html { font-size: 62.5%; } body { font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ line-height: 1.6; font-weight: 400; font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #222; } /* Typography –––––––––––––––––––––––––––––––––––––––––––––––––– */ h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 2rem; font-weight: 300; } h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } /* Larger than phablet */ @media (min-width: 550px) { h1 { font-size: 5.0rem; } h2 { font-size: 4.2rem; } h3 { font-size: 3.6rem; } h4 { font-size: 3.0rem; } h5 { font-size: 2.4rem; } h6 { font-size: 1.5rem; } } p { margin-top: 0; } /* Links –––––––––––––––––––––––––––––––––––––––––––––––––– */ a { color: #1EAEDB; } a:hover { color: #0FA0CE; } /* Buttons –––––––––––––––––––––––––––––––––––––––––––––––––– */ .button, button, input[type="submit"], input[type="reset"], input[type="button"] { display: inline-block; height: 38px; padding: 0 30px; color: #555; text-align: center; font-size: 11px; font-weight: 600; line-height: 38px; letter-spacing: .1rem; text-transform: uppercase; text-decoration: none; white-space: nowrap; background-color: transparent; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; box-sizing: border-box; } .button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover, .button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus { color: #333; border-color: #888; outline: 0; } .button.button-primary, button.button-primary, button.primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary { color: #FFF; background-color: #33C3F0; border-color: #33C3F0; } .button.button-primary:hover, button.button-primary:hover, button.primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, button.primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus { color: #FFF; background-color: #1EAEDB; border-color: #1EAEDB; } /* Forms –––––––––––––––––––––––––––––––––––––––––––––––––– */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select { height: 38px; padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ background-color: #fff; border: 1px solid #D1D1D1; border-radius: 4px; box-shadow: none; box-sizing: border-box; } /* Removes awkward default styles on some inputs for iOS */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; } textarea { min-height: 65px; padding-top: 6px; padding-bottom: 6px; } input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus { border: 1px solid #33C3F0; outline: 0; } label, legend { display: block; margin-bottom: .5rem; font-weight: 600; } fieldset { padding: 0; border-width: 0; } input[type="checkbox"], input[type="radio"] { display: inline; } label > .label-body { display: inline-block; margin-left: .5rem; font-weight: normal; } /* Lists –––––––––––––––––––––––––––––––––––––––––––––––––– */ ul { list-style: circle inside; } ol { list-style: decimal inside; } ol, ul { padding-left: 0; margin-top: 0; } ul ul, ul ol, ol ol, ol ul { margin: 1.5rem 0 1.5rem 3rem; font-size: 90%; } li { margin-bottom: 1rem; } /* Code –––––––––––––––––––––––––––––––––––––––––––––––––– */ code { padding: .2rem .5rem; margin: 0 .2rem; font-size: 90%; white-space: nowrap; background: #F1F1F1; border: 1px solid #E1E1E1; border-radius: 4px; } pre > code { display: block; padding: 1rem 1.5rem; white-space: pre; } /* Tables –––––––––––––––––––––––––––––––––––––––––––––––––– */ th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #E1E1E1; } th:first-child, td:first-child { padding-left: 0; } th:last-child, td:last-child { padding-right: 0; } /* Spacing –––––––––––––––––––––––––––––––––––––––––––––––––– */ button, .button { margin-bottom: 1rem; } input, textarea, select, fieldset { margin-bottom: 1.5rem; } pre, blockquote, dl, figure, table, p, ul, ol, form { margin-bottom: 2.5rem; } /* Utilities –––––––––––––––––––––––––––––––––––––––––––––––––– */ .u-full-width { width: 100%; box-sizing: border-box; } .u-max-full-width { max-width: 100%; box-sizing: border-box; } .u-pull-right { float: right; } .u-pull-left { float: left; } /* Misc –––––––––––––––––––––––––––––––––––––––––––––––––– */ hr { margin-top: 3rem; margin-bottom: 3.5rem; border-width: 0; border-top: 1px solid #E1E1E1; } /* Clearing –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Self Clearing Goodness */ .container:after, .row:after, .u-cf { content: ""; display: table; clear: both; } /* Media Queries –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Note: The best way to structure the use of media queries is to create the queries near the relevant code. For example, if you wanted to change the styles for buttons on small devices, paste the mobile query code up in the buttons section and style it there. */ /* Larger than mobile */ @media (min-width: 400px) {} /* Larger than phablet (also point when grid becomes active) */ @media (min-width: 550px) {} /* Larger than tablet */ @media (min-width: 750px) {} /* Larger than desktop */ @media (min-width: 1000px) {} /* Larger than Desktop HD */ @media (min-width: 1200px) {} ================================================ FILE: examples/rocket_example/api/static/css/style.css ================================================ .field-error { border: 1px solid #ff0000 !important; } .field-error-flash { color: #ff0000; display: block; margin: -10px 0 10px 0; } .field-success { border: 1px solid #5ab953 !important; } .field-success-flash { color: #5ab953; display: block; margin: -10px 0 10px 0; } span.completed { text-decoration: line-through; } form.inline { display: inline; } form.link, button.link { display: inline; color: #1eaedb; border: none; outline: none; background: none; cursor: pointer; padding: 0; margin: 0 0 0 0; height: inherit; text-decoration: underline; font-size: inherit; text-transform: none; font-weight: normal; line-height: inherit; letter-spacing: inherit; } form.link:hover, button.link:hover { color: #0fa0ce; } button.small { height: 20px; padding: 0 10px; font-size: 10px; line-height: 20px; margin: 0 2.5px; } .post:hover { background-color: #bce2ee; } .post td { padding: 5px; width: 150px; } #delete-button { color: red; border-color: red; } ================================================ FILE: examples/rocket_example/api/templates/base.html.tera ================================================ Rocket SeaORM Example

{% block content %}{% endblock content %}
================================================ FILE: examples/rocket_example/api/templates/edit.html.tera ================================================ {% extends "base" %} {% block content %}

Edit Post

{% endblock content %} ================================================ FILE: examples/rocket_example/api/templates/error/404.html.tera ================================================ 404 - tera

404: Hey! There's nothing here.

The page at {{ uri }} does not exist! ================================================ FILE: examples/rocket_example/api/templates/index.html.tera ================================================ {% extends "base" %} {% block content %}

Posts

{% if flash %} {{ flash.1 }} {% endif %} {% for post in posts %} {% endfor %}
ID Title Text
{{ post.id }} {{ post.title }} {{ post.text }}
{% if page == 1 %} Previous {% else %} Previous {% endif %} | {% if page == num_pages %} Next {% else %} Next {% endif %}
{% endblock content %} ================================================ FILE: examples/rocket_example/api/templates/new.html.tera ================================================ {% extends "base" %} {% block content %}

New Post

{% endblock content %} ================================================ FILE: examples/rocket_example/api/tests/crud_tests.rs ================================================ use entity::post; use rocket_example_api::service::{Mutation, Query}; use sea_orm::Database; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/rocket_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] rocket = { version = "0.5", features = ["json"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/rocket_example/entity/src/lib.rs ================================================ #[macro_use] extern crate rocket; pub mod post; ================================================ FILE: examples/rocket_example/entity/src/post.rs ================================================ use rocket::serde::{Deserialize, Serialize}; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize, FromForm)] #[serde(crate = "rocket::serde")] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/rocket_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-postgres", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/rocket_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/rocket_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; mod m20220120_000002_seed_posts; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } } ================================================ FILE: examples/rocket_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/rocket_example/migration/src/m20220120_000002_seed_posts.rs ================================================ use entity::post; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Posts table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Post", "Second Post"]; post::Entity::delete_many() .filter(post::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Posts seeded data removed."); Ok(()) } } ================================================ FILE: examples/rocket_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/rocket_example/src/main.rs ================================================ fn main() { rocket_example_api::main(); } ================================================ FILE: examples/rocket_okapi_example/Cargo.toml ================================================ [package] authors = [ "Sam Samai ", "Erick Pacheco "] edition = "2024" name = "rocket-example-api" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] async-stream = { version = "0.3" } async-trait = { version = "0.1" } dto = { path = "../dto" } entity = { path = "../entity" } futures = { version = "0.3" } futures-util = { version = "0.3" } migration = { path = "../migration" } rocket = { version = "0.5", features = ["json"] } rocket-okapi-example-service = { path = "../service" } rocket_cors = "0.6" rocket_db_pools = "0.2" rocket_dyn_templates = { version = "0.2", features = ["tera"] } rocket_okapi = { version = "0.9", features = [ "swagger", "rapidoc", "rocket_db_pools", ] } serde = "1.0" serde_json = { version = "1" } tokio = "1.41" [dependencies.sea-orm-rocket] features = [ "rocket_okapi", ] # enables rocket_okapi so to have open api features enabled path = "../../../sea-orm-rocket/lib" # remove this line in your own project and use the version line # version = "0.5" ================================================ FILE: examples/rocket_okapi_example/api/src/error.rs ================================================ use rocket::{ http::{ContentType, Status}, request::Request, response::{self, Responder, Response}, }; use rocket_okapi::okapi::openapi3::Responses; use rocket_okapi::okapi::schemars::{self, Map}; use rocket_okapi::{OpenApiError, r#gen::OpenApiGenerator, response::OpenApiResponderInner}; /// Error messages returned to user #[derive(Debug, serde::Serialize, schemars::JsonSchema)] pub struct Error { /// The title of the error message pub err: String, /// The description of the error pub msg: Option, // HTTP Status Code returned #[serde(skip)] pub http_status_code: u16, } impl OpenApiResponderInner for Error { fn responses(_generator: &mut OpenApiGenerator) -> Result { use rocket_okapi::okapi::openapi3::{RefOr, Response as OpenApiResponse}; let mut responses = Map::new(); responses.insert( "400".to_string(), RefOr::Object(OpenApiResponse { description: "\ # [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400)\n\ The request given is wrongly formatted or data asked could not be fulfilled. \ " .to_string(), ..Default::default() }), ); responses.insert( "404".to_string(), RefOr::Object(OpenApiResponse { description: "\ # [404 Not Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404)\n\ This response is given when you request a page that does not exists.\ " .to_string(), ..Default::default() }), ); responses.insert( "422".to_string(), RefOr::Object(OpenApiResponse { description: "\ # [422 Unprocessable Entity](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422)\n\ This response is given when you request body is not correctly formatted. \ ".to_string(), ..Default::default() }), ); responses.insert( "500".to_string(), RefOr::Object(OpenApiResponse { description: "\ # [500 Internal Server Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500)\n\ This response is given when something wend wrong on the server. \ ".to_string(), ..Default::default() }), ); Ok(Responses { responses, ..Default::default() }) } } impl std::fmt::Display for Error { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( formatter, "Error `{}`: {}", self.err, self.msg.as_deref().unwrap_or("") ) } } impl std::error::Error for Error {} impl<'r> Responder<'r, 'static> for Error { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { // Convert object to json let body = serde_json::to_string(&self).unwrap(); Response::build() .sized_body(body.len(), std::io::Cursor::new(body)) .header(ContentType::JSON) .status(Status::new(self.http_status_code)) .ok() } } impl From> for Error { fn from(err: rocket::serde::json::Error) -> Self { use rocket::serde::json::Error::*; match err { Io(io_error) => Error { err: "IO Error".to_owned(), msg: Some(io_error.to_string()), http_status_code: 422, }, Parse(_raw_data, parse_error) => Error { err: "Parse Error".to_owned(), msg: Some(parse_error.to_string()), http_status_code: 422, }, } } } ================================================ FILE: examples/rocket_okapi_example/api/src/lib.rs ================================================ #[macro_use] extern crate rocket; use rocket::fairing::{self, AdHoc}; use rocket::{Build, Rocket}; use migration::MigratorTrait; use rocket_db_pools::Database; use rocket_okapi::mount_endpoints_and_merged_docs; use rocket_okapi::okapi::openapi3::OpenApi; use rocket_okapi::rapidoc::{GeneralConfig, HideShowConfig, RapiDocConfig, make_rapidoc}; use rocket_okapi::settings::UrlObject; use rocket_okapi::swagger_ui::{SwaggerUIConfig, make_swagger_ui}; use rocket::http::Method; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; mod pool; use pool::Db; mod error; mod okapi_example; pub use entity::post; pub use entity::post::Entity as Post; async fn run_migrations(rocket: Rocket) -> fairing::Result { let conn = &Db::fetch(&rocket).unwrap().conn; let _ = migration::Migrator::up(conn, None).await; Ok(rocket) } #[tokio::main] async fn start() -> Result<(), rocket::Error> { let mut building_rocket = rocket::build() .attach(Db::init()) .attach(AdHoc::try_on_ignite("Migrations", run_migrations)) .mount( "/swagger-ui/", make_swagger_ui(&SwaggerUIConfig { url: "../v1/openapi.json".to_owned(), ..Default::default() }), ) .mount( "/rapidoc/", make_rapidoc(&RapiDocConfig { title: Some("Rocket/SeaOrm - RapiDoc documentation | RapiDoc".to_owned()), general: GeneralConfig { spec_urls: vec![UrlObject::new("General", "../v1/openapi.json")], ..Default::default() }, hide_show: HideShowConfig { allow_spec_url_load: false, allow_spec_file_load: false, ..Default::default() }, ..Default::default() }), ) .attach(cors()); let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); let custom_route_spec = (vec![], custom_openapi_spec()); mount_endpoints_and_merged_docs! { building_rocket, "/v1".to_owned(), openapi_settings, "/additional" => custom_route_spec, "/okapi-example" => okapi_example::get_routes_and_docs(&openapi_settings), }; building_rocket.launch().await.map(|_| ()) } fn cors() -> Cors { let allowed_origins = AllowedOrigins::some_exact(&["http://localhost:8000", "http://127.0.0.1:8000"]); rocket_cors::CorsOptions { allowed_origins, allowed_methods: vec![Method::Get, Method::Post, Method::Delete] .into_iter() .map(From::from) .collect(), allowed_headers: AllowedHeaders::all(), allow_credentials: true, ..Default::default() } .to_cors() .unwrap() } fn custom_openapi_spec() -> OpenApi { use rocket_okapi::okapi::openapi3::*; OpenApi { openapi: OpenApi::default_version(), info: Info { title: "SeaOrm-Rocket-Okapi Example".to_owned(), description: Some("API Docs for Rocket/SeaOrm example".to_owned()), terms_of_service: Some("https://github.com/SeaQL/sea-orm#license".to_owned()), contact: Some(Contact { name: Some("SeaOrm".to_owned()), url: Some("https://github.com/SeaQL/sea-orm".to_owned()), email: None, ..Default::default() }), license: Some(License { name: "MIT".to_owned(), url: Some("https://github.com/SeaQL/sea-orm/blob/master/LICENSE-MIT".to_owned()), ..Default::default() }), version: env!("CARGO_PKG_VERSION").to_owned(), ..Default::default() }, servers: vec![ Server { url: "http://127.0.0.1:8000/v1".to_owned(), description: Some("Localhost".to_owned()), ..Default::default() }, Server { url: "https://production-server.com/".to_owned(), description: Some("Remote development server".to_owned()), ..Default::default() }, ], ..Default::default() } } pub fn main() { let result = start(); println!("Rocket: deorbit."); if let Some(err) = result.err() { println!("Error: {err}"); } } ================================================ FILE: examples/rocket_okapi_example/api/src/okapi_example.rs ================================================ use dto::dto; use rocket::serde::json::Json; use rocket_okapi_example_service::{Mutation, Query}; use rocket_db_pools::Connection; use rocket_okapi::okapi::openapi3::OpenApi; use crate::error; use crate::pool; use pool::Db; pub use entity::post; use rocket_okapi::settings::OpenApiSettings; use rocket_okapi::{openapi, openapi_get_routes_spec}; const DEFAULT_POSTS_PER_PAGE: u64 = 5; pub fn get_routes_and_docs(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![settings: create, update, list, get_by_id, delete, destroy] } pub type R = std::result::Result, error::Error>; pub type DataResult<'a, T> = std::result::Result, rocket::serde::json::Error<'a>>; /// # Add a new post #[openapi(tag = "POST")] #[post("/", data = "")] async fn create(conn: Connection, post_data: DataResult<'_, post::Model>) -> R> { let db = conn.into_inner(); let form = post_data?.into_inner(); let cmd = Mutation::create_post(&db, form); match cmd.await { Ok(_) => Ok(Json(Some("Post successfully added.".to_string()))), Err(e) => { let m = error::Error { err: "Could not insert post".to_string(), msg: Some(e.to_string()), http_status_code: 400, }; Err(m) } } } /// # Update a post #[openapi(tag = "POST")] #[post("/", data = "")] async fn update( conn: Connection, id: i32, post_data: DataResult<'_, post::Model>, ) -> R> { let db = conn.into_inner(); let form = post_data?.into_inner(); let cmd = Mutation::update_post_by_id(&db, id, form); match cmd.await { Ok(_) => Ok(Json(Some("Post successfully updated.".to_string()))), Err(e) => { let m = error::Error { err: "Could not update post".to_string(), msg: Some(e.to_string()), http_status_code: 400, }; Err(m) } } } /// # Get post list #[openapi(tag = "POST")] #[get("/?&")] async fn list( conn: Connection, page: Option, posts_per_page: Option, ) -> R { let db = conn.into_inner(); // Set page number and items per page let page = page.unwrap_or(1); let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); if page == 0 { let m = error::Error { err: "error getting posts".to_string(), msg: Some("'page' param cannot be zero".to_string()), http_status_code: 400, }; return Err(m); } let (posts, num_pages) = Query::find_posts_in_page(&db, page, posts_per_page) .await .expect("Cannot find posts in page"); Ok(Json(dto::PostsDto { page, posts_per_page, num_pages, posts, })) } /// # get post by Id #[openapi(tag = "POST")] #[get("/")] async fn get_by_id(conn: Connection, id: i32) -> R> { let db = conn.into_inner(); let post: Option = Query::find_post_by_id(&db, id) .await .expect("could not find post"); Ok(Json(post)) } /// # delete post by Id #[openapi(tag = "POST")] #[delete("/")] async fn delete(conn: Connection, id: i32) -> R> { let db = conn.into_inner(); let cmd = Mutation::delete_post(&db, id); match cmd.await { Ok(_) => Ok(Json(Some("Post successfully deleted.".to_string()))), Err(e) => { let m = error::Error { err: "Error deleting post".to_string(), msg: Some(e.to_string()), http_status_code: 400, }; Err(m) } } } /// # delete all posts #[openapi(tag = "POST")] #[delete("/")] async fn destroy(conn: Connection) -> R> { let db = conn.into_inner(); let cmd = Mutation::delete_all_posts(&db); match cmd.await { Ok(_) => Ok(Json(Some( "All Posts were successfully deleted.".to_string(), ))), Err(e) => { let m = error::Error { err: "Error deleting all posts at once".to_string(), msg: Some(e.to_string()), http_status_code: 400, }; Err(m) } } } ================================================ FILE: examples/rocket_okapi_example/api/src/pool.rs ================================================ use rocket_okapi_example_service::sea_orm; use async_trait::async_trait; use rocket_db_pools::Database; use sea_orm::ConnectOptions; use sea_orm_rocket::{Config, rocket::figment::Figment}; use std::time::Duration; #[derive(Database, Debug)] #[database("sea_orm")] pub struct Db(SeaOrmPool); #[derive(Debug, Clone)] pub struct SeaOrmPool { pub conn: sea_orm::DatabaseConnection, } #[async_trait] impl rocket_db_pools::Pool for SeaOrmPool { type Error = sea_orm::DbErr; type Connection = sea_orm::DatabaseConnection; async fn init(figment: &Figment) -> Result { let config = figment.extract::().unwrap(); let mut options: ConnectOptions = config.url.into(); options .max_connections(config.max_connections as u32) .min_connections(config.min_connections.unwrap_or_default()) .connect_timeout(Duration::from_secs(config.connect_timeout)); if let Some(idle_timeout) = config.idle_timeout { options.idle_timeout(Duration::from_secs(idle_timeout)); } let conn = sea_orm::Database::connect(options).await?; Ok(SeaOrmPool { conn }) } async fn get(&self) -> Result { Ok(self.conn.clone()) } // DatabaseConnection automatically closes on drop async fn close(&self) {} } ================================================ FILE: examples/rocket_okapi_example/dto/Cargo.toml ================================================ [package] edition = "2024" name = "dto" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "dto" path = "src/lib.rs" [dependencies] rocket = { version = "0.5", features = ["json"] } [dependencies.entity] path = "../entity" [dependencies.rocket_okapi] version = "0.9" ================================================ FILE: examples/rocket_okapi_example/dto/src/dto.rs ================================================ use entity::*; use rocket::serde::{Deserialize, Serialize}; use rocket_okapi::okapi::schemars::{self, JsonSchema}; #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] #[serde(crate = "rocket::serde")] pub struct PostsDto { pub page: u64, pub posts_per_page: u64, pub num_pages: u64, pub posts: Vec, } ================================================ FILE: examples/rocket_okapi_example/dto/src/lib.rs ================================================ pub mod dto; ================================================ FILE: examples/rocket_okapi_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] rocket = { version = "0.5", features = ["json"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [dependencies.rocket_okapi] version = "0.9" ================================================ FILE: examples/rocket_okapi_example/entity/src/lib.rs ================================================ #[macro_use] extern crate rocket; pub mod post; ================================================ FILE: examples/rocket_okapi_example/entity/src/post.rs ================================================ use rocket::serde::{Deserialize, Serialize}; use rocket_okapi::okapi::schemars::{self, JsonSchema}; use sea_orm::entity::prelude::*; #[derive( Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize, FromForm, JsonSchema, )] #[serde(crate = "rocket::serde")] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/rocket_okapi_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] rocket = { version = "0.5" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI # "runtime-tokio-native-tls", # "sqlx-postgres", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/rocket_okapi_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/rocket_okapi_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![Box::new(m20220120_000001_create_post_table::Migration)] } } ================================================ FILE: examples/rocket_okapi_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/rocket_okapi_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { // Setting `DATABASE_URL` environment variable let key = "DATABASE_URL"; if std::env::var(key).is_err() { // Getting the database URL from Rocket.toml if it's not set let figment = rocket::Config::figment(); let database_url: String = figment .extract_inner("databases.sea_orm.url") .expect("Cannot find Database URL in Rocket.toml"); unsafe { std::env::set_var(key, database_url); } } cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/rocket_okapi_example/service/Cargo.toml ================================================ [package] edition = "2024" name = "rocket-okapi-example-service" rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] entity = { path = "../entity" } [dependencies.sea-orm] features = [ "runtime-tokio-native-tls", # "sqlx-postgres", # "sqlx-mysql", "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [dev-dependencies] tokio = "1.41" ================================================ FILE: examples/rocket_okapi_example/service/src/lib.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; pub use sea_orm; ================================================ FILE: examples/rocket_okapi_example/service/src/mutation.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/rocket_okapi_example/service/src/query.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/rocket_okapi_example/service/tests/crud_tests.rs ================================================ use entity::post; use rocket_okapi_example_service::{Mutation, Query}; use sea_orm::Database; #[tokio::test] async fn main() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/rocket_okapi_example/src/main.rs ================================================ fn main() { rocket_example_api::main(); } ================================================ FILE: examples/salvo_example/Cargo.toml ================================================ [package] edition = "2024" name = "sea-orm-salvo-example" rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] members = [".", "api", "entity", "migration"] [dependencies] salvo-example-api = { path = "api" } ================================================ FILE: examples/salvo_example/README.md ================================================ ![screenshot](Screenshot.png) # Salvo with SeaORM example app 1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database 1. Turn on the appropriate database feature for your chosen db in `api/Cargo.toml` (the `"sqlx-sqlite",` line) 1. Execute `cargo run` to start the server 1. Visit [localhost:8000](http://localhost:8000) in browser after seeing the `server started` line Run tests: ```bash cd api cargo test ``` ================================================ FILE: examples/salvo_example/api/Cargo.toml ================================================ [package] edition = "2024" name = "salvo-example-api" rust-version = "1.85.0" version = "0.1.0" [dependencies] dotenvy = "0.15" entity = { path = "../entity" } migration = { path = "../migration" } salvo = { version = "0.50", features = ["affix", "serve-static"] } serde = { version = "1", features = ["derive"] } tera = "1.19.0" tokio = { version = "1.29.0", features = ["macros", "rt-multi-thread"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-native-tls", # "sqlx-mysql", # "sqlx-postgres", "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/salvo_example/api/src/lib.rs ================================================ pub mod service; use entity::post; use migration::{Migrator, MigratorTrait}; use salvo::affix; use salvo::prelude::*; use sea_orm::{Database, DatabaseConnection}; use service::{Mutation, Query}; use tera::Tera; const DEFAULT_POSTS_PER_PAGE: u64 = 5; type Result = std::result::Result; #[derive(Debug, Clone)] struct AppState { templates: tera::Tera, conn: DatabaseConnection, } #[handler] async fn create(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> { let state = depot .obtain::() .ok_or_else(StatusError::internal_server_error)?; let conn = &state.conn; let form = req .parse_form::() .await .map_err(|_| StatusError::bad_request())?; Mutation::create_post(conn, form) .await .map_err(|_| StatusError::internal_server_error())?; Redirect::found("/").render(res); Ok(()) } #[handler] async fn list(req: &mut Request, depot: &mut Depot) -> Result> { let state = depot .obtain::() .ok_or_else(StatusError::internal_server_error)?; let conn = &state.conn; let page = req.query("page").unwrap_or(1); let posts_per_page = req .query("posts_per_page") .unwrap_or(DEFAULT_POSTS_PER_PAGE); let (posts, num_pages) = Query::find_posts_in_page(conn, page, posts_per_page) .await .map_err(|_| StatusError::internal_server_error())?; let mut ctx = tera::Context::new(); ctx.insert("posts", &posts); ctx.insert("page", &page); ctx.insert("posts_per_page", &posts_per_page); ctx.insert("num_pages", &num_pages); let body = state .templates .render("index.html.tera", &ctx) .map_err(|_| StatusError::internal_server_error())?; Ok(Text::Html(body)) } #[handler] async fn new(depot: &mut Depot) -> Result> { let state = depot .obtain::() .ok_or_else(StatusError::internal_server_error)?; let ctx = tera::Context::new(); let body = state .templates .render("new.html.tera", &ctx) .map_err(|_| StatusError::internal_server_error())?; Ok(Text::Html(body)) } #[handler] async fn edit(req: &mut Request, depot: &mut Depot) -> Result> { let state = depot .obtain::() .ok_or_else(StatusError::internal_server_error)?; let conn = &state.conn; let id = req.param::("id").unwrap_or_default(); let post: post::Model = Query::find_post_by_id(conn, id) .await .map_err(|_| StatusError::internal_server_error())? .ok_or_else(StatusError::not_found)?; let mut ctx = tera::Context::new(); ctx.insert("post", &post); let body = state .templates .render("edit.html.tera", &ctx) .map_err(|_| StatusError::internal_server_error())?; Ok(Text::Html(body)) } #[handler] async fn update(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> { let state = depot .obtain::() .ok_or_else(StatusError::internal_server_error)?; let conn = &state.conn; let id = req.param::("id").unwrap_or_default(); let form = req .parse_form::() .await .map_err(|_| StatusError::bad_request())?; Mutation::update_post_by_id(conn, id, form) .await .map_err(|_| StatusError::internal_server_error())?; Redirect::found("/").render(res); Ok(()) } #[handler] async fn delete(req: &mut Request, depot: &mut Depot, res: &mut Response) -> Result<()> { let state = depot .obtain::() .ok_or_else(StatusError::internal_server_error)?; let conn = &state.conn; let id = req.param::("id").unwrap_or_default(); Mutation::delete_post(conn, id) .await .map_err(|_| StatusError::internal_server_error())?; Redirect::found("/").render(res); Ok(()) } #[tokio::main] pub async fn main() { tracing_subscriber::fmt::init(); // get env vars dotenvy::dotenv().ok(); let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let host = std::env::var("HOST").expect("HOST is not set in .env file"); let port = std::env::var("PORT").expect("PORT is not set in .env file"); let server_url = format!("{host}:{port}"); // create post table if not exists let conn = Database::connect(&db_url).await.unwrap(); Migrator::up(&conn, None).await.unwrap(); let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap(); let state = AppState { templates, conn }; println!("Starting server at {server_url}"); let router = Router::new() .hoop(affix::inject(state)) .post(create) .get(list) .push(Router::with_path("new").get(new)) .push(Router::with_path("").get(edit).post(update)) .push(Router::with_path("delete/").post(delete)) .push( Router::with_path("static/<**>").get(salvo::prelude::StaticDir::new(concat!( env!("CARGO_MANIFEST_DIR"), "/static" ))), ); Server::new(TcpListener::bind(TcpListener::new(format!("{host}:{port}"))).await) .serve(router) .await; } ================================================ FILE: examples/salvo_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/salvo_example/api/src/service/mutation.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/salvo_example/api/src/service/query.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/salvo_example/api/static/css/normalize.css ================================================ /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined for any HTML5 element in IE 8/9. * Correct `block` display not defined for `details` or `summary` in IE 10/11 * and Firefox. * Correct `block` display not defined for `main` in IE 11. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } /** * 1. Correct `inline-block` display not defined in IE 8/9. * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. */ audio, canvas, progress, video { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9/10. * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background-color: transparent; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* Text-level semantics ========================================================================== */ /** * Address styling not present in IE 8/9/10/11, Safari, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari and Chrome. */ dfn { font-style: italic; } /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9/10. */ img { border: 0; } /** * Correct overflow not hidden in IE 9/10/11. */ svg:not(:root) { overflow: hidden; } /* Grouping content ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari. */ figure { margin: 1em 40px; } /** * Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Contain overflow in all browsers. */ pre { overflow: auto; } /** * Address odd `em`-unit font size rendering in all browsers. */ code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } /* Forms ========================================================================== */ /** * Known limitation: by default, Chrome and Safari on OS X allow very limited * styling of `select`, unless a `border` property is set. */ /** * 1. Correct color not being inherited. * Known issue: affects color of disabled elements. * 2. Correct font properties not being inherited. * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. */ button, input, optgroup, select, textarea { color: inherit; /* 1 */ font: inherit; /* 2 */ margin: 0; /* 3 */ } /** * Address `overflow` set to `hidden` in IE 8/9/10/11. */ button { overflow: visible; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. * Correct `select` style inheritance in Firefox. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ input { line-height: normal; } /** * It's recommended that you don't attempt to style these elements. * Firefox's implementation doesn't respect box-sizing, padding, or width. * * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Fix the cursor style for Chrome's increment/decrement buttons. For certain * `font-size` values of the `input`, it causes the cursor style of the * decrement button to change from `default` to `text`. */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Address `appearance` set to `searchfield` in Safari and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari and Chrome on OS X. * Safari (but not Chrome) clips the cancel button when the search input has * padding (and `textfield` appearance). */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9/10/11. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * Remove default vertical scrollbar in IE 8/9/10/11. */ textarea { overflow: auto; } /** * Don't inherit the `font-weight` (applied by a rule above). * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. */ optgroup { font-weight: bold; } /* Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } ================================================ FILE: examples/salvo_example/api/static/css/skeleton.css ================================================ /* * Skeleton V2.0.4 * Copyright 2014, Dave Gamache * www.getskeleton.com * Free to use under the MIT license. * https://opensource.org/licenses/mit-license.php * 12/29/2014 */ /* Table of contents –––––––––––––––––––––––––––––––––––––––––––––––––– - Grid - Base Styles - Typography - Links - Buttons - Forms - Lists - Code - Tables - Spacing - Utilities - Clearing - Media Queries */ /* Grid –––––––––––––––––––––––––––––––––––––––––––––––––– */ .container { position: relative; width: 100%; max-width: 960px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } .column, .columns { width: 100%; float: left; box-sizing: border-box; } /* For devices larger than 400px */ @media (min-width: 400px) { .container { width: 85%; padding: 0; } } /* For devices larger than 550px */ @media (min-width: 550px) { .container { width: 80%; } .column, .columns { margin-left: 4%; } .column:first-child, .columns:first-child { margin-left: 0; } .one.column, .one.columns { width: 4.66666666667%; } .two.columns { width: 13.3333333333%; } .three.columns { width: 22%; } .four.columns { width: 30.6666666667%; } .five.columns { width: 39.3333333333%; } .six.columns { width: 48%; } .seven.columns { width: 56.6666666667%; } .eight.columns { width: 65.3333333333%; } .nine.columns { width: 74.0%; } .ten.columns { width: 82.6666666667%; } .eleven.columns { width: 91.3333333333%; } .twelve.columns { width: 100%; margin-left: 0; } .one-third.column { width: 30.6666666667%; } .two-thirds.column { width: 65.3333333333%; } .one-half.column { width: 48%; } /* Offsets */ .offset-by-one.column, .offset-by-one.columns { margin-left: 8.66666666667%; } .offset-by-two.column, .offset-by-two.columns { margin-left: 17.3333333333%; } .offset-by-three.column, .offset-by-three.columns { margin-left: 26%; } .offset-by-four.column, .offset-by-four.columns { margin-left: 34.6666666667%; } .offset-by-five.column, .offset-by-five.columns { margin-left: 43.3333333333%; } .offset-by-six.column, .offset-by-six.columns { margin-left: 52%; } .offset-by-seven.column, .offset-by-seven.columns { margin-left: 60.6666666667%; } .offset-by-eight.column, .offset-by-eight.columns { margin-left: 69.3333333333%; } .offset-by-nine.column, .offset-by-nine.columns { margin-left: 78.0%; } .offset-by-ten.column, .offset-by-ten.columns { margin-left: 86.6666666667%; } .offset-by-eleven.column, .offset-by-eleven.columns { margin-left: 95.3333333333%; } .offset-by-one-third.column, .offset-by-one-third.columns { margin-left: 34.6666666667%; } .offset-by-two-thirds.column, .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } .offset-by-one-half.column, .offset-by-one-half.columns { margin-left: 52%; } } /* Base Styles –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* NOTE html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */ html { font-size: 62.5%; } body { font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ line-height: 1.6; font-weight: 400; font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #222; } /* Typography –––––––––––––––––––––––––––––––––––––––––––––––––– */ h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 2rem; font-weight: 300; } h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } /* Larger than phablet */ @media (min-width: 550px) { h1 { font-size: 5.0rem; } h2 { font-size: 4.2rem; } h3 { font-size: 3.6rem; } h4 { font-size: 3.0rem; } h5 { font-size: 2.4rem; } h6 { font-size: 1.5rem; } } p { margin-top: 0; } /* Links –––––––––––––––––––––––––––––––––––––––––––––––––– */ a { color: #1EAEDB; } a:hover { color: #0FA0CE; } /* Buttons –––––––––––––––––––––––––––––––––––––––––––––––––– */ .button, button, input[type="submit"], input[type="reset"], input[type="button"] { display: inline-block; height: 38px; padding: 0 30px; color: #555; text-align: center; font-size: 11px; font-weight: 600; line-height: 38px; letter-spacing: .1rem; text-transform: uppercase; text-decoration: none; white-space: nowrap; background-color: transparent; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; box-sizing: border-box; } .button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover, .button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus { color: #333; border-color: #888; outline: 0; } .button.button-primary, button.button-primary, button.primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary { color: #FFF; background-color: #33C3F0; border-color: #33C3F0; } .button.button-primary:hover, button.button-primary:hover, button.primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, button.primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus { color: #FFF; background-color: #1EAEDB; border-color: #1EAEDB; } /* Forms –––––––––––––––––––––––––––––––––––––––––––––––––– */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select { height: 38px; padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ background-color: #fff; border: 1px solid #D1D1D1; border-radius: 4px; box-shadow: none; box-sizing: border-box; } /* Removes awkward default styles on some inputs for iOS */ input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; } textarea { min-height: 65px; padding-top: 6px; padding-bottom: 6px; } input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus { border: 1px solid #33C3F0; outline: 0; } label, legend { display: block; margin-bottom: .5rem; font-weight: 600; } fieldset { padding: 0; border-width: 0; } input[type="checkbox"], input[type="radio"] { display: inline; } label > .label-body { display: inline-block; margin-left: .5rem; font-weight: normal; } /* Lists –––––––––––––––––––––––––––––––––––––––––––––––––– */ ul { list-style: circle inside; } ol { list-style: decimal inside; } ol, ul { padding-left: 0; margin-top: 0; } ul ul, ul ol, ol ol, ol ul { margin: 1.5rem 0 1.5rem 3rem; font-size: 90%; } li { margin-bottom: 1rem; } /* Code –––––––––––––––––––––––––––––––––––––––––––––––––– */ code { padding: .2rem .5rem; margin: 0 .2rem; font-size: 90%; white-space: nowrap; background: #F1F1F1; border: 1px solid #E1E1E1; border-radius: 4px; } pre > code { display: block; padding: 1rem 1.5rem; white-space: pre; } /* Tables –––––––––––––––––––––––––––––––––––––––––––––––––– */ th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #E1E1E1; } th:first-child, td:first-child { padding-left: 0; } th:last-child, td:last-child { padding-right: 0; } /* Spacing –––––––––––––––––––––––––––––––––––––––––––––––––– */ button, .button { margin-bottom: 1rem; } input, textarea, select, fieldset { margin-bottom: 1.5rem; } pre, blockquote, dl, figure, table, p, ul, ol, form { margin-bottom: 2.5rem; } /* Utilities –––––––––––––––––––––––––––––––––––––––––––––––––– */ .u-full-width { width: 100%; box-sizing: border-box; } .u-max-full-width { max-width: 100%; box-sizing: border-box; } .u-pull-right { float: right; } .u-pull-left { float: left; } /* Misc –––––––––––––––––––––––––––––––––––––––––––––––––– */ hr { margin-top: 3rem; margin-bottom: 3.5rem; border-width: 0; border-top: 1px solid #E1E1E1; } /* Clearing –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Self Clearing Goodness */ .container:after, .row:after, .u-cf { content: ""; display: table; clear: both; } /* Media Queries –––––––––––––––––––––––––––––––––––––––––––––––––– */ /* Note: The best way to structure the use of media queries is to create the queries near the relevant code. For example, if you wanted to change the styles for buttons on small devices, paste the mobile query code up in the buttons section and style it there. */ /* Larger than mobile */ @media (min-width: 400px) {} /* Larger than phablet (also point when grid becomes active) */ @media (min-width: 550px) {} /* Larger than tablet */ @media (min-width: 750px) {} /* Larger than desktop */ @media (min-width: 1000px) {} /* Larger than Desktop HD */ @media (min-width: 1200px) {} ================================================ FILE: examples/salvo_example/api/static/css/style.css ================================================ .field-error { border: 1px solid #ff0000 !important; } .field-error-flash { color: #ff0000; display: block; margin: -10px 0 10px 0; } .field-success { border: 1px solid #5ab953 !important; } .field-success-flash { color: #5ab953; display: block; margin: -10px 0 10px 0; } span.completed { text-decoration: line-through; } form.inline { display: inline; } form.link, button.link { display: inline; color: #1eaedb; border: none; outline: none; background: none; cursor: pointer; padding: 0; margin: 0 0 0 0; height: inherit; text-decoration: underline; font-size: inherit; text-transform: none; font-weight: normal; line-height: inherit; letter-spacing: inherit; } form.link:hover, button.link:hover { color: #0fa0ce; } button.small { height: 20px; padding: 0 10px; font-size: 10px; line-height: 20px; margin: 0 2.5px; } .post:hover { background-color: #bce2ee; } .post td { padding: 5px; width: 150px; } #delete-button { color: red; border-color: red; } ================================================ FILE: examples/salvo_example/api/templates/edit.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Edit Post

{% endblock content %} ================================================ FILE: examples/salvo_example/api/templates/error/404.html.tera ================================================ 404 - tera

404: Hey! There's nothing here.

The page at {{ uri }} does not exist! ================================================ FILE: examples/salvo_example/api/templates/index.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

Posts

{% if flash %} {{ flash.message }} {% endif %} {% for post in posts %} {% endfor %}
ID Title Text
{{ post.id }} {{ post.title }} {{ post.text }}
{% if page == 1 %} Previous {% else %} Previous {% endif %} | {% if page == num_pages %} Next {% else %} Next {% endif %}
{% endblock content %} ================================================ FILE: examples/salvo_example/api/templates/layout.html.tera ================================================ Salvo Example

{% block content %}{% endblock content %}
================================================ FILE: examples/salvo_example/api/templates/new.html.tera ================================================ {% extends "layout.html.tera" %} {% block content %}

New Post

{% endblock content %} ================================================ FILE: examples/salvo_example/api/tests/crud_tests.rs ================================================ use entity::post; use salvo_example_api::service::{Mutation, Query}; use sea_orm::Database; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/salvo_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/salvo_example/entity/src/lib.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub mod prelude; pub mod post; ================================================ FILE: examples/salvo_example/entity/src/post.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/salvo_example/entity/src/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub use super::post::Entity as Post; ================================================ FILE: examples/salvo_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-sqlite", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/salvo_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/salvo_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; mod m20220120_000002_seed_posts; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } } ================================================ FILE: examples/salvo_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/salvo_example/migration/src/m20220120_000002_seed_posts.rs ================================================ use entity::post; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Posts table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Post", "Second Post"]; post::Entity::delete_many() .filter(post::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Posts seeded data removed."); Ok(()) } } ================================================ FILE: examples/salvo_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/salvo_example/src/main.rs ================================================ fn main() { salvo_example_api::main(); } ================================================ FILE: examples/seaography_example/README.md ================================================ # SeaORM + Seaography Example | ![](https://raw.githubusercontent.com/SeaQL/sea-orm/master/examples/seaography_example/Seaography%20example.png) | |:--:| | Seaography screenshot with Bakery schema | | ![](https://raw.githubusercontent.com/SeaQL/sea-orm/master/tests/common/bakery_chain/bakery_chain_erd.png) | |:--:| | The Bakery schema | ## Running the project Specify a database url ```sh export DATABASE_URL="sqlite://../bakery.db" ``` Then, run ```sh cd graphql cargo run ``` ## Run some queries ### Find chocolate cakes and know where to buy them ```graphql { cake(filters: { name: { contains: "Chocolate" } }) { nodes { name price bakery { name } } } } ``` ### Find all cakes baked by Alice ```graphql { cake(having: { baker: { name: { eq: "Alice" } } }) { nodes { name price baker { nodes { name } } } } } ``` ### Bakery -> Cake -> Baker ```graphql { bakery(pagination: { page: { limit: 10, page: 0 } }, orderBy: { name: ASC }) { nodes { name cake { nodes { name price baker { nodes { name } } } } } } } ``` ## Starting from scratch ### Setup the Database `cd` into `migration` folder and follow instructions there, but basically: ```sh export DATABASE_URL="sqlite://../bakery.db?mode=rwc" ``` ```sh cd migration cargo run ``` ### Install Seaography ```sh cargo install sea-orm-cli@^2.0.0-rc cargo install seaography-cli@^2.0.0-rc ``` ### Generate GraphQL project ```sh rm -rf graphql # this entire folder is generated mkdir graphql cd graphql sea-orm-cli generate entity --output-dir ./src/entities --entity-format dense --seaography seaography-cli -o . -e ./src/entities --framework axum sea-orm-seaography-example ``` ================================================ FILE: examples/seaography_example/graphql/Cargo.toml ================================================ [package] edition = "2021" name = "sea-orm-seaography-example" version = "0.1.0" [dependencies] async-graphql-axum = { version = "7.0" } axum = { version = "0.8" } dotenv = "0.15.0" tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] } tracing = { version = "0.1.37" } tracing-subscriber = { version = "0.3.17" } [dependencies.sea-orm] features = ["sqlx-sqlite", "runtime-tokio-native-tls", "seaography"] version = "~2.0.0-rc" [dependencies.seaography] features = ["graphql-playground", "with-decimal", "with-chrono"] version = "~2.0.0-rc.3" # seaography version [dev-dependencies] serde_json = { version = "1.0.103" } [workspace] members = [] ================================================ FILE: examples/seaography_example/graphql/src/entities/baker.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "baker")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub contact: String, pub bakery_id: Option, #[sea_orm( belongs_to, from = "bakery_id", to = "id", on_update = "Cascade", on_delete = "Cascade" )] pub bakery: HasOne, #[sea_orm(has_many, via = "cake_baker")] pub cakes: HasMany, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/graphql/src/entities/bakery.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "bakery")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(column_type = "Double")] pub profit_margin: f64, #[sea_orm(has_many)] pub bakers: HasMany, #[sea_orm(has_many)] pub cakes: HasMany, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/graphql/src/entities/cake.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(column_type = "Decimal(Some((16, 4)))")] pub price: Decimal, pub bakery_id: i32, pub gluten_free: bool, #[sea_orm( belongs_to, from = "bakery_id", to = "id", on_update = "Cascade", on_delete = "Cascade" )] pub bakery: HasOne, #[sea_orm(has_many, via = "cake_baker")] pub bakers: HasMany, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/graphql/src/entities/cake_baker.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake_baker")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub baker_id: i32, #[sea_orm( belongs_to, from = "baker_id", to = "id", on_update = "Cascade", on_delete = "Cascade" )] pub baker: HasOne, #[sea_orm( belongs_to, from = "cake_id", to = "id", on_update = "Cascade", on_delete = "Cascade" )] pub cake: HasOne, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/graphql/src/entities/mod.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14 pub mod prelude; pub mod baker; pub mod bakery; pub mod cake; pub mod cake_baker; seaography::register_entity_modules!([baker, bakery, cake, cake_baker,]); ================================================ FILE: examples/seaography_example/graphql/src/entities/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14 pub use super::baker::Entity as Baker; pub use super::bakery::Entity as Bakery; pub use super::cake::Entity as Cake; pub use super::cake_baker::Entity as CakeBaker; ================================================ FILE: examples/seaography_example/graphql/src/lib.rs ================================================ pub mod entities; pub mod query_root; ================================================ FILE: examples/seaography_example/graphql/src/main.rs ================================================ use async_graphql::{ dynamic::Schema, http::{playground_source, GraphQLPlaygroundConfig}, }; use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; use axum::{ extract::State, response::{self, IntoResponse}, routing::get, Router, }; use dotenv::dotenv; use sea_orm::Database; use seaography::{async_graphql, lazy_static::lazy_static}; use std::env; use tokio::net::TcpListener; lazy_static! { static ref URL: String = env::var("URL").unwrap_or("localhost:8000".into()); static ref ENDPOINT: String = env::var("ENDPOINT").unwrap_or("/".into()); static ref DATABASE_URL: String = env::var("DATABASE_URL").expect("DATABASE_URL environment variable not set"); static ref DEPTH_LIMIT: Option = env::var("DEPTH_LIMIT").map_or(None, |data| Some( data.parse().expect("DEPTH_LIMIT is not a number") )); static ref COMPLEXITY_LIMIT: Option = env::var("COMPLEXITY_LIMIT") .map_or(None, |data| { Some(data.parse().expect("COMPLEXITY_LIMIT is not a number")) }); } async fn graphql_playground() -> impl IntoResponse { response::Html(playground_source(GraphQLPlaygroundConfig::new(&*ENDPOINT))) } async fn graphql_handler(State(schema): State, req: GraphQLRequest) -> GraphQLResponse { let req = req.into_inner(); schema.execute(req).await.into() } #[tokio::main] async fn main() { dotenv().ok(); tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) .with_test_writer() .init(); let db = Database::connect(&*DATABASE_URL) .await .expect("Fail to initialize database connection"); let schema = sea_orm_seaography_example::query_root::schema(db, *DEPTH_LIMIT, *COMPLEXITY_LIMIT) .unwrap(); let app = Router::new() .route(&*ENDPOINT, get(graphql_playground).post(graphql_handler)) .with_state(schema); println!("Visit GraphQL Playground at http://{}", *URL); axum::serve(TcpListener::bind(&*URL).await.unwrap(), app) .await .unwrap(); } ================================================ FILE: examples/seaography_example/graphql/src/query_root.rs ================================================ use crate::entities::*; use async_graphql::dynamic::*; use sea_orm::DatabaseConnection; use seaography::{async_graphql, lazy_static::lazy_static, Builder, BuilderContext}; lazy_static! { static ref CONTEXT: BuilderContext = BuilderContext::default(); } pub fn schema( database: DatabaseConnection, depth: Option, complexity: Option, ) -> Result { schema_builder(&CONTEXT, database, depth, complexity).finish() } pub fn schema_builder( context: &'static BuilderContext, database: DatabaseConnection, depth: Option, complexity: Option, ) -> SchemaBuilder { let mut builder = Builder::new(context, database.clone()); builder = register_entity_modules(builder); builder .set_depth_limit(depth) .set_complexity_limit(complexity) .schema_builder() .data(database) } ================================================ FILE: examples/seaography_example/graphql/tests/query_tests.rs ================================================ use async_graphql::{dynamic::*, Response}; use sea_orm::Database; use seaography::async_graphql; async fn schema() -> Schema { let database = Database::connect( std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://../bakery.db".into()), ) .await .unwrap(); sea_orm_seaography_example::query_root::schema(database, None, None).unwrap() } fn assert_eq(a: Response, b: &str) { assert_eq!( a.data.into_json().unwrap(), serde_json::from_str::(b).unwrap() ) } #[tokio::test] async fn test_cake_with_bakery() { let schema = schema().await; assert_eq( schema .execute( r#" { cake(filters: { name: { contains: "Chocolate" } }) { nodes { name price bakery { name } } } } "#, ) .await, r#" { "cake": { "nodes": [ { "name": "Chocolate Cake", "price": "10.25", "bakery": { "name": "SeaSide Bakery" } }, { "name": "Double Chocolate", "price": "12.5", "bakery": { "name": "SeaSide Bakery" } }, { "name": "Double Chocolate", "price": "12.5", "bakery": { "name": "LakeSide Bakery" } } ] } } "#, ) } #[tokio::test] async fn test_cake_with_baker() { let schema = schema().await; assert_eq( schema .execute( r#" { cake( filters: { name: { contains: "Cheese" } } having: { baker: { name: { eq: "Alice" } } } ) { nodes { name price baker { nodes { name } } } } } "#, ) .await, r#" { "cake": { "nodes": [ { "name": "New York Cheese", "price": "12.5", "baker": { "nodes": [ { "name": "Alice" }, { "name": "Bob" } ] } }, { "name": "New York Cheese", "price": "12.5", "baker": { "nodes": [ { "name": "Alice" }, { "name": "Bob" } ] } }, { "name": "Blueburry Cheese", "price": "11.5", "baker": { "nodes": [ { "name": "Alice" } ] } } ] } } "#, ) } #[tokio::test] async fn test_bakery_with_cake_with_baker() { let schema = schema().await; assert_eq( schema .execute( r#" { bakery(pagination: { page: { limit: 1, page: 0 } }, orderBy: { name: ASC }) { nodes { name cake { nodes { name price baker { nodes { name } } } } } } } "#, ) .await, r#" { "bakery": { "nodes": [ { "name": "LakeSide Bakery", "cake": { "nodes": [ { "name": "Double Chocolate", "price": "12.5", "baker": { "nodes": [ { "name": "Bob" } ] } }, { "name": "Lemon Cake", "price": "8.8", "baker": { "nodes": [ { "name": "Bob" } ] } }, { "name": "Strawberry Cake", "price": "9.9", "baker": { "nodes": [ { "name": "Bob" } ] } }, { "name": "Orange Cake", "price": "6.5", "baker": { "nodes": [ { "name": "Bob" } ] } }, { "name": "New York Cheese", "price": "12.5", "baker": { "nodes": [ { "name": "Alice" }, { "name": "Bob" } ] } }, { "name": "Blueburry Cheese", "price": "11.5", "baker": { "nodes": [ { "name": "Bob" } ] } } ] } } ] } } "#, ) } ================================================ FILE: examples/seaography_example/migration/Cargo.toml ================================================ [workspace] [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [dependencies.sea-orm-migration] features = [ "runtime-tokio-native-tls", "sqlx-sqlite", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/seaography_example/migration/README.md ================================================ # Bakery Schema ## MySQL Assume the database is named `bakery`: ```sql CREATE DATABASE bakery; GRANT ALL PRIVILEGES ON bakery.* TO sea; ``` ## SQLite ```sh export DATABASE_URL=sqlite://../bakery.db?mode=rwc ``` # Re-generate entities ```sh sea-orm-cli generate entity --output-dir src/entity ``` # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/seaography_example/migration/src/entity/baker.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "baker")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub contact: String, pub bakery_id: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::bakery::Entity", from = "Column::BakeryId", to = "super::bakery::Column::Id", on_update = "Cascade", on_delete = "Cascade" )] Bakery, #[sea_orm(has_many = "super::cake_baker::Entity")] CakeBaker, } impl Related for Entity { fn to() -> RelationDef { Relation::Bakery.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::CakeBaker.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_baker::Relation::Cake.def() } fn via() -> Option { Some(super::cake_baker::Relation::Baker.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/migration/src/entity/bakery.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "bakery")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(column_type = "Double")] pub profit_margin: f64, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::baker::Entity")] Baker, #[sea_orm(has_many = "super::cake::Entity")] Cake, } impl Related for Entity { fn to() -> RelationDef { Relation::Baker.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/migration/src/entity/cake.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(column_type = "Decimal(Some((16, 4)))")] pub price: Decimal, pub bakery_id: Option, pub gluten_free: i8, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::bakery::Entity", from = "Column::BakeryId", to = "super::bakery::Column::Id", on_update = "Cascade", on_delete = "Cascade" )] Bakery, #[sea_orm(has_many = "super::cake_baker::Entity")] CakeBaker, } impl Related for Entity { fn to() -> RelationDef { Relation::Bakery.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::CakeBaker.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_baker::Relation::Baker.def() } fn via() -> Option { Some(super::cake_baker::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/migration/src/entity/cake_baker.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake_baker")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub baker_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::baker::Entity", from = "Column::BakerId", to = "super::baker::Column::Id", on_update = "Cascade", on_delete = "Cascade" )] Baker, #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id", on_update = "Cascade", on_delete = "Cascade" )] Cake, } impl Related for Entity { fn to() -> RelationDef { Relation::Baker.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/seaography_example/migration/src/entity/mod.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 pub mod prelude; pub mod baker; pub mod bakery; pub mod cake; pub mod cake_baker; ================================================ FILE: examples/seaography_example/migration/src/entity/prelude.rs ================================================ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 pub use super::baker::Entity as Baker; pub use super::bakery::Entity as Bakery; pub use super::cake::Entity as Cake; pub use super::cake_baker::Entity as CakeBaker; ================================================ FILE: examples/seaography_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod entity; mod m20230101_000001_create_bakery_table; mod m20230101_000002_create_baker_table; mod m20230101_000003_create_cake_table; mod m20230101_000004_create_cake_baker_table; mod m20230101_000005_create_customer_table; mod m20230101_000006_create_order_table; mod m20230101_000007_create_lineitem_table; mod m20230102_000001_seed_bakery_data; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20230101_000001_create_bakery_table::Migration), Box::new(m20230101_000002_create_baker_table::Migration), Box::new(m20230101_000003_create_cake_table::Migration), Box::new(m20230101_000004_create_cake_baker_table::Migration), // Box::new(m20230101_000005_create_customer_table::Migration), // Box::new(m20230101_000006_create_order_table::Migration), // Box::new(m20230101_000007_create_lineitem_table::Migration), Box::new(m20230102_000001_seed_bakery_data::Migration), ] } } ================================================ FILE: examples/seaography_example/migration/src/m20230101_000001_create_bakery_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("bakery") .col(pk_auto("id")) .col(string("name")) .col(double("profit_margin")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("bakery").to_owned()) .await } } ================================================ FILE: examples/seaography_example/migration/src/m20230101_000002_create_baker_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("baker") .col(pk_auto("id")) .col(string("name")) .col(string("contact")) .col(integer_null("bakery_id")) .foreign_key( ForeignKey::create() .name("fk-baker-bakery_id") .from("baker", "bakery_id") .to("bakery", "id") .on_delete(ForeignKeyAction::Cascade) .on_update(ForeignKeyAction::Cascade), ) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("baker").to_owned()) .await } } ================================================ FILE: examples/seaography_example/migration/src/m20230101_000003_create_cake_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("cake") .col(pk_auto("id")) .col(string("name")) .col(decimal_len("price", 16, 4)) .col(integer("bakery_id")) .foreign_key( ForeignKey::create() .name("fk-cake-bakery_id") .from("cake", "bakery_id") .to("bakery", "id") .on_delete(ForeignKeyAction::Cascade) .on_update(ForeignKeyAction::Cascade), ) .col(boolean("gluten_free")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("cake").to_owned()) .await } } ================================================ FILE: examples/seaography_example/migration/src/m20230101_000004_create_cake_baker_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("cake_baker") .col(integer("cake_id")) .col(integer("baker_id")) .primary_key( Index::create() .name("pk-cake_baker") .col("cake_id") .col("baker_id"), ) .foreign_key( ForeignKey::create() .name("fk-cake_baker-cake_id") .from("cake_baker", "cake_id") .to("cake", "id") .on_delete(ForeignKeyAction::Cascade) .on_update(ForeignKeyAction::Cascade), ) .foreign_key( ForeignKey::create() .name("fk-cake_baker-baker_id") .from("cake_baker", "baker_id") .to("baker", "id") .on_delete(ForeignKeyAction::Cascade) .on_update(ForeignKeyAction::Cascade), ) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("cake_baker").to_owned()) .await } } ================================================ FILE: examples/seaography_example/migration/src/m20230101_000005_create_customer_table.rs ================================================ ================================================ FILE: examples/seaography_example/migration/src/m20230101_000006_create_order_table.rs ================================================ ================================================ FILE: examples/seaography_example/migration/src/m20230101_000007_create_lineitem_table.rs ================================================ ================================================ FILE: examples/seaography_example/migration/src/m20230102_000001_seed_bakery_data.rs ================================================ use crate::entity::{prelude::*, *}; use sea_orm::entity::*; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let bakery = bakery::ActiveModel { name: Set("SeaSide Bakery".to_owned()), profit_margin: Set(10.4), ..Default::default() }; let sea = Bakery::insert(bakery).exec(db).await?.last_insert_id; let bakery = bakery::ActiveModel { name: Set("LakeSide Bakery".to_owned()), profit_margin: Set(5.8), ..Default::default() }; let lake = Bakery::insert(bakery).exec(db).await?.last_insert_id; let alice = baker::ActiveModel { name: Set("Alice".to_owned()), contact: Set("+44 15273388".to_owned()), bakery_id: Set(Some(sea)), ..Default::default() }; let alice = Baker::insert(alice).exec(db).await?.last_insert_id; let bob = baker::ActiveModel { name: Set("Bob".to_owned()), contact: Set("+852 12345678".to_owned()), bakery_id: Set(Some(lake)), ..Default::default() }; let bob = Baker::insert(bob).exec(db).await?.last_insert_id; let cake = cake::ActiveModel { name: Set("Chocolate Cake".to_owned()), price: Set("10.25".parse().unwrap()), gluten_free: Set(0), bakery_id: Set(Some(sea)), ..Default::default() }; let choco = Cake::insert(cake).exec(db).await?.last_insert_id; let mut cake = cake::ActiveModel { name: Set("Double Chocolate".to_owned()), price: Set("12.5".parse().unwrap()), gluten_free: Set(0), bakery_id: Set(Some(sea)), ..Default::default() }; let double_1 = Cake::insert(cake.clone()).exec(db).await?.last_insert_id; cake.bakery_id = Set(Some(lake)); let double_2 = Cake::insert(cake).exec(db).await?.last_insert_id; let mut cake = cake::ActiveModel { name: Set("Lemon Cake".to_owned()), price: Set("8.8".parse().unwrap()), gluten_free: Set(0), bakery_id: Set(Some(sea)), ..Default::default() }; let lemon_1 = Cake::insert(cake.clone()).exec(db).await?.last_insert_id; cake.bakery_id = Set(Some(lake)); let lemon_2 = Cake::insert(cake).exec(db).await?.last_insert_id; let mut cake = cake::ActiveModel { name: Set("Strawberry Cake".to_owned()), price: Set("9.9".parse().unwrap()), gluten_free: Set(0), bakery_id: Set(Some(sea)), ..Default::default() }; let straw_1 = Cake::insert(cake.clone()).exec(db).await?.last_insert_id; cake.bakery_id = Set(Some(lake)); let straw_2 = Cake::insert(cake).exec(db).await?.last_insert_id; let cake = cake::ActiveModel { name: Set("Orange Cake".to_owned()), price: Set("6.5".parse().unwrap()), gluten_free: Set(1), bakery_id: Set(Some(lake)), ..Default::default() }; let orange = Cake::insert(cake).exec(db).await?.last_insert_id; let mut cake = cake::ActiveModel { name: Set("New York Cheese".to_owned()), price: Set("12.5".parse().unwrap()), gluten_free: Set(0), bakery_id: Set(Some(sea)), ..Default::default() }; let cheese_1 = Cake::insert(cake.clone()).exec(db).await?.last_insert_id; cake.bakery_id = Set(Some(lake)); let cheese_2 = Cake::insert(cake).exec(db).await?.last_insert_id; let mut cake = cake::ActiveModel { name: Set("Blueburry Cheese".to_owned()), price: Set("11.5".parse().unwrap()), gluten_free: Set(1), bakery_id: Set(Some(sea)), ..Default::default() }; let blue_1 = Cake::insert(cake.clone()).exec(db).await?.last_insert_id; cake.bakery_id = Set(Some(lake)); let blue_2 = Cake::insert(cake).exec(db).await?.last_insert_id; let rel = cake_baker::ActiveModel { cake_id: Set(choco), baker_id: Set(alice), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(double_1), baker_id: Set(alice), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(double_2), baker_id: Set(bob), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(lemon_1), baker_id: Set(alice), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(lemon_2), baker_id: Set(bob), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(straw_1), baker_id: Set(alice), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(straw_2), baker_id: Set(bob), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(orange), baker_id: Set(bob), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(cheese_1), baker_id: Set(alice), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(cheese_1), baker_id: Set(bob), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(cheese_2), baker_id: Set(alice), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(cheese_2), baker_id: Set(bob), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(blue_1), baker_id: Set(alice), }; CakeBaker::insert(rel).exec(db).await?; let rel = cake_baker::ActiveModel { cake_id: Set(blue_2), baker_id: Set(bob), }; CakeBaker::insert(rel).exec(db).await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); Cake::delete_many().exec(db).await?; Baker::delete_many().exec(db).await?; Bakery::delete_many().exec(db).await?; Ok(()) } } ================================================ FILE: examples/seaography_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/tonic_example/Cargo.toml ================================================ [package] edition = "2024" name = "sea-orm-tonic-example" publish = false rust-version = "1.85.0" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] members = [".", "api", "entity", "migration"] [dependencies] tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "full"] } tonic = "0.9.2" tonic-example-api = { path = "api" } [[bin]] name = "server" path = "./src/server.rs" [[bin]] name = "client" path = "./src/client.rs" ================================================ FILE: examples/tonic_example/README.md ================================================ # Tonic + gRPC + SeaORM Simple implementation of gRPC using SeaORM. run server using ```bash cargo run --bin server ``` run client using ```bash cargo run --bin client ``` Run tests: ```bash cd api cargo test ``` ================================================ FILE: examples/tonic_example/api/Cargo.toml ================================================ [package] edition = "2024" name = "tonic-example-api" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] entity = { path = "../entity" } migration = { path = "../migration" } prost = "0.11.9" serde = "1.0" tokio = { version = "1.29", features = ["full"] } tonic = "0.9.2" [dependencies.sea-orm] features = [ "debug-print", "runtime-tokio-rustls", # "sqlx-mysql", # "sqlx-postgres", "sqlx-sqlite", ] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version [build-dependencies] tonic-build = "0.9.2" ================================================ FILE: examples/tonic_example/api/build.rs ================================================ fn main() { let proto_file = "./proto/post.proto"; tonic_build::configure() .build_server(true) .compile(&[proto_file], &["."]) .unwrap_or_else(|e| panic!("protobuf compile error: {}", e)); println!("cargo:rerun-if-changed={}", proto_file); } ================================================ FILE: examples/tonic_example/api/proto/post.proto ================================================ syntax = "proto3"; package Post; service Blogpost { rpc GetPosts(PostPerPage) returns (PostList) {} rpc AddPost(Post) returns (PostId) {} rpc UpdatePost(Post) returns (ProcessStatus) {} rpc DeletePost(PostId) returns (ProcessStatus) {} rpc GetPostById(PostId) returns (Post) {} } message PostPerPage { uint64 per_page = 1; } message ProcessStatus { bool success = 1; } message PostId { int32 id = 1; } message Post { int32 id = 1; string title = 2; string content = 3; } message PostList { repeated Post post = 1; } ================================================ FILE: examples/tonic_example/api/src/lib.rs ================================================ pub mod service; use tonic::transport::Server; use tonic::{Request, Response, Status}; use entity::post; use migration::{Migrator, MigratorTrait}; use sea_orm::{Database, DatabaseConnection}; use service::{Mutation, Query}; use std::env; pub mod post_mod { tonic::include_proto!("post"); } use post_mod::{ Post, PostId, PostList, PostPerPage, ProcessStatus, blogpost_server::{Blogpost, BlogpostServer}, }; impl Post { fn into_model(self) -> post::Model { post::Model { id: self.id, title: self.title, text: self.content, } } } #[derive(Default)] pub struct MyServer { connection: DatabaseConnection, } #[tonic::async_trait] impl Blogpost for MyServer { async fn get_posts(&self, request: Request) -> Result, Status> { let conn = &self.connection; let posts_per_page = request.into_inner().per_page; let mut response = PostList { post: Vec::new() }; let (posts, _) = Query::find_posts_in_page(conn, 1, posts_per_page) .await .expect("Cannot find posts in page"); for post in posts { response.post.push(Post { id: post.id, title: post.title, content: post.text, }); } Ok(Response::new(response)) } async fn add_post(&self, request: Request) -> Result, Status> { let conn = &self.connection; let input = request.into_inner().into_model(); let inserted = Mutation::create_post(conn, input) .await .expect("could not insert post"); let response = PostId { id: inserted.id.unwrap(), }; Ok(Response::new(response)) } async fn update_post(&self, request: Request) -> Result, Status> { let conn = &self.connection; let input = request.into_inner().into_model(); match Mutation::update_post_by_id(conn, input.id, input).await { Ok(_) => Ok(Response::new(ProcessStatus { success: true })), Err(_) => Ok(Response::new(ProcessStatus { success: false })), } } async fn delete_post( &self, request: Request, ) -> Result, Status> { let conn = &self.connection; let id = request.into_inner().id; match Mutation::delete_post(conn, id).await { Ok(_) => Ok(Response::new(ProcessStatus { success: true })), Err(_) => Ok(Response::new(ProcessStatus { success: false })), } } async fn get_post_by_id(&self, request: Request) -> Result, Status> { let conn = &self.connection; let id = request.into_inner().id; if let Some(post) = Query::find_post_by_id(conn, id).await.ok().flatten() { Ok(Response::new(Post { id, title: post.title, content: post.text, })) } else { Err(Status::new( tonic::Code::Aborted, "Could not find post with id ".to_owned() + &id.to_string(), )) } } } #[tokio::main] async fn start() -> Result<(), Box> { let addr = "0.0.0.0:50051".parse()?; let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); // establish database connection let connection = Database::connect(&database_url).await?; Migrator::up(&connection, None).await?; let hello_server = MyServer { connection }; Server::builder() .add_service(BlogpostServer::new(hello_server)) .serve(addr) .await?; Ok(()) } pub fn main() { let result = start(); if let Some(err) = result.err() { println!("Error: {err}"); } } ================================================ FILE: examples/tonic_example/api/src/service/mod.rs ================================================ mod mutation; mod query; pub use mutation::*; pub use query::*; ================================================ FILE: examples/tonic_example/api/src/service/mutation.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Mutation; impl Mutation { pub async fn create_post( db: &DbConn, form_data: post::Model, ) -> Result { post::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await } pub async fn update_post_by_id( db: &DbConn, id: i32, form_data: post::Model, ) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await } pub async fn delete_post(db: &DbConn, id: i32) -> Result { let post: post::ActiveModel = Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom("Cannot find post.".to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: &DbConn) -> Result { Post::delete_many().exec(db).await } } ================================================ FILE: examples/tonic_example/api/src/service/query.rs ================================================ use ::entity::{post, post::Entity as Post}; use sea_orm::*; pub struct Query; impl Query { pub async fn find_post_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Post::find_by_id(id).one(db).await } /// If ok, returns (post models, num pages). pub async fn find_posts_in_page( db: &DbConn, page: u64, posts_per_page: u64, ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } } ================================================ FILE: examples/tonic_example/api/tests/crud_tests.rs ================================================ use entity::post; use sea_orm::Database; use tonic_example_api::service::{Mutation, Query}; #[tokio::test] async fn crud_tests() { let db = &Database::connect("sqlite::memory:").await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title A".to_owned(), text: "Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(1), title: sea_orm::ActiveValue::Unchanged("Title A".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text A".to_owned()) } ); } { let post = Mutation::create_post( db, post::Model { id: 0, title: "Title B".to_owned(), text: "Text B".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::ActiveModel { id: sea_orm::ActiveValue::Unchanged(2), title: sea_orm::ActiveValue::Unchanged("Title B".to_owned()), text: sea_orm::ActiveValue::Unchanged("Text B".to_owned()) } ); } { let post = Query::find_post_by_id(db, 1).await.unwrap().unwrap(); assert_eq!(post.id, 1); assert_eq!(post.title, "Title A"); } { let post = Mutation::update_post_by_id( db, 1, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), }, ) .await .unwrap(); assert_eq!( post, post::Model { id: 1, title: "New Title A".to_owned(), text: "New Text A".to_owned(), } ); } { let result = Mutation::delete_post(db, 2).await.unwrap(); assert_eq!(result.rows_affected, 1); } { let post = Query::find_post_by_id(db, 2).await.unwrap(); assert!(post.is_none()); } { let result = Mutation::delete_all_posts(db).await.unwrap(); assert_eq!(result.rows_affected, 1); } } ================================================ FILE: examples/tonic_example/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] serde = { version = "1", features = ["derive"] } [dependencies.sea-orm] path = "../../../" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm version ================================================ FILE: examples/tonic_example/entity/src/lib.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub mod prelude; pub mod post; ================================================ FILE: examples/tonic_example/entity/src/post.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: examples/tonic_example/entity/src/prelude.rs ================================================ //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.16 pub use super::post::Entity as Post; ================================================ FILE: examples/tonic_example/migration/Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] entity = { path = "../entity" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-sqlite", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.0-rc.37" # sea-orm-migration version ================================================ FILE: examples/tonic_example/migration/README.md ================================================ # Running Migrator CLI - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: examples/tonic_example/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220120_000001_create_post_table; mod m20220120_000002_seed_posts; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } } ================================================ FILE: examples/tonic_example/migration/src/m20220120_000001_create_post_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: examples/tonic_example/migration/src/m20220120_000002_seed_posts.rs ================================================ use entity::post; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; } println!("Posts table seeded successfully."); Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let titles_to_delete = vec!["First Post", "Second Post"]; post::Entity::delete_many() .filter(post::Column::Title.is_in(titles_to_delete)) .exec(db) .await?; println!("Posts seeded data removed."); Ok(()) } } ================================================ FILE: examples/tonic_example/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: examples/tonic_example/src/client.rs ================================================ use tonic::Request; use tonic::transport::Endpoint; use tonic_example_api::post_mod::{PostPerPage, blogpost_client::BlogpostClient}; #[tokio::main] async fn main() -> Result<(), Box> { let addr = Endpoint::from_static("http://0.0.0.0:50051"); /* Client code is not implemented in completely as it would just make the code base look too complicated .... and interface requires a lot of boilerplate code to implement. But a basic implementation is given below .... please refer it to implement other ways to make your code pretty */ let mut client = BlogpostClient::connect(addr).await?; let request = Request::new(PostPerPage { per_page: 10 }); let response = client.get_posts(request).await?; for post in response.into_inner().post.iter() { println!("{post:?}"); } Ok(()) } ================================================ FILE: examples/tonic_example/src/server.rs ================================================ fn main() { tonic_example_api::main(); } ================================================ FILE: issues/1143/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-1143" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] serde = "1" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } [dependencies.sea-orm] default-features = false features = ["macros", "runtime-tokio-native-tls"] path = "../../" ================================================ FILE: issues/1143/src/entity/mod.rs ================================================ pub mod sea_orm_active_enums; ================================================ FILE: issues/1143/src/entity/sea_orm_active_enums.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(StringLen::N(1))")] pub enum Category { #[sea_orm(string_value = "B")] Big, #[sea_orm(string_value = "S")] Small, } #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "i32", db_type = "Integer")] pub enum Color { #[sea_orm(num_value = 0)] Black, #[sea_orm(num_value = 1)] White, } #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")] pub enum Tea { #[sea_orm(string_value = "EverydayTea")] EverydayTea, #[sea_orm(string_value = "BreakfastTea")] BreakfastTea, } ================================================ FILE: issues/1143/src/main.rs ================================================ mod entity; #[tokio::main] async fn main() {} ================================================ FILE: issues/1278/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-1278" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] serde = "1" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } [dependencies.sea-orm] default-features = false features = ["macros", "runtime-tokio-native-tls"] path = "../../" ================================================ FILE: issues/1278/src/entity.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "pool")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/1278/src/main.rs ================================================ use sea_orm::{ Database, DeriveColumn, EntityTrait, EnumIter, FromQueryResult, QuerySelect, }; mod entity; #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryAs { PoolName, } #[derive(Debug, FromQueryResult)] struct PoolResult { name: String, } #[tokio::main] async fn main() { let db = Database::connect("xxxx").await.unwrap(); let result1 = entity::Entity::find() .select_only() .column(entity::Column::Name) .into_model::() .all(&db) .await .unwrap(); let result2: Vec = entity::Entity::find() .select_only() .column_as(entity::Column::Name, QueryAs::PoolName) .into_values::<_, QueryAs>() .all(&db) .await .unwrap(); } ================================================ FILE: issues/1357/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-1357" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] anyhow = "1" serde = "1" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } [dependencies.sea-orm] default-features = false features = ["macros", "runtime-tokio-native-tls", "sqlx-sqlite"] path = "../../" ================================================ FILE: issues/1357/src/entity.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "pool")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: i64, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/1357/src/main.rs ================================================ use anyhow::Result; use sea_orm::{ConnectionTrait, Database, EntityTrait, IntoActiveModel, Schema}; mod entity; use entity::*; #[tokio::main] async fn main() -> Result<()> { let db = Database::connect("sqlite::memory:").await.unwrap(); let builder = db.get_database_backend(); let schema = Schema::new(builder); let stmt = schema.create_table_from_entity(Entity); db.execute(builder.build(&stmt)).await?; let model = Model { id: 100, name: "Hello".to_owned(), }; let res = Entity::insert(model.clone().into_active_model()) .exec(&db) .await?; assert_eq!(Entity::find().one(&db).await?, Some(model.clone())); assert_eq!(res.last_insert_id, model.id); let model = Model { id: -10, name: "World".to_owned(), }; let res = Entity::insert(model.clone().into_active_model()) .exec(&db) .await?; assert_eq!(Entity::find().one(&db).await?, Some(model.clone())); assert_eq!(res.last_insert_id, model.id); Ok(()) } ================================================ FILE: issues/1473/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-1473" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies.sea-orm] default-features = false features = ["macros", "runtime-tokio-native-tls"] path = "../../" ================================================ FILE: issues/1473/src/main.rs ================================================ use sea_orm::{DeriveIden, Iden}; #[derive(DeriveIden)] enum Character { Table, Id, } #[derive(DeriveIden)] struct Glyph; fn main() { assert_eq!(Character::Table.to_string(), "character"); assert_eq!(Character::Id.to_string(), "id"); assert_eq!(Glyph.to_string(), "glyph"); } ================================================ FILE: issues/1582/schema.sql ================================================ CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Table without any dependencies CREATE TABLE IF NOT EXISTS public.table_1 ( id uuid DEFAULT public.uuid_generate_v4() NOT NULL PRIMARY KEY ); -- Table that depends on table_1 CREATE TABLE IF NOT EXISTS public.table_2 ( id uuid DEFAULT public.uuid_generate_v4() NOT NULL, table_1_id uuid NOT NULL REFERENCES public.table_1(id), -- Add constraints / uniques here if needed. Will auto apply to all partitions. PRIMARY KEY (id, table_1_id) ) PARTITION BY HASH (table_1_id); -- Generate partition tables. -- No clue if this works on other SQL implementations than PostgreSQL. DO $$ DECLARE counter integer := 0; BEGIN WHILE counter < 128 LOOP EXECUTE('CREATE TABLE IF NOT EXISTS public.table_2_p_hash_p' || counter || ' PARTITION OF public.table_2 FOR VALUES WITH (MODULUS 128, REMAINDER ' || counter || ');'); counter := counter + 1; END LOOP; END$$; -- Table that depends on table_1 and table_2. -- Foreign keys to table_2 are through the partitions CREATE TABLE IF NOT EXISTS public.table_3 ( id uuid DEFAULT public.uuid_generate_v4() NOT NULL PRIMARY KEY, -- FK to table_1 only needed so that we can FK to table_2 table_1_id uuid REFERENCES public.table_1(id) NOT NULL, table_2_id uuid NOT NULL, FOREIGN KEY (table_2_id, table_1_id) REFERENCES public.table_2(id, table_1_id) ); ================================================ FILE: issues/1599/Cargo.toml ================================================ [workspace] members = ["entity", "graphql"] ================================================ FILE: issues/1599/entity/Cargo.toml ================================================ [package] edition = "2024" name = "entity" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "entity" path = "src/lib.rs" [dependencies] async-graphql = { version = "5", optional = true } sea-orm = { path = "../../../" } seaography = { path = "../../../../seaography", optional = true } [features] seaography = ["dep:seaography", "async-graphql", "sea-orm/seaography"] ================================================ FILE: issues/1599/entity/src/cake.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_name = "name", enum_name = "Name")] pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, #[sea_orm( has_many = "super::fruit::Entity", on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# )] TropicalFruit, #[sea_orm( has_many = "super::fruit::Entity", condition_type = "any", on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# )] OrTropicalFruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity { #[sea_orm(entity = "super::fruit::Entity")] Fruit, #[sea_orm(entity = "super::filling::Entity")] Filling, #[sea_orm(entity = "super::fruit::Entity", def = "Relation::TropicalFruit.def()")] TropicalFruit, #[sea_orm( entity = "super::fruit::Entity", def = "Relation::OrTropicalFruit.def()" )] OrTropicalFruit, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/1599/entity/src/cake_filling.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> &'static str { "cake_filling" } } #[derive(Clone, Debug, PartialEq, Eq, DeriveModel, DeriveActiveModel)] pub struct Model { pub cake_id: i32, pub filling_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { CakeId, FillingId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { CakeId, FillingId, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = (i32, i32); fn auto_increment() -> bool { false } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, Filling, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::CakeId => ColumnType::Integer.def(), Self::FillingId => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), Self::Filling => Entity::belongs_to(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), } } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/1599/entity/src/filling.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] #[sea_orm(table_name = "filling")] pub struct Entity; #[derive(Clone, Debug, PartialEq, Eq, DeriveModel, DeriveActiveModel)] pub struct Model { pub id: i32, pub name: String, pub vendor_id: Option, #[sea_orm(ignore)] pub ignored_attr: i32, } // If your column names are not in snake-case, derive `DeriveCustomColumn` here. #[derive(Copy, Clone, Debug, EnumIter, DeriveCustomColumn)] pub enum Column { Id, Name, VendorId, } // Then, customize each column names here. impl IdenStatic for Column { fn as_str(&self) -> &str { match self { // Override column names Self::Id => "id", // Leave all other columns using default snake-case values _ => self.default_as_str(), } } } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(StringLen::None).def(), Self::VendorId => ColumnType::Integer.def().nullable(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Cake.def() } fn via() -> Option { Some(super::cake_filling::Relation::Filling.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/1599/entity/src/fruit.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "fruit")] pub struct Model { #[sea_orm(primary_key)] #[cfg_attr(feature = "with-json", serde(skip_deserializing))] pub id: i32, pub name: String, pub cake_id: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id" )] Cake, } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/1599/entity/src/lib.rs ================================================ pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; ================================================ FILE: issues/1599/graphql/Cargo.toml ================================================ [package] edition = "2024" name = "graphql" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] async-graphql = { version = "5.0.6", features = [ "decimal", "chrono", "dataloader", "dynamic-schema", ] } async-graphql-poem = { version = "5.0.6" } async-trait = { version = "0.1.64" } dotenv = "0.15.0" lazy_static = { version = "1.4.0" } poem = { version = "1.3.55" } tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] } tracing = { version = "0.1.37" } tracing-subscriber = { version = "0.3.16" } entity = { path = "../entity", features = ["seaography"] } sea-orm = { path = "../../../" } seaography = { path = "../../../../seaography" } ================================================ FILE: issues/1599/graphql/src/main.rs ================================================ use async_graphql::{ dataloader::DataLoader, http::{playground_source, GraphQLPlaygroundConfig}, }; use async_graphql_poem::GraphQL; use dotenv::dotenv; use lazy_static::lazy_static; use poem::{get, handler, listener::TcpListener, web::Html, IntoResponse, Route, Server}; use sea_orm::{prelude::*, Database}; use std::env; pub mod query_root; pub struct OrmDataloader { pub db: DatabaseConnection, } lazy_static! { static ref URL: String = env::var("URL").unwrap_or("0.0.0.0:8000".into()); static ref ENDPOINT: String = env::var("ENDPOINT").unwrap_or("/".into()); static ref DATABASE_URL: String = env::var("DATABASE_URL").expect("DATABASE_URL environment variable not set"); static ref DEPTH_LIMIT: Option = env::var("DEPTH_LIMIT").map_or(None, |data| Some( data.parse().expect("DEPTH_LIMIT is not a number") )); static ref COMPLEXITY_LIMIT: Option = env::var("COMPLEXITY_LIMIT") .map_or(None, |data| { Some(data.parse().expect("COMPLEXITY_LIMIT is not a number")) }); } #[handler] async fn graphql_playground() -> impl IntoResponse { Html(playground_source(GraphQLPlaygroundConfig::new(&ENDPOINT))) } #[tokio::main] async fn main() { dotenv().ok(); tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) .with_test_writer() .init(); let database = Database::connect(&*DATABASE_URL) .await .expect("Fail to initialize database connection"); let orm_dataloader: DataLoader = DataLoader::new( OrmDataloader { db: database.clone(), }, tokio::spawn, ); let schema = query_root::schema(database, orm_dataloader, *DEPTH_LIMIT, *COMPLEXITY_LIMIT).unwrap(); let app = Route::new().at( &*ENDPOINT, get(graphql_playground).post(GraphQL::new(schema)), ); println!("Visit GraphQL Playground at http://{}", *URL); Server::new(TcpListener::bind(&*URL)) .run(app) .await .expect("Fail to start web server"); } ================================================ FILE: issues/1599/graphql/src/query_root.rs ================================================ use crate::OrmDataloader; use async_graphql::{dataloader::DataLoader, dynamic::*}; use entity::*; use sea_orm::DatabaseConnection; use seaography::{Builder, BuilderContext}; lazy_static::lazy_static! { static ref CONTEXT : BuilderContext = BuilderContext :: default () ; } pub fn schema( database: DatabaseConnection, orm_dataloader: DataLoader, depth: Option, complexity: Option, ) -> Result { let mut builder = Builder::new(&CONTEXT); // Register entity including relations seaography::register_entities!(builder, [cake]); // Register entity only, no relations seaography::register_entities_without_relation!(builder, [cake_filling, filling, fruit]); let schema = builder.schema_builder(); let schema = if let Some(depth) = depth { schema.limit_depth(depth) } else { schema }; let schema = if let Some(complexity) = complexity { schema.limit_complexity(complexity) } else { schema }; schema.data(database).data(orm_dataloader).finish() } ================================================ FILE: issues/1790/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-1790" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] anyhow = "1" serde = "1" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } [dependencies.sea-orm] default-features = false features = ["macros", "runtime-tokio-native-tls", "sqlx-sqlite"] path = "../../" ================================================ FILE: issues/1790/insert_test.rs ================================================ mod tests { // currently ok #[test] fn insert_do_nothing_postgres() { assert_eq!( Insert::::new() .add(cake::Model { id: 1, name: "Apple Pie".to_owned(), }) .on_conflict(OnConflict::new() .do_nothing() .to_owned() ) .build(DbBackend::Postgres) .to_string(), r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie') ON CONFLICT DO NOTHING"#, ); } //failed to run #[test] fn insert_do_nothing_mysql() { assert_eq!( Insert::::new() .add(cake::Model { id: 1, name: "Apple Pie".to_owned(), }) .on_conflict(OnConflict::new() .do_nothing() .to_owned() ) .build(DbBackend::Mysql) .to_string(), r#"INSERT IGNORE INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, ); } // currently ok #[test] fn insert_do_nothing() { assert_eq!( Insert::::new() .add(cake::Model { id: 1, name: "Apple Pie".to_owned(), }) .on_conflict(OnConflict::new() .do_nothing() .to_owned() ) .build(DbBackend::Sqlite) .to_string(), r#"INSERT IGNORE INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, ); } } ================================================ FILE: issues/249/Cargo.toml ================================================ [workspace] members = ["service", "app"] ================================================ FILE: issues/249/README.md ================================================ # Demo of a pure logic crate depending on SeaORM with no enabled features ================================================ FILE: issues/249/app/Cargo.toml ================================================ [package] edition = "2024" name = "sea-orm-issues-249-app" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] sea-orm = { path = "../../../", default-features = false, features = [ "macros", "tests-cfg", "sqlx-sqlite", "runtime-async-std-native-tls", ] } service = { path = "../service" } ================================================ FILE: issues/249/app/src/main.rs ================================================ use service::clone_a_model; use sea_orm::tests_cfg::cake; fn main() { let c1 = cake::Model { id: 1, name: "Cheese".to_owned(), }; let c2 = clone_a_model(&c1); println!("{:?}", c2); } ================================================ FILE: issues/249/service/Cargo.toml ================================================ [package] edition = "2024" name = "service" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] sea-orm = { path = "../../../", default-features = false } [dev-dependencies] sea-orm = { path = "../../../", features = ["mock"] } ================================================ FILE: issues/249/service/src/lib.rs ================================================ pub use sea_orm::entity::*; pub fn clone_a_model(model: &M) -> M where M: ModelTrait { model.clone() } #[cfg(test)] mod tests { use super::*; #[test] fn test() { println!("OK"); } } ================================================ FILE: issues/262/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-262" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] async-std = { version = "1", features = ["attributes", "tokio1"] } sea-orm = { path = "../../", features = [ "sqlx-all", "runtime-async-std-native-tls", "debug-print", ] } ================================================ FILE: issues/262/src/cake.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub md5hash: String, pub md5_hash: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} #[cfg(test)] mod tests { use super::*; #[test] fn test_case_transform_1() { assert_eq!(Column::Md5hash.to_string().as_str(), "md5hash"); assert_eq!(Column::Md5Hash.to_string().as_str(), "md5_hash"); } } ================================================ FILE: issues/262/src/main.rs ================================================ mod cake; use sea_orm::*; #[async_std::main] pub async fn main() { let db = Database::connect("mysql://sea:sea@localhost/bakery") .await .unwrap(); async_std::task::spawn(async move { cake::Entity::find().one(&db).await.unwrap(); }) .await; } ================================================ FILE: issues/319/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-319" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] async-std = { version = "1", features = ["attributes", "tokio1"] } sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "with-json", "with-chrono", "macros", ], default-features = false } serde = { version = "1", features = ["derive"] } ================================================ FILE: issues/319/src/main.rs ================================================ mod material; use sea_orm::*; #[async_std::main] pub async fn main() { let db = Database::connect("mysql://sea:sea@localhost/bakery") .await .unwrap(); async_std::task::spawn(async move { material::Entity::find().one(&db).await.unwrap(); }) .await; } ================================================ FILE: issues/319/src/material.rs ================================================ use sea_orm::entity::prelude::*; use serde::{Serialize, Deserialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "materials")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub created_at: DateTimeWithTimeZone, pub updated_at: DateTimeWithTimeZone, pub name: String, #[sea_orm(column_type = "Text", nullable)] pub description: Option , pub tag_ids: Vec , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/324/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-324" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", ] } ================================================ FILE: issues/324/src/main.rs ================================================ mod model; pub fn main() {} ================================================ FILE: issues/324/src/model.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key)] pub id: AccountId, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} #[derive(Clone, Debug, PartialEq)] pub struct AccountId(Uuid); impl From for Uuid { fn from(account_id: AccountId) -> Self { account_id.0 } } macro_rules! impl_try_from_u64_err { ($newtype: ident) => { impl sea_orm::TryFromU64 for $newtype { fn try_from_u64(_n: u64) -> Result { Err(sea_orm::DbErr::ConvertFromU64(stringify!($newtype))) } } }; } macro_rules! into_sea_query_value { ($newtype: ident: Box($name: ident)) => { impl From<$newtype> for sea_orm::Value { fn from(source: $newtype) -> Self { sea_orm::Value::$name(Some(Box::new(source.into()))) } } impl sea_orm::TryGetable for $newtype { fn try_get( res: &sea_orm::QueryResult, pre: &str, col: &str, ) -> Result { let val: $name = res.try_get(pre, col).map_err(sea_orm::TryGetError::DbErr)?; Ok($newtype(val)) } } impl sea_orm::sea_query::Nullable for $newtype { fn null() -> sea_orm::Value { sea_orm::Value::$name(None) } } impl sea_orm::sea_query::ValueType for $newtype { fn try_from(v: sea_orm::Value) -> Result { match v { sea_orm::Value::$name(Some(x)) => Ok($newtype(*x)), _ => Err(sea_orm::sea_query::ValueTypeErr), } } fn type_name() -> String { stringify!($newtype).to_owned() } fn array_type() -> sea_orm::sea_query::ArrayType { sea_orm::sea_query::ArrayType::$name } fn column_type() -> sea_orm::sea_query::ColumnType { sea_orm::sea_query::ColumnType::$name } } }; } into_sea_query_value!(AccountId: Box(Uuid)); impl_try_from_u64_err!(AccountId); ================================================ FILE: issues/352/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-352" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", ] } ================================================ FILE: issues/352/src/binary_primary_key.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id_1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id_2: String, pub owner: String, pub name: String, pub description: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/352/src/main.rs ================================================ mod unary_primary_key; mod binary_primary_key; mod ternary_primary_key; mod quaternary_primary_key; mod quinary_primary_key; mod senary_primary_key; pub fn main() {} ================================================ FILE: issues/352/src/quaternary_primary_key.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id_1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id_2: String, #[sea_orm(primary_key, auto_increment = false)] pub id_3: f64, #[sea_orm(primary_key, auto_increment = false)] pub id_4: Uuid, pub owner: String, pub name: String, pub description: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/352/src/quinary_primary_key.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id_1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id_2: String, #[sea_orm(primary_key, auto_increment = false)] pub id_3: f64, #[sea_orm(primary_key, auto_increment = false)] pub id_4: Uuid, #[sea_orm(primary_key, auto_increment = false)] pub id_5: DateTime, pub owner: String, pub name: String, pub description: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/352/src/senary_primary_key.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id_1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id_2: String, #[sea_orm(primary_key, auto_increment = false)] pub id_3: f64, #[sea_orm(primary_key, auto_increment = false)] pub id_4: Uuid, #[sea_orm(primary_key, auto_increment = false)] pub id_5: DateTime, #[sea_orm(primary_key, auto_increment = false)] pub id_6: DateTimeWithTimeZone, pub owner: String, pub name: String, pub description: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/352/src/ternary_primary_key.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id_1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id_2: String, #[sea_orm(primary_key, auto_increment = false)] pub id_3: f64, pub owner: String, pub name: String, pub description: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/352/src/unary_primary_key.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id_1: i32, pub owner: String, pub name: String, pub description: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/356/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-356" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", ] } ================================================ FILE: issues/356/src/main.rs ================================================ mod model; pub fn main() {} ================================================ FILE: issues/356/src/model.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model", table_iden)] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub owner: String, pub name: String, pub description: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} #[cfg(test)] mod tests { use super::*; use sea_orm::*; #[test] fn test_columns_1() { assert_eq!( Column::iter() .map(|col| col.to_string()) .collect::>(), vec![ "id".to_owned(), "owner".to_owned(), "name".to_owned(), "description".to_owned(), ] ); assert_eq!(Column::Table.to_string().as_str(), "model"); assert_eq!(Column::Id.to_string().as_str(), "id"); assert_eq!(Column::Owner.to_string().as_str(), "owner"); assert_eq!(Column::Name.to_string().as_str(), "name"); assert_eq!(Column::Description.to_string().as_str(), "description"); } } ================================================ FILE: issues/400/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-400" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", ] } ================================================ FILE: issues/400/src/main.rs ================================================ mod model; pub fn main() {} ================================================ FILE: issues/400/src/model.rs ================================================ use sea_orm::entity::prelude::*; use std::marker::PhantomData; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "model")] pub struct Model { #[sea_orm(primary_key)] pub id: AccountId, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} #[derive(Clone, Debug, PartialEq)] pub struct AccountId(Uuid, PhantomData); impl AccountId { pub fn new(id: Uuid) -> Self { AccountId(id, PhantomData) } } impl From> for Uuid { fn from(account_id: AccountId) -> Self { account_id.0 } } impl sea_orm::TryFromU64 for AccountId { fn try_from_u64(_n: u64) -> Result { Err(sea_orm::DbErr::ConvertFromU64(stringify!(AccountId))) } } impl From> for sea_orm::Value { fn from(source: AccountId) -> Self { sea_orm::Value::Uuid(Some(Box::new(source.into()))) } } impl sea_orm::TryGetable for AccountId { fn try_get( res: &sea_orm::QueryResult, pre: &str, col: &str, ) -> Result { let val: Uuid = res.try_get(pre, col).map_err(sea_orm::TryGetError::DbErr)?; Ok(AccountId::::new(val)) } } impl sea_orm::sea_query::Nullable for AccountId { fn null() -> sea_orm::Value { sea_orm::Value::Uuid(None) } } impl sea_orm::sea_query::ValueType for AccountId { fn try_from(v: sea_orm::Value) -> Result { match v { sea_orm::Value::Uuid(Some(x)) => Ok(AccountId::::new(*x)), _ => Err(sea_orm::sea_query::ValueTypeErr), } } fn type_name() -> String { stringify!(AccountId).to_owned() } fn array_type() -> sea_orm::sea_query::ArrayType { sea_orm::sea_query::ArrayType::Uuid } fn column_type() -> sea_orm::sea_query::ColumnType { sea_orm::sea_query::ColumnType::Uuid } } ================================================ FILE: issues/471/Cargo.toml ================================================ [workspace] # A separate workspace [package] authors = ["Sebastian Pütz "] edition = "2024" name = "sea-orm-issues-400-471" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] anyhow = "1" dotenvy = "0.15" futures-util = "0.3" serde = "1" tokio = { version = "1.14", features = ["full"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } [dependencies.sea-orm] default-features = false features = [ "macros", "mock", "sqlx-all", "runtime-tokio-rustls", "debug-print", ] path = "../../" # remove this line in your own project ================================================ FILE: issues/471/README.md ================================================ Demonstrator for using streaming queries with `tokio::spawn` or in contexts that require `Send` futures. ================================================ FILE: issues/471/src/main.rs ================================================ mod post; mod setup; use futures_util::StreamExt; use post::Entity as Post; use sea_orm::{prelude::*, Database}; use std::env; #[tokio::main] async fn main() -> anyhow::Result<()> { unsafe { env::set_var("RUST_LOG", "debug"); } tracing_subscriber::fmt::init(); dotenvy::dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let db = Database::connect(db_url) .await .expect("Database connection failed"); let _ = setup::create_post_table(&db); tokio::task::spawn(async move { let mut stream = Post::find().stream(&db).await.unwrap(); while let Some(item) = stream.next().await { let item = item?; println!("got something: {}", item.text); } Ok::<(), anyhow::Error>(()) }) .await? } ================================================ FILE: issues/471/src/post.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.3.2 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "posts")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, #[sea_orm(column_type = "Text")] pub text: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/471/src/setup.rs ================================================ use sea_orm::sea_query::{ColumnDef, TableCreateStatement}; use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult}; async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { let builder = db.get_database_backend(); db.execute(builder.build(stmt)).await } pub async fn create_post_table(db: &DbConn) -> Result { let stmt = sea_query::Table::create() .table(super::post::Entity) .if_not_exists() .col( ColumnDef::new(super::post::Column::Id) .integer() .not_null() .auto_increment() .primary_key(), ) .col( ColumnDef::new(super::post::Column::Title) .string() .not_null(), ) .col( ColumnDef::new(super::post::Column::Text) .string() .not_null(), ) .to_owned(); create_table(db, &stmt).await } ================================================ FILE: issues/630/Cargo.toml ================================================ [workspace] # A separate workspace [package] authors = ["Erik Rhodes "] edition = "2024" name = "sea-orm-issues-630" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] serde = "1" tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } [dependencies.sea-orm] default-features = false features = [ "macros", "runtime-tokio-native-tls", "debug-print", "with-json", "with-chrono", ] path = "../../" [features] default = ["sqlx-mysql"] sqlx-mysql = ["sea-orm/sqlx-mysql"] ================================================ FILE: issues/630/README.md ================================================ # sea_orm_underscore_fields A minimal repository showing an issue with SeaORM. Connects to the database with `env!()`, so make sure to set `DATABASE_URL` when compiling. The file `src/entity/underscores_workaround.rs` shows the workaround to get the names to query correctly, and what happens if it's not included. ================================================ FILE: issues/630/create_underscores_table.sql ================================================ CREATE TABLE underscores ( `id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, `a_b_c_d` INT NOT NULL, `a_b_c_dd` INT NOT NULL, `a_b_cc_d` INT NOT NULL, `a_bb_c_d` INT NOT NULL, `aa_b_c_d` INT NOT NULL ); INSERT INTO underscores ( `a_b_c_d`, `a_b_c_dd`, `a_b_cc_d`, `a_bb_c_d`, `aa_b_c_d` ) VALUES (1, 2, 3, 4, 5); ================================================ FILE: issues/630/src/entity/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.6.0 pub mod prelude; pub mod underscores; pub mod underscores_workaround; ================================================ FILE: issues/630/src/entity/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.6.0 pub use super::underscores::Entity as Underscores; ================================================ FILE: issues/630/src/entity/underscores.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.6.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "underscores")] pub struct Model { #[sea_orm(primary_key)] pub id: u32, pub a_b_c_d: i32, pub a_b_c_dd: i32, pub a_b_cc_d: i32, pub a_bb_c_d: i32, pub aa_b_c_d: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} #[cfg(test)] mod tests { use super::*; use sea_orm::Iterable; #[test] fn column_names() { assert_eq!( Column::iter().map(|c| c.to_string()).collect::>(), vec!["id", "a_b_c_d", "a_b_c_dd", "a_b_cc_d", "a_bb_c_d", "aa_b_c_d"] ) } } ================================================ FILE: issues/630/src/entity/underscores_workaround.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.6.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "underscores")] pub struct Model { #[sea_orm(primary_key)] pub id: u32, #[sea_orm(column_name = "a_b_c_d")] pub a_b_c_d: i32, #[sea_orm(column_name = "a_b_c_dd")] pub a_b_c_dd: i32, #[sea_orm(column_name = "a_b_cc_d")] pub a_b_cc_d: i32, #[sea_orm(column_name = "a_bb_c_d")] pub a_bb_c_d: i32, #[sea_orm(column_name = "aa_b_c_d")] pub aa_b_c_d: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} #[cfg(test)] mod tests { use super::*; use sea_orm::Iterable; #[test] fn column_names() { assert_eq!( Column::iter().map(|c| c.to_string()).collect::>(), vec!["id", "a_b_c_d", "a_b_c_dd", "a_b_cc_d", "a_bb_c_d", "aa_b_c_d"] ) } } ================================================ FILE: issues/630/src/main.rs ================================================ use std::collections::HashMap; // use sea_orm::sea_query::tests_cfg::json; use sea_orm::{ConnectOptions, Database, EntityTrait}; mod entity; use entity::{underscores, underscores_workaround}; #[tokio::main] async fn main() { let url = option_env!("DATABASE_URL"); if let Some(url) = url { let opts = ConnectOptions::new(url.to_string()); let conn = Database::connect(opts).await.unwrap(); let results = underscores::Entity::find().all(&conn).await; dbg!(results); let results_workaround = underscores_workaround::Entity::find().all(&conn).await; dbg!(results_workaround); } let control = HashMap::from([ ("a_b_c_d", 1i32), ("a_b_c_dd", 2i32), ("a_b_cc_d", 3i32), ("a_bb_c_d", 4i32), ("aa_b_c_d", 5i32), ]); // let control = json!(control); dbg!(control); } ================================================ FILE: issues/693/Cargo.toml ================================================ [workspace] # A separate workspace [package] authors = ["bleuse "] edition = "2024" name = "sea-orm-issues-693" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] anyhow = "1" dotenvy = "0.15" futures-util = "0.3" serde = "1" tokio = { version = "1.14", features = ["full"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } [dependencies.sea-orm] default-features = false features = ["runtime-tokio-rustls", "sqlx-mysql", "macros"] path = "../../" # remove this line in your own project ================================================ FILE: issues/693/src/container.rs ================================================ pub mod prelude { pub use super::model::{ ActiveModel as ContainerActiveModel, Column as ContainerColumn, Entity as Container, Model as ContainerModel, PrimaryKey as ContainerPrimaryKey, Relation as ContainerRelation, }; } pub mod model { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "container")] pub struct Model { #[sea_orm(primary_key, column_name = "db_id")] pub rust_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "crate::Content")] Content, // 1(Container) ⇆ n(Content) } impl Related for Entity { fn to() -> RelationDef { Relation::Content.def() } } impl ActiveModelBehavior for ActiveModel {} } ================================================ FILE: issues/693/src/content.rs ================================================ pub mod prelude { pub use super::model::{ ActiveModel as ContentActiveModel, Column as ContentColumn, Entity as Content, Model as ContentModel, PrimaryKey as ContentPrimaryKey, Relation as ContentRelation, }; } pub mod model { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "content")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub container_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "crate::Container", from = "crate::ContentColumn::ContainerId", to = "crate::ContainerColumn::RustId" )] Container, // 1(Container) ⇆ n(Content) } impl Related for Entity { fn to() -> RelationDef { Relation::Container.def() } } impl ActiveModelBehavior for ActiveModel {} } ================================================ FILE: issues/693/src/main.rs ================================================ mod container; mod content; use container::prelude::*; use content::prelude::*; use sea_orm::{DbBackend, EntityTrait, QueryTrait}; fn main() { assert_eq!( Container::find().find_with_related(Content).build(DbBackend::MySql).to_string(), [ "SELECT `container`.`db_id` AS `A_db_id`, `content`.`id` AS `B_id`, `content`.`container_id` AS `B_container_id`", "FROM `container`", "LEFT JOIN `content` ON `container`.`db_id` = `content`.`container_id`", "ORDER BY `container`.`db_id` ASC", ].join(" ") ); } ================================================ FILE: issues/86/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-issues-86" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] sea-orm = { path = "../../", features = [ "sqlx-all", "runtime-tokio-native-tls", "debug-print", ] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } ================================================ FILE: issues/86/src/cake.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: issues/86/src/main.rs ================================================ mod cake; use sea_orm::*; #[tokio::main] pub async fn main() { tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .with_test_writer() .init(); let db = Database::connect("mysql://sea:sea@localhost/bakery") .await .unwrap(); tokio::spawn(async move { cake::Entity::find().one(&db).await.unwrap(); }) .await.unwrap(); } ================================================ FILE: issues/892/Cargo.toml ================================================ [workspace] # A separate workspace [package] authors = [] edition = "2024" name = "sea-orm-issues-892" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"] } [dependencies.sea-orm] default-features = false features = ["runtime-tokio-rustls", "tests-cfg", "sqlx-sqlite", "macros"] path = "../../" # remove this line in your own project ================================================ FILE: issues/892/src/main.rs ================================================ use sea_orm::tests_cfg::{cake, cake_filling}; use sea_orm::{Database, DbErr, EntityTrait, JoinType, QuerySelect, RelationTrait}; #[tokio::main] async fn main() -> Result<(), DbErr> { let db = Database::connect("sqlite::memory:").await?; tokio::spawn(async move { let _cakes = cake::Entity::find() .join_rev(JoinType::InnerJoin, cake_filling::Relation::Cake.def()) .all(&db) .await .unwrap(); }) .await .unwrap(); Ok(()) } ================================================ FILE: sea-orm-arrow/Cargo.toml ================================================ [package] authors = ["Chris Tsang "] categories = ["database"] description = "Arrow integration for SeaORM" documentation = "https://docs.rs/sea-orm-arrow" edition = "2024" homepage = "https://www.sea-ql.org/SeaORM" keywords = ["orm", "arrow", "parquet", "sea-orm"] license = "MIT OR Apache-2.0" name = "sea-orm-arrow" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "2.0.0-rc.4" [dependencies] arrow = { version = "58", default-features = false } sea-query = { version = "1.0.0-rc", default-features = false, features = [ "thread-safe", ] } thiserror = { version = "2", default-features = false } [features] prettyprint = ["arrow/prettyprint"] with-bigdecimal = ["sea-query/with-bigdecimal"] with-chrono = ["sea-query/with-chrono"] with-rust_decimal = ["sea-query/with-rust_decimal"] with-time = ["sea-query/with-time"] ================================================ FILE: sea-orm-arrow/src/lib.rs ================================================ pub use arrow; use arrow::array::*; use arrow::datatypes::i256; use sea_query::{ColumnType, Value}; // --------------------------------------------------------------------------- // Error type // --------------------------------------------------------------------------- /// Errors that can occur when converting between SeaORM [`Value`]s and Arrow arrays. #[derive(Debug, thiserror::Error)] pub enum ArrowError { /// The Arrow array type is incompatible with the target SeaORM column type. #[error("expected {expected} for column type {col_type}, got Arrow type {actual}")] TypeMismatch { expected: &'static str, col_type: &'static str, actual: String, }, /// A value lies outside the representable range for the target type. #[error("{0}")] OutOfRange(String), /// The column type or Arrow data type is not supported for conversion. #[error("{0}")] Unsupported(String), } fn type_err(expected: &'static str, col_type: &'static str, array: &dyn Array) -> ArrowError { ArrowError::TypeMismatch { expected, col_type, actual: format!("{:?}", array.data_type()), } } // --------------------------------------------------------------------------- // Arrow -> Value // --------------------------------------------------------------------------- /// Extract a [`Value`] from an Arrow array at the given row index, /// based on the expected [`ColumnType`] from the entity definition. /// /// For date/time column types, this produces chrono `Value` variants when /// the `with-chrono` feature is enabled, or time-crate variants when only /// `with-time` is enabled. pub fn arrow_array_to_value( array: &dyn Array, col_type: &ColumnType, row: usize, ) -> Result { if array.is_null(row) { return Ok(null_value_for_type(col_type)); } match col_type { ColumnType::TinyInteger => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("Int8Array", "TinyInteger", array))?; Ok(Value::TinyInt(Some(arr.value(row)))) } ColumnType::SmallInteger => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("Int16Array", "SmallInteger", array))?; Ok(Value::SmallInt(Some(arr.value(row)))) } ColumnType::Integer => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("Int32Array", "Integer", array))?; Ok(Value::Int(Some(arr.value(row)))) } ColumnType::BigInteger => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("Int64Array", "BigInteger", array))?; Ok(Value::BigInt(Some(arr.value(row)))) } ColumnType::TinyUnsigned => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("UInt8Array", "TinyUnsigned", array))?; Ok(Value::TinyUnsigned(Some(arr.value(row)))) } ColumnType::SmallUnsigned => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("UInt16Array", "SmallUnsigned", array))?; Ok(Value::SmallUnsigned(Some(arr.value(row)))) } ColumnType::Unsigned => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("UInt32Array", "Unsigned", array))?; Ok(Value::Unsigned(Some(arr.value(row)))) } ColumnType::BigUnsigned => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("UInt64Array", "BigUnsigned", array))?; Ok(Value::BigUnsigned(Some(arr.value(row)))) } ColumnType::Float => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("Float32Array", "Float", array))?; Ok(Value::Float(Some(arr.value(row)))) } ColumnType::Double => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("Float64Array", "Double", array))?; Ok(Value::Double(Some(arr.value(row)))) } ColumnType::String(_) | ColumnType::Text | ColumnType::Char(_) => { if let Some(arr) = array.as_any().downcast_ref::() { Ok(Value::String(Some(arr.value(row).to_owned()))) } else if let Some(arr) = array.as_any().downcast_ref::() { Ok(Value::String(Some(arr.value(row).to_owned()))) } else { Err(type_err( "StringArray or LargeStringArray", "String/Text", array, )) } } ColumnType::Boolean => { let arr = array .as_any() .downcast_ref::() .ok_or_else(|| type_err("BooleanArray", "Boolean", array))?; Ok(Value::Bool(Some(arr.value(row)))) } // Binary types ColumnType::Binary(_) | ColumnType::VarBinary(_) => arrow_to_bytes(array, row), // Decimal types ColumnType::Decimal(_) | ColumnType::Money(_) => arrow_to_decimal(array, row), // Date/time types: delegate to feature-gated helpers. // Prefer chrono when available; fall back to time crate. #[cfg(feature = "with-chrono")] ColumnType::Date => arrow_to_chrono_date(array, row), #[cfg(feature = "with-chrono")] ColumnType::Time => arrow_to_chrono_time(array, row), #[cfg(feature = "with-chrono")] ColumnType::DateTime | ColumnType::Timestamp => arrow_to_chrono_datetime(array, row), #[cfg(feature = "with-chrono")] ColumnType::TimestampWithTimeZone => arrow_to_chrono_datetime_utc(array, row), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::Date => arrow_to_time_date(array, row), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::Time => arrow_to_time_time(array, row), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::DateTime | ColumnType::Timestamp => arrow_to_time_datetime(array, row), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::TimestampWithTimeZone => arrow_to_time_datetime_tz(array, row), _ => Err(ArrowError::Unsupported(format!( "Unsupported column type for Arrow conversion: {col_type:?}" ))), } } /// When both `with-chrono` and `with-time` are enabled, this provides the /// time-crate alternative for date/time columns. Called as a fallback when /// the chrono Value variant doesn't match the model's field type. #[cfg(all(feature = "with-chrono", feature = "with-time"))] pub fn arrow_array_to_value_alt( array: &dyn Array, col_type: &ColumnType, row: usize, ) -> Result, ArrowError> { if array.is_null(row) { return Ok(Some(null_value_for_type_time(col_type))); } match col_type { ColumnType::Date => arrow_to_time_date(array, row).map(Some), ColumnType::Time => arrow_to_time_time(array, row).map(Some), ColumnType::DateTime | ColumnType::Timestamp => { arrow_to_time_datetime(array, row).map(Some) } ColumnType::TimestampWithTimeZone => arrow_to_time_datetime_tz(array, row).map(Some), _ => Ok(None), } } /// Returns true for ColumnTypes that may need a chrono->time fallback. pub fn is_datetime_column(col_type: &ColumnType) -> bool { matches!( col_type, ColumnType::Date | ColumnType::Time | ColumnType::DateTime | ColumnType::Timestamp | ColumnType::TimestampWithTimeZone ) } // --------------------------------------------------------------------------- // Binary helpers // --------------------------------------------------------------------------- fn arrow_to_bytes(array: &dyn Array, row: usize) -> Result { if let Some(arr) = array.as_any().downcast_ref::() { return Ok(Value::Bytes(Some(arr.value(row).to_vec()))); } if let Some(arr) = array.as_any().downcast_ref::() { return Ok(Value::Bytes(Some(arr.value(row).to_vec()))); } if let Some(arr) = array.as_any().downcast_ref::() { return Ok(Value::Bytes(Some(arr.value(row).to_vec()))); } Err(type_err( "BinaryArray, LargeBinaryArray, or FixedSizeBinaryArray", "Binary/VarBinary", array, )) } // --------------------------------------------------------------------------- // Decimal helpers // --------------------------------------------------------------------------- /// Convert Arrow Decimal128Array or Decimal256Array to a decimal Value. /// Prefers rust_decimal when available and precision fits, otherwise bigdecimal. fn arrow_to_decimal(array: &dyn Array, row: usize) -> Result { if let Some(arr) = array.as_any().downcast_ref::() { let value = arr.value(row); let precision = arr.precision(); let scale = arr.scale(); return decimal128_to_value(value, precision, scale); } if let Some(arr) = array.as_any().downcast_ref::() { let value = arr.value(row); let precision = arr.precision(); let scale = arr.scale(); return decimal64_to_value(value, precision, scale); } if let Some(arr) = array.as_any().downcast_ref::() { let value = arr.value(row); let precision = arr.precision(); let scale = arr.scale(); return decimal256_to_value(value, precision, scale); } Err(type_err( "Decimal64Array, Decimal128Array, or Decimal256Array", "Decimal", array, )) } #[cfg(feature = "with-rust_decimal")] fn decimal64_to_value(value: i64, _precision: u8, scale: i8) -> Result { use sea_query::prelude::Decimal; if scale < 0 { #[cfg(feature = "with-bigdecimal")] return decimal64_to_bigdecimal(value, scale); #[cfg(not(feature = "with-bigdecimal"))] return Err(ArrowError::Unsupported(format!( "Decimal64 with negative scale={scale} not supported by rust_decimal. \ Enable 'with-bigdecimal' feature." ))); } let decimal = Decimal::from_i128_with_scale(value as i128, scale as u32); Ok(Value::Decimal(Some(decimal))) } #[cfg(not(feature = "with-rust_decimal"))] fn decimal64_to_value(_value: i64, _precision: u8, _scale: i8) -> Result { #[cfg(feature = "with-bigdecimal")] return decimal64_to_bigdecimal(_value, _scale); #[cfg(not(feature = "with-bigdecimal"))] Err(ArrowError::Unsupported( "Decimal64Array requires 'with-rust_decimal' or 'with-bigdecimal' feature".into(), )) } #[cfg(feature = "with-bigdecimal")] fn decimal64_to_bigdecimal(value: i64, scale: i8) -> Result { use sea_query::prelude::bigdecimal::{BigDecimal, num_bigint::BigInt}; let bigint = BigInt::from(value); let decimal = BigDecimal::new(bigint, scale as i64); Ok(Value::BigDecimal(Some(Box::new(decimal)))) } #[cfg(feature = "with-rust_decimal")] fn decimal128_to_value(value: i128, precision: u8, scale: i8) -> Result { use sea_query::prelude::Decimal; if precision > 28 || scale > 28 || scale < 0 { #[cfg(feature = "with-bigdecimal")] return decimal128_to_bigdecimal(value, scale); #[cfg(not(feature = "with-bigdecimal"))] return Err(ArrowError::Unsupported(format!( "Decimal128 with precision={precision}, scale={scale} exceeds rust_decimal limits \ (max precision=28, scale=0-28). Enable 'with-bigdecimal' feature for arbitrary precision." ))); } let decimal = Decimal::from_i128_with_scale(value, scale as u32); Ok(Value::Decimal(Some(decimal))) } #[cfg(not(feature = "with-rust_decimal"))] fn decimal128_to_value(_value: i128, _precision: u8, _scale: i8) -> Result { #[cfg(feature = "with-bigdecimal")] return decimal128_to_bigdecimal(_value, _scale); #[cfg(not(feature = "with-bigdecimal"))] Err(ArrowError::Unsupported( "Decimal128Array requires 'with-rust_decimal' or 'with-bigdecimal' feature".into(), )) } #[cfg(feature = "with-bigdecimal")] fn decimal128_to_bigdecimal(value: i128, scale: i8) -> Result { use sea_query::prelude::bigdecimal::{BigDecimal, num_bigint::BigInt}; let bigint = BigInt::from(value); let decimal = BigDecimal::new(bigint, scale as i64); Ok(Value::BigDecimal(Some(Box::new(decimal)))) } fn decimal256_to_value(_value: i256, _precision: u8, _scale: i8) -> Result { #[cfg(feature = "with-bigdecimal")] { use sea_query::prelude::bigdecimal::{ BigDecimal, num_bigint::{BigInt, Sign}, }; let bytes = _value.to_be_bytes(); let (sign, magnitude) = if _value.is_negative() { let mut abs_bytes = [0u8; 32]; let mut carry = true; for i in (0..32).rev() { abs_bytes[i] = !bytes[i]; if carry { if abs_bytes[i] == 255 { abs_bytes[i] = 0; } else { abs_bytes[i] += 1; carry = false; } } } (Sign::Minus, abs_bytes.to_vec()) } else if _value == i256::ZERO { (Sign::NoSign, vec![0]) } else { let first_nonzero = bytes.iter().position(|&b| b != 0).unwrap_or(31); (Sign::Plus, bytes[first_nonzero..].to_vec()) }; let bigint = BigInt::from_bytes_be(sign, &magnitude); let decimal = BigDecimal::new(bigint, _scale as i64); return Ok(Value::BigDecimal(Some(Box::new(decimal)))); } #[cfg(not(feature = "with-bigdecimal"))] Err(ArrowError::Unsupported( "Decimal256Array requires 'with-bigdecimal' feature for arbitrary precision support".into(), )) } // --------------------------------------------------------------------------- // Chrono date/time helpers // --------------------------------------------------------------------------- #[cfg(feature = "with-chrono")] fn arrow_to_chrono_date(array: &dyn Array, row: usize) -> Result { use sea_query::prelude::chrono::NaiveDate; let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).expect("valid date"); if let Some(arr) = array.as_any().downcast_ref::() { let days = arr.value(row); let date = epoch .checked_add_signed(sea_query::prelude::chrono::Duration::days(days as i64)) .ok_or_else(|| ArrowError::OutOfRange(format!("Date32 value {days} out of range")))?; Ok(Value::ChronoDate(Some(date))) } else if let Some(arr) = array.as_any().downcast_ref::() { let ms = arr.value(row); let date = epoch .checked_add_signed(sea_query::prelude::chrono::Duration::milliseconds(ms)) .ok_or_else(|| ArrowError::OutOfRange(format!("Date64 value {ms} out of range")))?; Ok(Value::ChronoDate(Some(date))) } else { Err(type_err("Date32Array or Date64Array", "Date", array)) } } #[cfg(feature = "with-chrono")] fn arrow_to_chrono_time(array: &dyn Array, row: usize) -> Result { use sea_query::prelude::chrono::NaiveTime; if let Some(arr) = array.as_any().downcast_ref::() { let secs = arr.value(row) as u32; let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, 0).ok_or_else(|| { ArrowError::OutOfRange(format!("Time32Second value {secs} out of range")) })?; Ok(Value::ChronoTime(Some(t))) } else if let Some(arr) = array.as_any().downcast_ref::() { let ms = arr.value(row); let secs = (ms / 1_000) as u32; let nanos = ((ms % 1_000) * 1_000_000) as u32; let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or_else(|| { ArrowError::OutOfRange(format!("Time32Millisecond value {ms} out of range")) })?; Ok(Value::ChronoTime(Some(t))) } else if let Some(arr) = array.as_any().downcast_ref::() { let us = arr.value(row); let secs = (us / 1_000_000) as u32; let nanos = ((us % 1_000_000) * 1_000) as u32; let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or_else(|| { ArrowError::OutOfRange(format!("Time64Microsecond value {us} out of range")) })?; Ok(Value::ChronoTime(Some(t))) } else if let Some(arr) = array.as_any().downcast_ref::() { let ns = arr.value(row); let secs = (ns / 1_000_000_000) as u32; let nanos = (ns % 1_000_000_000) as u32; let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or_else(|| { ArrowError::OutOfRange(format!("Time64Nanosecond value {ns} out of range")) })?; Ok(Value::ChronoTime(Some(t))) } else { Err(type_err("Time32/Time64 Array", "Time", array)) } } #[cfg(feature = "with-chrono")] fn arrow_timestamp_to_utc( array: &dyn Array, row: usize, ) -> Result, ArrowError> { use sea_query::prelude::chrono::{DateTime, Utc}; if let Some(arr) = array.as_any().downcast_ref::() { DateTime::::from_timestamp(arr.value(row), 0) .ok_or_else(|| ArrowError::OutOfRange("Timestamp seconds out of range".into())) } else if let Some(arr) = array.as_any().downcast_ref::() { DateTime::::from_timestamp_millis(arr.value(row)) .ok_or_else(|| ArrowError::OutOfRange("Timestamp milliseconds out of range".into())) } else if let Some(arr) = array.as_any().downcast_ref::() { DateTime::::from_timestamp_micros(arr.value(row)) .ok_or_else(|| ArrowError::OutOfRange("Timestamp microseconds out of range".into())) } else if let Some(arr) = array.as_any().downcast_ref::() { let nanos = arr.value(row); let secs = nanos.div_euclid(1_000_000_000); let nsec = nanos.rem_euclid(1_000_000_000) as u32; DateTime::::from_timestamp(secs, nsec) .ok_or_else(|| ArrowError::OutOfRange("Timestamp nanoseconds out of range".into())) } else { Err(type_err( "TimestampSecond/Millisecond/Microsecond/NanosecondArray", "DateTime/Timestamp", array, )) } } #[cfg(feature = "with-chrono")] fn arrow_to_chrono_datetime(array: &dyn Array, row: usize) -> Result { let dt = arrow_timestamp_to_utc(array, row)?; Ok(Value::ChronoDateTime(Some(dt.naive_utc()))) } #[cfg(feature = "with-chrono")] fn arrow_to_chrono_datetime_utc(array: &dyn Array, row: usize) -> Result { let dt = arrow_timestamp_to_utc(array, row)?; Ok(Value::ChronoDateTimeUtc(Some(dt))) } // --------------------------------------------------------------------------- // Time-crate date/time helpers // --------------------------------------------------------------------------- #[cfg(feature = "with-time")] fn arrow_to_time_date(array: &dyn Array, row: usize) -> Result { const EPOCH_JULIAN: i32 = 2_440_588; if let Some(arr) = array.as_any().downcast_ref::() { let days = arr.value(row); let date = sea_query::prelude::time::Date::from_julian_day(EPOCH_JULIAN + days).map_err(|e| { ArrowError::OutOfRange(format!("Date32 value {days} out of range: {e}")) })?; Ok(Value::TimeDate(Some(date))) } else if let Some(arr) = array.as_any().downcast_ref::() { let ms = arr.value(row); let days = (ms / 86_400_000) as i32; let date = sea_query::prelude::time::Date::from_julian_day(EPOCH_JULIAN + days) .map_err(|e| ArrowError::OutOfRange(format!("Date64 value {ms} out of range: {e}")))?; Ok(Value::TimeDate(Some(date))) } else { Err(type_err("Date32Array or Date64Array", "Date", array)) } } #[cfg(feature = "with-time")] fn arrow_to_time_time(array: &dyn Array, row: usize) -> Result { if let Some(arr) = array.as_any().downcast_ref::() { let secs = arr.value(row); let t = sea_query::prelude::time::Time::from_hms( (secs / 3600) as u8, ((secs % 3600) / 60) as u8, (secs % 60) as u8, ) .map_err(|e| { ArrowError::OutOfRange(format!("Time32Second value {secs} out of range: {e}")) })?; Ok(Value::TimeTime(Some(t))) } else if let Some(arr) = array.as_any().downcast_ref::() { let ms = arr.value(row); let total_secs = ms / 1_000; let nanos = ((ms % 1_000) * 1_000_000) as u32; let t = sea_query::prelude::time::Time::from_hms_nano( (total_secs / 3600) as u8, ((total_secs % 3600) / 60) as u8, (total_secs % 60) as u8, nanos, ) .map_err(|e| { ArrowError::OutOfRange(format!("Time32Millisecond value {ms} out of range: {e}")) })?; Ok(Value::TimeTime(Some(t))) } else if let Some(arr) = array.as_any().downcast_ref::() { let us = arr.value(row); let total_secs = us / 1_000_000; let nanos = ((us % 1_000_000) * 1_000) as u32; let t = sea_query::prelude::time::Time::from_hms_nano( (total_secs / 3600) as u8, ((total_secs % 3600) / 60) as u8, (total_secs % 60) as u8, nanos, ) .map_err(|e| { ArrowError::OutOfRange(format!("Time64Microsecond value {us} out of range: {e}")) })?; Ok(Value::TimeTime(Some(t))) } else if let Some(arr) = array.as_any().downcast_ref::() { let ns = arr.value(row); let total_secs = ns / 1_000_000_000; let nanos = (ns % 1_000_000_000) as u32; let t = sea_query::prelude::time::Time::from_hms_nano( (total_secs / 3600) as u8, ((total_secs % 3600) / 60) as u8, (total_secs % 60) as u8, nanos, ) .map_err(|e| { ArrowError::OutOfRange(format!("Time64Nanosecond value {ns} out of range: {e}")) })?; Ok(Value::TimeTime(Some(t))) } else { Err(type_err("Time32/Time64 Array", "Time", array)) } } #[cfg(feature = "with-time")] fn arrow_timestamp_to_offset_dt( array: &dyn Array, row: usize, ) -> Result { if let Some(arr) = array.as_any().downcast_ref::() { sea_query::prelude::time::OffsetDateTime::from_unix_timestamp(arr.value(row)) .map_err(|e| ArrowError::OutOfRange(format!("Timestamp seconds out of range: {e}"))) } else if let Some(arr) = array.as_any().downcast_ref::() { let ms = arr.value(row); sea_query::prelude::time::OffsetDateTime::from_unix_timestamp_nanos(ms as i128 * 1_000_000) .map_err(|e| { ArrowError::OutOfRange(format!("Timestamp milliseconds out of range: {e}")) }) } else if let Some(arr) = array.as_any().downcast_ref::() { let us = arr.value(row); sea_query::prelude::time::OffsetDateTime::from_unix_timestamp_nanos(us as i128 * 1_000) .map_err(|e| { ArrowError::OutOfRange(format!("Timestamp microseconds out of range: {e}")) }) } else if let Some(arr) = array.as_any().downcast_ref::() { sea_query::prelude::time::OffsetDateTime::from_unix_timestamp_nanos(arr.value(row) as i128) .map_err(|e| ArrowError::OutOfRange(format!("Timestamp nanoseconds out of range: {e}"))) } else { Err(type_err( "TimestampSecond/Millisecond/Microsecond/NanosecondArray", "DateTime/Timestamp", array, )) } } #[cfg(feature = "with-time")] fn arrow_to_time_datetime(array: &dyn Array, row: usize) -> Result { let odt = arrow_timestamp_to_offset_dt(array, row)?; Ok(Value::TimeDateTime(Some( sea_query::prelude::time::PrimitiveDateTime::new(odt.date(), odt.time()), ))) } #[cfg(feature = "with-time")] fn arrow_to_time_datetime_tz(array: &dyn Array, row: usize) -> Result { let odt = arrow_timestamp_to_offset_dt(array, row)?; Ok(Value::TimeDateTimeWithTimeZone(Some(odt))) } // --------------------------------------------------------------------------- // Null value helpers // --------------------------------------------------------------------------- fn null_value_for_type(col_type: &ColumnType) -> Value { match col_type { ColumnType::TinyInteger => Value::TinyInt(None), ColumnType::SmallInteger => Value::SmallInt(None), ColumnType::Integer => Value::Int(None), ColumnType::BigInteger => Value::BigInt(None), ColumnType::TinyUnsigned => Value::TinyUnsigned(None), ColumnType::SmallUnsigned => Value::SmallUnsigned(None), ColumnType::Unsigned => Value::Unsigned(None), ColumnType::BigUnsigned => Value::BigUnsigned(None), ColumnType::Float => Value::Float(None), ColumnType::Double => Value::Double(None), ColumnType::String(_) | ColumnType::Text | ColumnType::Char(_) => Value::String(None), ColumnType::Binary(_) | ColumnType::VarBinary(_) => Value::Bytes(None), ColumnType::Boolean => Value::Bool(None), #[cfg(feature = "with-rust_decimal")] ColumnType::Decimal(_) | ColumnType::Money(_) => Value::Decimal(None), #[cfg(all(feature = "with-bigdecimal", not(feature = "with-rust_decimal")))] ColumnType::Decimal(_) | ColumnType::Money(_) => Value::BigDecimal(None), #[cfg(feature = "with-chrono")] ColumnType::Date => Value::ChronoDate(None), #[cfg(feature = "with-chrono")] ColumnType::Time => Value::ChronoTime(None), #[cfg(feature = "with-chrono")] ColumnType::DateTime | ColumnType::Timestamp => Value::ChronoDateTime(None), #[cfg(feature = "with-chrono")] ColumnType::TimestampWithTimeZone => Value::ChronoDateTimeUtc(None), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::Date => Value::TimeDate(None), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::Time => Value::TimeTime(None), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::DateTime | ColumnType::Timestamp => Value::TimeDateTime(None), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] ColumnType::TimestampWithTimeZone => Value::TimeDateTimeWithTimeZone(None), _ => Value::Int(None), } } /// Null values for the time crate variants, used by the alt-value fallback path. #[cfg(all(feature = "with-chrono", feature = "with-time"))] fn null_value_for_type_time(col_type: &ColumnType) -> Value { match col_type { ColumnType::Date => Value::TimeDate(None), ColumnType::Time => Value::TimeTime(None), ColumnType::DateTime | ColumnType::Timestamp => Value::TimeDateTime(None), ColumnType::TimestampWithTimeZone => Value::TimeDateTimeWithTimeZone(None), _ => null_value_for_type(col_type), } } // --------------------------------------------------------------------------- // Value -> Arrow // --------------------------------------------------------------------------- /// Convert a slice of [`Value`]s to an Arrow array matching the /// target [`DataType`](arrow::datatypes::DataType). /// /// `Value::Variant(None)` (SQL NULL) entries become null in the array. pub fn values_to_arrow_array( values: &[Value], data_type: &arrow::datatypes::DataType, ) -> Result, ArrowError> { use arrow::datatypes::{DataType, TimeUnit}; use std::sync::Arc; match data_type { DataType::Int8 => { let arr: Int8Array = values .iter() .map(|v| match v { Value::TinyInt(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Int16 => { let arr: Int16Array = values .iter() .map(|v| match v { Value::SmallInt(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Int32 => { let arr: Int32Array = values .iter() .map(|v| match v { Value::Int(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Int64 => { let arr: Int64Array = values .iter() .map(|v| match v { Value::BigInt(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt8 => { let arr: UInt8Array = values .iter() .map(|v| match v { Value::TinyUnsigned(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt16 => { let arr: UInt16Array = values .iter() .map(|v| match v { Value::SmallUnsigned(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt32 => { let arr: UInt32Array = values .iter() .map(|v| match v { Value::Unsigned(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt64 => { let arr: UInt64Array = values .iter() .map(|v| match v { Value::BigUnsigned(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Float32 => { let arr: Float32Array = values .iter() .map(|v| match v { Value::Float(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Float64 => { let arr: Float64Array = values .iter() .map(|v| match v { Value::Double(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Boolean => { let arr: BooleanArray = values .iter() .map(|v| match v { Value::Bool(inner) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Utf8 => { let strs: Vec> = values .iter() .map(|v| match v { Value::String(Some(s)) => Some(s.as_str()), _ => None, }) .collect(); Ok(Arc::new(StringArray::from(strs))) } DataType::LargeUtf8 => { let strs: Vec> = values .iter() .map(|v| match v { Value::String(Some(s)) => Some(s.as_str()), _ => None, }) .collect(); Ok(Arc::new(LargeStringArray::from(strs))) } DataType::Binary => { let bufs: Vec> = values .iter() .map(|v| match v { Value::Bytes(Some(b)) => Some(b.as_slice()), _ => None, }) .collect(); Ok(Arc::new(BinaryArray::from(bufs))) } DataType::LargeBinary => { let bufs: Vec> = values .iter() .map(|v| match v { Value::Bytes(Some(b)) => Some(b.as_slice()), _ => None, }) .collect(); Ok(Arc::new(LargeBinaryArray::from(bufs))) } DataType::FixedSizeBinary(byte_width) => { let mut builder = FixedSizeBinaryBuilder::with_capacity(values.len(), *byte_width); for v in values { match v { Value::Bytes(Some(b)) => builder.append_value(b.as_slice()).map_err(|e| { ArrowError::Unsupported(format!("FixedSizeBinary append error: {e}")) })?, _ => builder.append_null(), } } Ok(Arc::new(builder.finish())) } DataType::Date32 => { let arr: Date32Array = values.iter().map(extract_date32).collect(); Ok(Arc::new(arr)) } DataType::Time32(unit) => { let vals: Vec> = values.iter().map(|v| extract_time32(v, unit)).collect(); let arr: Arc = match unit { TimeUnit::Second => Arc::new(Time32SecondArray::from(vals)), TimeUnit::Millisecond => Arc::new(Time32MillisecondArray::from(vals)), _ => { return Err(ArrowError::Unsupported(format!( "Unsupported Time32 unit: {unit:?}" ))); } }; Ok(arr) } DataType::Time64(unit) => { let vals: Vec> = values.iter().map(|v| extract_time64(v, unit)).collect(); let arr: Arc = match unit { TimeUnit::Microsecond => Arc::new(Time64MicrosecondArray::from(vals)), TimeUnit::Nanosecond => Arc::new(Time64NanosecondArray::from(vals)), _ => { return Err(ArrowError::Unsupported(format!( "Unsupported Time64 unit: {unit:?}" ))); } }; Ok(arr) } DataType::Timestamp(unit, tz) => { let vals: Vec> = values.iter().map(|v| extract_timestamp(v, unit)).collect(); let arr: Arc = match unit { TimeUnit::Second => { let mut a = TimestampSecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } TimeUnit::Millisecond => { let mut a = TimestampMillisecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } TimeUnit::Microsecond => { let mut a = TimestampMicrosecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } TimeUnit::Nanosecond => { let mut a = TimestampNanosecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } }; Ok(arr) } DataType::Decimal64(precision, scale) => { let arr: Decimal64Array = values .iter() .map(|v| extract_decimal64(v, *scale)) .collect(); let arr = arr .with_precision_and_scale(*precision, *scale) .map_err(|e| { ArrowError::Unsupported(format!("Invalid Decimal64 precision/scale: {e}")) })?; Ok(Arc::new(arr)) } DataType::Decimal128(precision, scale) => { let arr: Decimal128Array = values .iter() .map(|v| extract_decimal128(v, *scale)) .collect(); let arr = arr .with_precision_and_scale(*precision, *scale) .map_err(|e| { ArrowError::Unsupported(format!("Invalid Decimal128 precision/scale: {e}")) })?; Ok(Arc::new(arr)) } DataType::Decimal256(precision, scale) => { let arr: Decimal256Array = values .iter() .map(|v| extract_decimal256(v, *scale)) .collect(); let arr = arr .with_precision_and_scale(*precision, *scale) .map_err(|e| { ArrowError::Unsupported(format!("Invalid Decimal256 precision/scale: {e}")) })?; Ok(Arc::new(arr)) } _ => Err(ArrowError::Unsupported(format!( "Unsupported Arrow DataType for to_arrow: {data_type:?}" ))), } } /// Convert a slice of optional [`Value`]s to an Arrow array matching the /// target [`DataType`](arrow::datatypes::DataType). /// /// `None` entries (from `ActiveValue::NotSet`) become null in the array. /// `Some(Value::Variant(None))` (SQL NULL) also become null. pub fn option_values_to_arrow_array( values: &[Option], data_type: &arrow::datatypes::DataType, ) -> Result, ArrowError> { use arrow::datatypes::{DataType, TimeUnit}; use std::sync::Arc; match data_type { DataType::Int8 => { let arr: Int8Array = values .iter() .map(|v| match v { Some(Value::TinyInt(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Int16 => { let arr: Int16Array = values .iter() .map(|v| match v { Some(Value::SmallInt(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Int32 => { let arr: Int32Array = values .iter() .map(|v| match v { Some(Value::Int(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Int64 => { let arr: Int64Array = values .iter() .map(|v| match v { Some(Value::BigInt(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt8 => { let arr: UInt8Array = values .iter() .map(|v| match v { Some(Value::TinyUnsigned(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt16 => { let arr: UInt16Array = values .iter() .map(|v| match v { Some(Value::SmallUnsigned(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt32 => { let arr: UInt32Array = values .iter() .map(|v| match v { Some(Value::Unsigned(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::UInt64 => { let arr: UInt64Array = values .iter() .map(|v| match v { Some(Value::BigUnsigned(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Float32 => { let arr: Float32Array = values .iter() .map(|v| match v { Some(Value::Float(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Float64 => { let arr: Float64Array = values .iter() .map(|v| match v { Some(Value::Double(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Boolean => { let arr: BooleanArray = values .iter() .map(|v| match v { Some(Value::Bool(inner)) => *inner, _ => None, }) .collect(); Ok(Arc::new(arr)) } DataType::Utf8 => { let strs: Vec> = values .iter() .map(|v| match v { Some(Value::String(Some(s))) => Some(s.as_str()), _ => None, }) .collect(); Ok(Arc::new(StringArray::from(strs))) } DataType::LargeUtf8 => { let strs: Vec> = values .iter() .map(|v| match v { Some(Value::String(Some(s))) => Some(s.as_str()), _ => None, }) .collect(); Ok(Arc::new(LargeStringArray::from(strs))) } DataType::Binary => { let bufs: Vec> = values .iter() .map(|v| match v { Some(Value::Bytes(Some(b))) => Some(b.as_slice()), _ => None, }) .collect(); Ok(Arc::new(BinaryArray::from(bufs))) } DataType::LargeBinary => { let bufs: Vec> = values .iter() .map(|v| match v { Some(Value::Bytes(Some(b))) => Some(b.as_slice()), _ => None, }) .collect(); Ok(Arc::new(LargeBinaryArray::from(bufs))) } DataType::FixedSizeBinary(byte_width) => { let mut builder = FixedSizeBinaryBuilder::with_capacity(values.len(), *byte_width); for v in values { match v { Some(Value::Bytes(Some(b))) => { builder.append_value(b.as_slice()).map_err(|e| { ArrowError::Unsupported(format!("FixedSizeBinary append error: {e}")) })? } _ => builder.append_null(), } } Ok(Arc::new(builder.finish())) } DataType::Date32 => { let arr: Date32Array = values.iter().map(extract_date32_option).collect(); Ok(Arc::new(arr)) } DataType::Time32(unit) => { let vals: Vec> = values .iter() .map(|v| extract_time32_option(v, unit)) .collect(); let arr: Arc = match unit { TimeUnit::Second => Arc::new(Time32SecondArray::from(vals)), TimeUnit::Millisecond => Arc::new(Time32MillisecondArray::from(vals)), _ => { return Err(ArrowError::Unsupported(format!( "Unsupported Time32 unit: {unit:?}" ))); } }; Ok(arr) } DataType::Time64(unit) => { let vals: Vec> = values .iter() .map(|v| extract_time64_option(v, unit)) .collect(); let arr: Arc = match unit { TimeUnit::Microsecond => Arc::new(Time64MicrosecondArray::from(vals)), TimeUnit::Nanosecond => Arc::new(Time64NanosecondArray::from(vals)), _ => { return Err(ArrowError::Unsupported(format!( "Unsupported Time64 unit: {unit:?}" ))); } }; Ok(arr) } DataType::Timestamp(unit, tz) => { let vals: Vec> = values .iter() .map(|v| extract_timestamp_option(v, unit)) .collect(); let arr: Arc = match unit { TimeUnit::Second => { let mut a = TimestampSecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } TimeUnit::Millisecond => { let mut a = TimestampMillisecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } TimeUnit::Microsecond => { let mut a = TimestampMicrosecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } TimeUnit::Nanosecond => { let mut a = TimestampNanosecondArray::from(vals); if let Some(tz) = tz { a = a.with_timezone(tz.as_ref()); } Arc::new(a) } }; Ok(arr) } DataType::Decimal64(precision, scale) => { let arr: Decimal64Array = values .iter() .map(|v| extract_decimal64_option(v, *scale)) .collect(); let arr = arr .with_precision_and_scale(*precision, *scale) .map_err(|e| { ArrowError::Unsupported(format!("Invalid Decimal64 precision/scale: {e}")) })?; Ok(Arc::new(arr)) } DataType::Decimal128(precision, scale) => { let arr: Decimal128Array = values .iter() .map(|v| extract_decimal128_option(v, *scale)) .collect(); let arr = arr .with_precision_and_scale(*precision, *scale) .map_err(|e| { ArrowError::Unsupported(format!("Invalid Decimal128 precision/scale: {e}")) })?; Ok(Arc::new(arr)) } DataType::Decimal256(precision, scale) => { let arr: Decimal256Array = values .iter() .map(|v| extract_decimal256_option(v, *scale)) .collect(); let arr = arr .with_precision_and_scale(*precision, *scale) .map_err(|e| { ArrowError::Unsupported(format!("Invalid Decimal256 precision/scale: {e}")) })?; Ok(Arc::new(arr)) } _ => Err(ArrowError::Unsupported(format!( "Unsupported Arrow DataType for to_arrow: {data_type:?}" ))), } } // --------------------------------------------------------------------------- // Date extraction helpers // --------------------------------------------------------------------------- fn extract_date32_option(v: &Option) -> Option { extract_date32(v.as_ref()?) } fn extract_date32(v: &Value) -> Option { #[cfg(feature = "with-chrono")] if let Value::ChronoDate(Some(d)) = v { let epoch = sea_query::prelude::chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(); return Some((*d - epoch).num_days() as i32); } #[cfg(feature = "with-time")] if let Value::TimeDate(Some(d)) = v { return Some(d.to_julian_day() - 2_440_588); } let _ = v; None } // --------------------------------------------------------------------------- // Time extraction helpers // --------------------------------------------------------------------------- fn extract_time32_option(v: &Option, unit: &arrow::datatypes::TimeUnit) -> Option { extract_time32(v.as_ref()?, unit) } fn extract_time32(v: &Value, unit: &arrow::datatypes::TimeUnit) -> Option { #[cfg(any(feature = "with-chrono", feature = "with-time"))] use arrow::datatypes::TimeUnit; #[cfg(feature = "with-chrono")] if let Value::ChronoTime(Some(t)) = v { use sea_query::prelude::chrono::Timelike; let secs = t.num_seconds_from_midnight() as i32; return match unit { TimeUnit::Second => Some(secs), TimeUnit::Millisecond => { let ms = (t.nanosecond() / 1_000_000) as i32; Some(secs * 1_000 + ms) } _ => None, }; } #[cfg(feature = "with-time")] if let Value::TimeTime(Some(t)) = v { let secs = (t.hour() as i32) * 3600 + (t.minute() as i32) * 60 + (t.second() as i32); return match unit { TimeUnit::Second => Some(secs), TimeUnit::Millisecond => { let ms = (t.nanosecond() / 1_000_000) as i32; Some(secs * 1_000 + ms) } _ => None, }; } let _ = (v, unit); None } fn extract_time64_option(v: &Option, unit: &arrow::datatypes::TimeUnit) -> Option { extract_time64(v.as_ref()?, unit) } fn extract_time64(v: &Value, unit: &arrow::datatypes::TimeUnit) -> Option { #[cfg(any(feature = "with-chrono", feature = "with-time"))] use arrow::datatypes::TimeUnit; #[cfg(feature = "with-chrono")] if let Value::ChronoTime(Some(t)) = v { use sea_query::prelude::chrono::Timelike; let secs = t.num_seconds_from_midnight() as i64; let nanos = (t.nanosecond() % 1_000_000_000) as i64; return match unit { TimeUnit::Microsecond => Some(secs * 1_000_000 + nanos / 1_000), TimeUnit::Nanosecond => Some(secs * 1_000_000_000 + nanos), _ => None, }; } #[cfg(feature = "with-time")] if let Value::TimeTime(Some(t)) = v { let secs = (t.hour() as i64) * 3600 + (t.minute() as i64) * 60 + (t.second() as i64); let nanos = t.nanosecond() as i64; return match unit { TimeUnit::Microsecond => Some(secs * 1_000_000 + nanos / 1_000), TimeUnit::Nanosecond => Some(secs * 1_000_000_000 + nanos), _ => None, }; } let _ = (v, unit); None } // --------------------------------------------------------------------------- // Timestamp extraction helpers // --------------------------------------------------------------------------- fn extract_timestamp_option(v: &Option, unit: &arrow::datatypes::TimeUnit) -> Option { extract_timestamp(v.as_ref()?, unit) } fn extract_timestamp(v: &Value, unit: &arrow::datatypes::TimeUnit) -> Option { #[cfg(any(feature = "with-chrono", feature = "with-time"))] use arrow::datatypes::TimeUnit; #[cfg(feature = "with-chrono")] { if let Value::ChronoDateTime(Some(dt)) = v { let utc = dt.and_utc(); return Some(match unit { TimeUnit::Second => utc.timestamp(), TimeUnit::Millisecond => utc.timestamp_millis(), TimeUnit::Microsecond => utc.timestamp_micros(), TimeUnit::Nanosecond => utc.timestamp_nanos_opt().unwrap_or(0), }); } if let Value::ChronoDateTimeUtc(Some(dt)) = v { return Some(match unit { TimeUnit::Second => dt.timestamp(), TimeUnit::Millisecond => dt.timestamp_millis(), TimeUnit::Microsecond => dt.timestamp_micros(), TimeUnit::Nanosecond => dt.timestamp_nanos_opt().unwrap_or(0), }); } } #[cfg(feature = "with-time")] { if let Value::TimeDateTime(Some(dt)) = v { let odt = dt.assume_utc(); return Some(offset_dt_to_timestamp(&odt, unit)); } if let Value::TimeDateTimeWithTimeZone(Some(dt)) = v { return Some(offset_dt_to_timestamp(dt, unit)); } } let _ = (v, unit); None } #[cfg(feature = "with-time")] fn offset_dt_to_timestamp( dt: &sea_query::prelude::time::OffsetDateTime, unit: &arrow::datatypes::TimeUnit, ) -> i64 { use arrow::datatypes::TimeUnit; match unit { TimeUnit::Second => dt.unix_timestamp(), TimeUnit::Millisecond => (dt.unix_timestamp_nanos() / 1_000_000) as i64, TimeUnit::Microsecond => (dt.unix_timestamp_nanos() / 1_000) as i64, TimeUnit::Nanosecond => dt.unix_timestamp_nanos() as i64, } } // --------------------------------------------------------------------------- // Decimal extraction helpers // --------------------------------------------------------------------------- fn extract_decimal64_option(v: &Option, target_scale: i8) -> Option { extract_decimal64(v.as_ref()?, target_scale) } fn extract_decimal64(v: &Value, target_scale: i8) -> Option { #[cfg(feature = "with-rust_decimal")] if let Value::Decimal(Some(d)) = v { let mantissa = d.mantissa(); let current_scale = d.scale() as i8; let scale_diff = target_scale - current_scale; let scaled = if scale_diff >= 0 { mantissa * 10i128.pow(scale_diff as u32) } else { mantissa / 10i128.pow((-scale_diff) as u32) }; return i64::try_from(scaled).ok(); } #[cfg(feature = "with-bigdecimal")] if let Value::BigDecimal(Some(d)) = v { return bigdecimal_to_i64(d, target_scale); } let _ = (v, target_scale); None } #[cfg(feature = "with-bigdecimal")] fn bigdecimal_to_i64( d: &sea_query::prelude::bigdecimal::BigDecimal, target_scale: i8, ) -> Option { use sea_query::prelude::bigdecimal::ToPrimitive; let rescaled = d.clone().with_scale(target_scale as i64); let (digits, _) = rescaled.into_bigint_and_exponent(); digits.to_i64() } fn extract_decimal128_option(v: &Option, target_scale: i8) -> Option { extract_decimal128(v.as_ref()?, target_scale) } fn extract_decimal128(v: &Value, target_scale: i8) -> Option { #[cfg(feature = "with-rust_decimal")] if let Value::Decimal(Some(d)) = v { let mantissa = d.mantissa(); let current_scale = d.scale() as i8; let scale_diff = target_scale - current_scale; return if scale_diff >= 0 { Some(mantissa * 10i128.pow(scale_diff as u32)) } else { Some(mantissa / 10i128.pow((-scale_diff) as u32)) }; } #[cfg(feature = "with-bigdecimal")] if let Value::BigDecimal(Some(d)) = v { return bigdecimal_to_i128(d, target_scale); } let _ = (v, target_scale); None } #[cfg(feature = "with-bigdecimal")] fn bigdecimal_to_i128( d: &sea_query::prelude::bigdecimal::BigDecimal, target_scale: i8, ) -> Option { use sea_query::prelude::bigdecimal::ToPrimitive; let rescaled = d.clone().with_scale(target_scale as i64); let (digits, _) = rescaled.into_bigint_and_exponent(); digits.to_i128() } fn extract_decimal256_option(v: &Option, target_scale: i8) -> Option { extract_decimal256(v.as_ref()?, target_scale) } fn extract_decimal256(v: &Value, target_scale: i8) -> Option { #[cfg(feature = "with-bigdecimal")] if let Value::BigDecimal(Some(d)) = v { return bigdecimal_to_i256(d, target_scale); } #[cfg(feature = "with-rust_decimal")] if let Value::Decimal(Some(d)) = v { let mantissa = d.mantissa(); let current_scale = d.scale() as i8; let scale_diff = target_scale - current_scale; let scaled = if scale_diff >= 0 { mantissa * 10i128.pow(scale_diff as u32) } else { mantissa / 10i128.pow((-scale_diff) as u32) }; return Some(i256::from_i128(scaled)); } let _ = (v, target_scale); None } #[cfg(feature = "with-bigdecimal")] fn bigdecimal_to_i256( d: &sea_query::prelude::bigdecimal::BigDecimal, target_scale: i8, ) -> Option { let rescaled = d.clone().with_scale(target_scale as i64); let (digits, _) = rescaled.into_bigint_and_exponent(); bigint_to_i256(&digits) } #[cfg(feature = "with-bigdecimal")] fn bigint_to_i256(bi: &sea_query::prelude::bigdecimal::num_bigint::BigInt) -> Option { use sea_query::prelude::bigdecimal::num_bigint::Sign; let (sign, bytes) = bi.to_bytes_be(); if bytes.len() > 32 { return None; } let mut buf = [0u8; 32]; let start = 32 - bytes.len(); buf[start..].copy_from_slice(&bytes); let val = i256::from_be_bytes(buf); match sign { Sign::Minus => Some(val.wrapping_neg()), _ => Some(val), } } ================================================ FILE: sea-orm-cli/Cargo.toml ================================================ [workspace] # A separate workspace [package] authors = [ "Chris Tsang ", "Billy Chan ", ] categories = ["database"] default-run = "sea-orm-cli" description = "Command line utility for SeaORM" documentation = "https://docs.rs/sea-orm" edition = "2024" homepage = "https://www.sea-ql.org/SeaORM" keywords = ["async", "orm", "mysql", "postgres", "sqlite"] license = "MIT OR Apache-2.0" name = "sea-orm-cli" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "2.0.0-rc.37" [lib] name = "sea_orm_cli" path = "src/lib.rs" [[bin]] name = "sea-orm-cli" path = "src/bin/main.rs" required-features = ["cli", "codegen"] [[bin]] name = "sea" path = "src/bin/main.rs" required-features = ["cli", "codegen"] [dependencies] async-std = { version = "1.9", default-features = false, features = [ "attributes", "tokio1", ], optional = true } chrono = { version = "0.4.20", default-features = false, features = [ "clock", ] } clap = { version = "4.3", features = ["env", "derive"], optional = true } dotenvy = { version = "0.15", default-features = false, optional = true } glob = { version = "0.3", default-features = false } indoc = "2.0.6" regex = { version = "1.11.2" } sea-orm-codegen = { version = "=2.0.0-rc.37", path = "../sea-orm-codegen", default-features = false, optional = true } sea-schema = { version = "0.17.0-rc.1", default-features = false, features = [ "discovery", "writer", "probe", ], optional = true } sqlx = { version = "0.8.4", default-features = false, optional = true } tokio = { version = "1.38.2", default-features = false, features = [ "rt-multi-thread", "macros", ], optional = true } tracing = { version = "0.1", default-features = false } tracing-subscriber = { version = "0.3.17", default-features = false, features = [ "env-filter", "fmt", ] } url = { version = "2.2", default-features = false } [dev-dependencies] smol = "1.2.5" [features] cli = ["clap", "dotenvy"] codegen = ["cli", "sqlx", "sea-schema", "sea-orm-codegen"] default = [ "codegen", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "runtime-tokio-native-tls", ] postgres-vector = ["sea-schema/postgres-vector"] sqlx-mysql = [ "sqlx?/sqlx-mysql", "sea-schema?/sqlx-mysql", "sea-schema?/mysql", ] sqlx-postgres = [ "sqlx?/sqlx-postgres", "sea-schema?/sqlx-postgres", "sea-schema?/postgres", ] sqlx-sqlite = [ "sqlx?/sqlx-sqlite", "sea-schema?/sqlx-sqlite", "sea-schema?/sqlite", ] runtime-actix = ["runtime-tokio"] runtime-actix-native-tls = ["runtime-tokio-native-tls"] runtime-actix-rustls = ["runtime-tokio-rustls"] runtime-async-std = [ "async-std", "sqlx?/runtime-async-std", "sea-schema?/runtime-async-std", ] runtime-async-std-native-tls = [ "async-std", "sqlx?/runtime-async-std-native-tls", "sea-schema?/runtime-async-std-native-tls", ] runtime-async-std-rustls = [ "async-std", "sqlx?/runtime-async-std-rustls", "sea-schema?/runtime-async-std-rustls", ] runtime-tokio = ["tokio", "sqlx?/runtime-tokio", "sea-schema?/runtime-tokio"] runtime-tokio-native-tls = [ "tokio", "sqlx?/runtime-tokio-native-tls", "sea-schema?/runtime-tokio-native-tls", ] runtime-tokio-rustls = [ "tokio", "sqlx?/runtime-tokio-rustls", "sea-schema?/runtime-tokio-rustls", ] # This allows us to develop using an overridden version of sea-query [patch.crates-io] # sea-query = { path = "../sea-query" } # sea-query = { git = "https://github.com/SeaQL/sea-query", branch = "master" } ================================================ FILE: sea-orm-cli/README.md ================================================ # SeaORM CLI Install and Usage: ```sh > cargo install sea-orm-cli > sea-orm-cli help ``` Or: ```sh > cargo install --bin sea > sea help ``` Getting Help: ```sh cargo run -- -h ``` ## Running Entity Generator: ```sh # MySQL (`--database-schema` option is ignored) cargo run -- generate entity -u mysql://sea:sea@localhost/bakery -o out # SQLite (`--database-schema` option is ignored) cargo run -- generate entity -u sqlite://bakery.db -o out # PostgreSQL cargo run -- generate entity -u postgres://sea:sea@localhost/bakery -s public -o out ``` ## Running Migration: - Initialize migration directory ```sh cargo run -- migrate init ``` - Generate a new migration file ```sh cargo run -- migrate generate MIGRATION_NAME ``` - Apply all pending migrations ```sh cargo run -- migrate ``` ```sh cargo run -- migrate up ``` - Apply first 10 pending migrations ```sh cargo run -- migrate up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- migrate down ``` - Rollback last 10 applied migrations ```sh cargo run -- migrate down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- migrate fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- migrate refresh ``` - Rollback all applied migrations ```sh cargo run -- migrate reset ``` - Check the status of all migrations ```sh cargo run -- migrate status ``` ================================================ FILE: sea-orm-cli/src/bin/main.rs ================================================ #[cfg_attr( any( feature = "runtime-async-std", feature = "runtime-async-std-native-tls", feature = "runtime-async-std-rustls" ), async_std::main )] #[cfg_attr( any( feature = "runtime-tokio", feature = "runtime-tokio-native-tls", feature = "runtime-tokio-rustls", ), tokio::main )] async fn main() { sea_orm_cli::main().await } ================================================ FILE: sea-orm-cli/src/cli.rs ================================================ use clap::{ArgAction, ArgGroup, Parser, Subcommand, ValueEnum}; #[cfg(feature = "codegen")] use dotenvy::dotenv; use std::ffi::OsStr; #[cfg(feature = "codegen")] use crate::{handle_error, run_generate_command, run_migrate_command}; #[derive(Parser, Debug)] #[command( version, author, help_template = r#"{before-help}{name} {version} {about-with-newline} {usage-heading} {usage} {all-args}{after-help} "#, about = r#" ____ ___ ____ __ __ /\ / ___| ___ __ _ / _ \ | _ \ | \/ | {.-} \___ \ / _ \ / _` || | | || |_) || |\/| | ;_.-'\ ___) || __/| (_| || |_| || _ < | | | | { _.}_ |____/ \___| \__,_| \___/ |_| \_\|_| |_| \.-' / `, \ | / An async & dynamic ORM for Rust \ | ,/ =============================== \|_/ Getting Started - Documentation: https://www.sea-ql.org/SeaORM - Tutorial: https://www.sea-ql.org/sea-orm-tutorial - Examples: https://github.com/SeaQL/sea-orm/tree/master/examples - Cookbook: https://www.sea-ql.org/sea-orm-cookbook Join our Discord server to chat with others in the SeaQL community! - Invitation: https://discord.com/invite/uCPdDXzbdv SeaQL Community Survey 2025 - Link: https://www.sea-ql.org/community-survey/ If you like what we do, consider starring, sharing and contributing! "# )] pub struct Cli { #[arg(global = true, short, long, help = "Show debug messages")] pub verbose: bool, #[command(subcommand)] pub command: Commands, } #[allow(clippy::large_enum_variant)] #[derive(Subcommand, PartialEq, Eq, Debug)] pub enum Commands { #[command( about = "Codegen related commands", arg_required_else_help = true, display_order = 10 )] Generate { #[command(subcommand)] command: GenerateSubcommands, }, #[command(about = "Migration related commands", display_order = 20)] Migrate { #[arg( global = true, short = 'd', long, env = "MIGRATION_DIR", help = "Migration script directory. If your migrations are in their own crate, you can provide the root of that crate. If your migrations are in a submodule of your app, you should provide the directory of that submodule.", default_value = "./migration" )] migration_dir: String, #[arg( global = true, short = 's', long, env = "DATABASE_SCHEMA", long_help = "Database schema\n \ - For MySQL and SQLite, this argument is ignored.\n \ - For PostgreSQL, this argument is optional with default value 'public'.\n" )] database_schema: Option, #[arg( global = true, short = 'u', long, env = "DATABASE_URL", help = "Database URL", hide_env_values = true )] database_url: Option, #[command(subcommand)] command: Option, }, } #[derive(Subcommand, PartialEq, Eq, Debug)] pub enum MigrateSubcommands { #[command(about = "Initialize migration directory", display_order = 10)] Init, #[command(about = "Generate a new, empty migration", display_order = 20)] Generate { #[arg(required = true, help = "Name of the new migration")] migration_name: String, #[arg( long, default_value = "true", help = "Generate migration file based on Utc time", conflicts_with = "local_time", display_order = 1001 )] universal_time: bool, #[arg( long, help = "Generate migration file based on Local time", conflicts_with = "universal_time", display_order = 1002 )] local_time: bool, }, #[command( about = "Drop all tables from the database, then reapply all migrations", display_order = 30 )] Fresh, #[command( about = "Rollback all applied migrations, then reapply all migrations", display_order = 40 )] Refresh, #[command(about = "Rollback all applied migrations", display_order = 50)] Reset, #[command(about = "Check the status of all migrations", display_order = 60)] Status, #[command(about = "Apply pending migrations", display_order = 70)] Up { #[arg(short, long, help = "Number of pending migrations to apply")] num: Option, }, #[command(about = "Rollback applied migrations", display_order = 80)] Down { #[arg( short, long, default_value = "1", help = "Number of applied migrations to be rolled back", display_order = 90 )] num: u32, }, } #[derive(Subcommand, PartialEq, Eq, Debug)] pub enum GenerateSubcommands { #[command(about = "Generate entity")] #[command(group(ArgGroup::new("formats").args(&["compact_format", "expanded_format", "frontend_format"])))] #[command(group(ArgGroup::new("group-tables").args(&["tables", "include_hidden_tables"])))] Entity { #[arg(long, help = "Which format to generate entity files in")] entity_format: Option, #[arg(long, help = "Generate entity file of compact format")] compact_format: bool, #[arg(long, help = "Generate entity file of expanded format")] expanded_format: bool, #[arg(long, help = "Generate entity file of frontend format")] frontend_format: bool, #[arg( long, help = "Generate entity file for hidden tables (i.e. table name starts with an underscore)" )] include_hidden_tables: bool, #[arg( short = 't', long, value_delimiter = ',', help = "Generate entity file for specified tables only (comma separated)" )] tables: Vec, #[arg( long, value_delimiter = ',', default_value = "seaql_migrations", help = "Skip generating entity file for specified tables (comma separated)" )] ignore_tables: Vec, #[arg( long, default_value = "1", help = "The maximum amount of connections to use when connecting to the database." )] max_connections: u32, #[arg( long, default_value = "30", long_help = "Acquire timeout in seconds of the connection used for schema discovery" )] acquire_timeout: u64, #[arg( short = 'o', long, default_value = "./", help = "Entity file output directory" )] output_dir: String, #[arg( short = 's', long, env = "DATABASE_SCHEMA", long_help = "Database schema\n \ - For MySQL, this argument is ignored.\n \ - For PostgreSQL, this argument is optional with default value 'public'." )] database_schema: Option, #[arg( short = 'u', long, env = "DATABASE_URL", help = "Database URL", hide_env_values = true )] database_url: String, #[arg( long, default_value = "all", help = "Generate prelude.rs file (all, none, all-allow-unused-imports)" )] with_prelude: String, #[arg( long, default_value = "none", help = "Automatically derive serde Serialize / Deserialize traits for the entity (none, \ serialize, deserialize, both)" )] with_serde: String, #[arg( long, help = "Generate a serde field attribute, '#[serde(skip_deserializing)]', for the primary key fields to skip them during deserialization, this flag will be affective only when '--with-serde' is 'both' or 'deserialize'" )] serde_skip_deserializing_primary_key: bool, #[arg( long, default_value = "false", help = "Opt-in to add skip attributes to hidden columns (i.e. when 'with-serde' enabled and column name starts with an underscore)" )] serde_skip_hidden_column: bool, #[arg( long, default_value = "false", long_help = "Automatically derive the Copy trait on generated enums.\n\ Enums generated from a database don't have associated data by default, and as such can \ derive Copy. " )] with_copy_enums: bool, #[arg( long, default_value_t, value_enum, help = "The datetime crate to use for generating entities." )] date_time_crate: DateTimeCrate, #[arg( long, default_value_t, value_enum, help = "The primitive type to use for big integer." )] big_integer_type: BigIntegerType, #[arg( long, short = 'l', default_value = "false", help = "Generate index file as `lib.rs` instead of `mod.rs`." )] lib: bool, #[arg( long, value_delimiter = ',', help = "Add extra derive macros to generated model struct (comma separated), e.g. `--model-extra-derives 'ts_rs::Ts','CustomDerive'`" )] model_extra_derives: Vec, #[arg( long, value_delimiter = ',', help = r#"Add extra attributes to generated model struct, no need for `#[]` (comma separated), e.g. `--model-extra-attributes 'serde(rename_all = "camelCase")','ts(export)'`"# )] model_extra_attributes: Vec, #[arg( long, value_delimiter = ',', help = "Add extra derive macros to generated enums (comma separated), e.g. `--enum-extra-derives 'ts_rs::Ts','CustomDerive'`" )] enum_extra_derives: Vec, #[arg( long, value_delimiter = ',', help = r#"Add extra attributes to generated enums, no need for `#[]` (comma separated), e.g. `--enum-extra-attributes 'serde(rename_all = "camelCase")','ts(export)'`"# )] enum_extra_attributes: Vec, #[arg( long, value_delimiter = ',', help = "Add extra derive macros to generated column enum (comma separated), e.g. `--column-extra-derives 'async_graphql::Enum','CustomDerive'`" )] column_extra_derives: Vec, #[arg( long, default_value = "false", long_help = "Generate helper Enumerations that are used by Seaography." )] seaography: bool, #[arg( long, default_value = "true", default_missing_value = "true", num_args = 0..=1, require_equals = true, action = ArgAction::Set, long_help = "Generate empty ActiveModelBehavior impls." )] impl_active_model_behavior: bool, #[arg( long = "experimental-preserve-user-modifications", alias = "preserve-user-modifications", default_value = "false", default_missing_value = "true", num_args = 0..=1, require_equals = true, action = ArgAction::Set, long_help = indoc::indoc! { " Experimental!: Preserve user modifications when regenerating entity files. Only supports: - Extra derives and attributes of `Model` and `Relation` - Impl blocks of `ActiveModelBehavior` Deprecated alias: `--preserve-user-modifications`" } )] preserve_user_modifications: bool, #[arg( long, default_value_t, value_enum, help = "Control how the codegen version is displayed in the top banner of the generated file." )] banner_version: BannerVersion, #[arg( long, default_value = "false", help = "Also generate a Mermaid ER diagram as `entities.mermaid` in the output directory" )] er_diagram: bool, }, } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, Default)] pub enum DateTimeCrate { #[default] Chrono, Time, } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, Default)] pub enum BigIntegerType { #[default] I64, I32, } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, Default)] pub enum BannerVersion { Off, Major, #[default] Minor, Patch, } fn is_deprecated_preserve_user_modifications_flag(arg: &OsStr) -> bool { arg.to_str() .is_some_and(|arg| arg.starts_with("--preserve-user-modifications")) } /// Use this to build a local, version-controlled `sea-orm-cli` in dependent projects /// (see [example use case](https://github.com/SeaQL/sea-orm/discussions/1889)). #[cfg(feature = "codegen")] pub async fn main() { dotenv().ok(); let deprecated_preserve_user_modifications_flag_used = std::env::args_os() .skip(1) .any(|arg| is_deprecated_preserve_user_modifications_flag(&arg)); let cli = Cli::parse(); if deprecated_preserve_user_modifications_flag_used { eprintln!( "warning: `--preserve-user-modifications` is deprecated; use `--experimental-preserve-user-modifications` instead." ); } let verbose = cli.verbose; match cli.command { Commands::Generate { command } => { run_generate_command(command, verbose) .await .unwrap_or_else(handle_error); } Commands::Migrate { migration_dir, database_schema, database_url, command, } => run_migrate_command( command, &migration_dir, database_schema, database_url, verbose, ) .unwrap_or_else(handle_error), } } ================================================ FILE: sea-orm-cli/src/commands/generate.rs ================================================ use crate::{BannerVersion, BigIntegerType, DateTimeCrate, GenerateSubcommands}; use core::time; use sea_orm_codegen::{ BannerVersion as CodegenBannerVersion, BigIntegerType as CodegenBigIntegerType, DateTimeCrate as CodegenDateTimeCrate, EntityFormat, EntityTransformer, EntityWriterContext, MergeReport, OutputFile, WithPrelude, WithSerde, merge_entity_files, }; use std::{error::Error, fs, path::Path, process::Command, str::FromStr}; use tracing_subscriber::{EnvFilter, prelude::*}; use url::Url; pub async fn run_generate_command( command: GenerateSubcommands, verbose: bool, ) -> Result<(), Box> { match command { GenerateSubcommands::Entity { entity_format, compact_format: _, expanded_format, frontend_format, include_hidden_tables, tables, ignore_tables, max_connections, acquire_timeout, output_dir, database_schema, database_url, with_prelude, with_serde, serde_skip_deserializing_primary_key, serde_skip_hidden_column, with_copy_enums, date_time_crate, big_integer_type, lib, model_extra_derives, model_extra_attributes, enum_extra_derives, enum_extra_attributes, column_extra_derives, seaography, impl_active_model_behavior, preserve_user_modifications, banner_version, er_diagram, } => { if verbose { let _ = tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .with_test_writer() .try_init(); } else { let filter_layer = EnvFilter::try_new("sea_orm_codegen=info").unwrap(); let fmt_layer = tracing_subscriber::fmt::layer() .with_target(false) .with_level(false) .without_time(); let _ = tracing_subscriber::registry() .with(filter_layer) .with(fmt_layer) .try_init(); } // The database should be a valid URL that can be parsed // protocol://username:password@host/database_name let url = Url::parse(&database_url)?; // Make sure we have all the required url components // // Missing scheme will have been caught by the Url::parse() call // above let is_sqlite = url.scheme() == "sqlite"; // Closures for filtering tables let filter_tables = |table: &String| -> bool { tables.is_empty() || tables.contains(table) }; let filter_hidden_tables = |table: &str| -> bool { if include_hidden_tables { true } else { !table.starts_with('_') } }; let filter_skip_tables = |table: &String| -> bool { !ignore_tables.contains(table) }; let _database_name = if !is_sqlite { // The database name should be the first element of the path string // // Throwing an error if there is no database name since it might be // accepted by the database without it, while we're looking to dump // information from a particular database let database_name = url .path_segments() .unwrap_or_else(|| { panic!( "There is no database name as part of the url path: {}", url.as_str() ) }) .next() .unwrap(); // An empty string as the database name is also an error if database_name.is_empty() { panic!( "There is no database name as part of the url path: {}", url.as_str() ); } database_name } else { Default::default() }; let (schema_name, table_stmts) = match url.scheme() { "mysql" => { #[cfg(not(feature = "sqlx-mysql"))] { panic!("mysql feature is off") } #[cfg(feature = "sqlx-mysql")] { use sea_schema::mysql::discovery::SchemaDiscovery; use sqlx::MySql; println!("Connecting to MySQL ..."); let connection = sqlx_connect::( max_connections, acquire_timeout, url.as_str(), None, ) .await?; println!("Discovering schema ..."); let schema_discovery = SchemaDiscovery::new(connection, _database_name); let schema = schema_discovery.discover().await?; let table_stmts = schema .tables .into_iter() .filter(|schema| filter_tables(&schema.info.name)) .filter(|schema| filter_hidden_tables(&schema.info.name)) .filter(|schema| filter_skip_tables(&schema.info.name)) .map(|schema| schema.write()) .collect(); (None, table_stmts) } } "sqlite" => { #[cfg(not(feature = "sqlx-sqlite"))] { panic!("sqlite feature is off") } #[cfg(feature = "sqlx-sqlite")] { use sea_schema::sqlite::discovery::SchemaDiscovery; use sqlx::Sqlite; println!("Connecting to SQLite ..."); let connection = sqlx_connect::( max_connections, acquire_timeout, url.as_str(), None, ) .await?; println!("Discovering schema ..."); let schema_discovery = SchemaDiscovery::new(connection); let schema = schema_discovery .discover() .await? .merge_indexes_into_table(); let table_stmts = schema .tables .into_iter() .filter(|schema| filter_tables(&schema.name)) .filter(|schema| filter_hidden_tables(&schema.name)) .filter(|schema| filter_skip_tables(&schema.name)) .map(|schema| schema.write()) .collect(); (None, table_stmts) } } "postgres" | "postgresql" => { #[cfg(not(feature = "sqlx-postgres"))] { panic!("postgres feature is off") } #[cfg(feature = "sqlx-postgres")] { use sea_schema::postgres::discovery::SchemaDiscovery; use sqlx::Postgres; println!("Connecting to Postgres ..."); let schema = database_schema.as_deref().unwrap_or("public"); let connection = sqlx_connect::( max_connections, acquire_timeout, url.as_str(), Some(schema), ) .await?; println!("Discovering schema ..."); let schema_discovery = SchemaDiscovery::new(connection, schema); let schema = schema_discovery.discover().await?; let table_stmts = schema .tables .into_iter() .filter(|schema| filter_tables(&schema.info.name)) .filter(|schema| filter_hidden_tables(&schema.info.name)) .filter(|schema| filter_skip_tables(&schema.info.name)) .map(|schema| schema.write()) .collect(); (database_schema, table_stmts) } } _ => unimplemented!("{} is not supported", url.scheme()), }; println!("... discovered."); let writer_context = EntityWriterContext::new( if expanded_format { EntityFormat::Expanded } else if frontend_format { EntityFormat::Frontend } else if let Some(entity_format) = entity_format { EntityFormat::from_str(&entity_format).expect("Invalid entity-format option") } else { EntityFormat::default() }, WithPrelude::from_str(&with_prelude).expect("Invalid prelude option"), WithSerde::from_str(&with_serde).expect("Invalid serde derive option"), with_copy_enums, date_time_crate.into(), big_integer_type.into(), schema_name, lib, serde_skip_deserializing_primary_key, serde_skip_hidden_column, model_extra_derives, model_extra_attributes, enum_extra_derives, enum_extra_attributes, column_extra_derives, seaography, impl_active_model_behavior, banner_version.into(), ); let entity_writer = EntityTransformer::transform(table_stmts)?; let dir = Path::new(&output_dir); fs::create_dir_all(dir)?; if er_diagram { let diagram = entity_writer.generate_er_diagram(); let diagram_path = dir.join("entities.mermaid"); fs::write(&diagram_path, &diagram)?; println!("Writing {}", diagram_path.display()); } let output = entity_writer.generate(&writer_context); let mut merge_fallback_files: Vec = Vec::new(); for OutputFile { name, content } in output.files.iter() { let file_path = dir.join(name); println!("Writing {}", file_path.display()); if !matches!( name.as_str(), "mod.rs" | "lib.rs" | "prelude.rs" | "sea_orm_active_enums.rs" ) && file_path.exists() && preserve_user_modifications { let prev_content = fs::read_to_string(&file_path)?; match merge_entity_files(&prev_content, content) { Ok(merged) => { fs::write(file_path, merged)?; } Err(MergeReport { output, warnings, fallback_applied, }) => { for message in warnings { eprintln!("{message}"); } fs::write(file_path, output)?; if fallback_applied { merge_fallback_files.push(name.clone()); } } } } else { fs::write(file_path, content)?; }; } // Format each of the files for OutputFile { name, .. } in output.files.iter() { let exit_status = Command::new("rustfmt").arg(dir.join(name)).status()?; // Get the status code if !exit_status.success() { // Propagate the error if any return Err(format!("Fail to format file `{name}`").into()); } } if merge_fallback_files.is_empty() { println!("... Done."); } else { return Err(format!( "Merge fallback applied for {} file(s): \n{}", merge_fallback_files.len(), merge_fallback_files.join("\n") ) .into()); } } } Ok(()) } async fn sqlx_connect( max_connections: u32, acquire_timeout: u64, url: &str, schema: Option<&str>, ) -> Result, Box> where DB: sqlx::Database, for<'a> &'a mut ::Connection: sqlx::Executor<'a>, { let mut pool_options = sqlx::pool::PoolOptions::::new() .max_connections(max_connections) .acquire_timeout(time::Duration::from_secs(acquire_timeout)); // Set search_path for Postgres, E.g. Some("public") by default // MySQL & SQLite connection initialize with schema `None` if let Some(schema) = schema { let sql = format!("SET search_path = '{schema}'"); pool_options = pool_options.after_connect(move |conn, _| { let sql = sql.clone(); Box::pin(async move { sqlx::Executor::execute(conn, sql.as_str()) .await .map(|_| ()) }) }); } pool_options.connect(url).await.map_err(Into::into) } impl From for CodegenDateTimeCrate { fn from(date_time_crate: DateTimeCrate) -> CodegenDateTimeCrate { match date_time_crate { DateTimeCrate::Chrono => CodegenDateTimeCrate::Chrono, DateTimeCrate::Time => CodegenDateTimeCrate::Time, } } } impl From for CodegenBigIntegerType { fn from(date_time_crate: BigIntegerType) -> CodegenBigIntegerType { match date_time_crate { BigIntegerType::I64 => CodegenBigIntegerType::I64, BigIntegerType::I32 => CodegenBigIntegerType::I32, } } } impl From for CodegenBannerVersion { fn from(banner_version: BannerVersion) -> CodegenBannerVersion { match banner_version { BannerVersion::Off => CodegenBannerVersion::Off, BannerVersion::Major => CodegenBannerVersion::Major, BannerVersion::Minor => CodegenBannerVersion::Minor, BannerVersion::Patch => CodegenBannerVersion::Patch, } } } #[cfg(test)] mod tests { use clap::Parser; use super::*; use crate::{Cli, Commands}; #[test] #[should_panic( expected = "called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase" )] fn test_generate_entity_no_protocol() { let cli = Cli::parse_from([ "sea-orm-cli", "generate", "entity", "--database-url", "://root:root@localhost:3306/database", ]); match cli.command { Commands::Generate { command } => { smol::block_on(run_generate_command(command, cli.verbose)).unwrap(); } _ => unreachable!(), } } #[test] #[should_panic( expected = "There is no database name as part of the url path: postgresql://root:root@localhost:3306" )] fn test_generate_entity_no_database_section() { let cli = Cli::parse_from([ "sea-orm-cli", "generate", "entity", "--database-url", "postgresql://root:root@localhost:3306", ]); match cli.command { Commands::Generate { command } => { smol::block_on(run_generate_command(command, cli.verbose)).unwrap(); } _ => unreachable!(), } } #[test] #[should_panic( expected = "There is no database name as part of the url path: mysql://root:root@localhost:3306/" )] fn test_generate_entity_no_database_path() { let cli = Cli::parse_from([ "sea-orm-cli", "generate", "entity", "--database-url", "mysql://root:root@localhost:3306/", ]); match cli.command { Commands::Generate { command } => { smol::block_on(run_generate_command(command, cli.verbose)).unwrap(); } _ => unreachable!(), } } #[test] #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: EmptyHost")] fn test_generate_entity_no_host() { let cli = Cli::parse_from([ "sea-orm-cli", "generate", "entity", "--database-url", "postgres://root:root@/database", ]); match cli.command { Commands::Generate { command } => { smol::block_on(run_generate_command(command, cli.verbose)).unwrap(); } _ => unreachable!(), } } } ================================================ FILE: sea-orm-cli/src/commands/migrate.rs ================================================ use chrono::{Local, Utc}; use regex::Regex; use std::{ error::Error, fmt::Display, fs, io::Write, path::{Path, PathBuf}, process::Command, }; #[cfg(feature = "cli")] use crate::MigrateSubcommands; #[cfg(feature = "cli")] pub fn run_migrate_command( command: Option, migration_dir: &str, database_schema: Option, database_url: Option, verbose: bool, ) -> Result<(), Box> { match command { Some(MigrateSubcommands::Init) => run_migrate_init(migration_dir)?, Some(MigrateSubcommands::Generate { migration_name, universal_time: _, local_time, }) => run_migrate_generate(migration_dir, &migration_name, !local_time)?, _ => { let (subcommand, migration_dir, steps, verbose) = match command { Some(MigrateSubcommands::Fresh) => ("fresh", migration_dir, None, verbose), Some(MigrateSubcommands::Refresh) => ("refresh", migration_dir, None, verbose), Some(MigrateSubcommands::Reset) => ("reset", migration_dir, None, verbose), Some(MigrateSubcommands::Status) => ("status", migration_dir, None, verbose), Some(MigrateSubcommands::Up { num }) => ("up", migration_dir, num, verbose), Some(MigrateSubcommands::Down { num }) => { ("down", migration_dir, Some(num), verbose) } _ => ("up", migration_dir, None, verbose), }; // Construct the `--manifest-path` let manifest_path = if migration_dir.ends_with('/') { format!("{migration_dir}Cargo.toml") } else { format!("{migration_dir}/Cargo.toml") }; // Construct the arguments that will be supplied to `cargo` command let mut args = vec!["run", "--manifest-path", &manifest_path, "--", subcommand]; let mut envs = vec![]; let mut num: String = "".to_string(); if let Some(steps) = steps { num = steps.to_string(); } if !num.is_empty() { args.extend(["-n", &num]) } if let Some(database_url) = &database_url { envs.push(("DATABASE_URL", database_url)); } if let Some(database_schema) = &database_schema { envs.push(("DATABASE_SCHEMA", database_schema)); } if verbose { args.push("-v"); } // Run migrator CLI on user's behalf println!("Running `cargo {}`", args.join(" ")); let exit_status = Command::new("cargo").args(args).envs(envs).status()?; // Get the status code if !exit_status.success() { // Propagate the error if any return Err("Fail to run migration".into()); } } } Ok(()) } pub fn run_migrate_init(migration_dir: &str) -> Result<(), Box> { let migration_dir = match migration_dir.ends_with('/') { true => migration_dir.to_string(), false => format!("{migration_dir}/"), }; println!("Initializing migration directory..."); macro_rules! write_file { ($filename: literal) => { let fn_content = |content: String| content; write_file!($filename, $filename, fn_content); }; ($filename: literal, $template: literal) => { let fn_content = |content: String| content; write_file!($filename, $template, fn_content); }; ($filename: literal, $template: literal, $fn_content: expr) => { let filepath = [&migration_dir, $filename].join(""); println!("Creating file `{}`", filepath); let path = Path::new(&filepath); let prefix = path.parent().unwrap(); fs::create_dir_all(prefix).unwrap(); let mut file = fs::File::create(path)?; let content = include_str!(concat!("../../template/migration/", $template)); let content = $fn_content(content.to_string()); file.write_all(content.as_bytes())?; }; } write_file!("src/lib.rs"); write_file!("src/m20220101_000001_create_table.rs"); write_file!("src/main.rs"); write_file!("Cargo.toml", "_Cargo.toml", |content: String| { let ver = format!( "{}.{}.0", env!("CARGO_PKG_VERSION_MAJOR"), env!("CARGO_PKG_VERSION_MINOR") ); content.replace("", &ver) }); write_file!("README.md"); if glob::glob(&format!("{migration_dir}**/.git"))?.count() > 0 { write_file!(".gitignore", "_gitignore"); } println!("Done!"); Ok(()) } pub fn run_migrate_generate( migration_dir: &str, migration_name: &str, universal_time: bool, ) -> Result<(), Box> { // Make sure the migration name doesn't contain any characters that // are invalid module names in Rust. if migration_name.contains('-') { return Err(Box::new(MigrationCommandError::InvalidName( "Hyphen `-` cannot be used in migration name".to_string(), ))); } println!("Generating new migration..."); // build new migration filename const FMT: &str = "%Y%m%d_%H%M%S"; let formatted_now = if universal_time { Utc::now().format(FMT) } else { Local::now().format(FMT) }; let migration_name = migration_name.trim().replace(' ', "_"); let migration_name = format!("m{formatted_now}_{migration_name}"); create_new_migration(&migration_name, migration_dir)?; update_migrator(&migration_name, migration_dir)?; Ok(()) } /// `get_full_migration_dir` looks for a `src` directory /// inside of `migration_dir` and appends that to the returned path if found. /// /// Otherwise, `migration_dir` can point directly to a directory containing the /// migrations. In that case, nothing is appended. /// /// This way, `src` doesn't need to be appended in the standard case where /// migrations are in their own crate. If the migrations are in a submodule /// of another crate, `migration_dir` can point directly to that module. fn get_full_migration_dir(migration_dir: &str) -> PathBuf { let without_src = Path::new(migration_dir).to_owned(); let with_src = without_src.join("src"); match () { _ if with_src.is_dir() => with_src, _ => without_src, } } fn create_new_migration(migration_name: &str, migration_dir: &str) -> Result<(), Box> { let migration_filepath = get_full_migration_dir(migration_dir).join(format!("{}.rs", &migration_name)); println!("Creating migration file `{}`", migration_filepath.display()); // TODO: make OS agnostic let migration_template = include_str!("../../template/migration/src/m20220101_000001_create_table.rs"); let mut migration_file = fs::File::create(migration_filepath)?; migration_file.write_all(migration_template.as_bytes())?; Ok(()) } /// `get_migrator_filepath` looks for a file `migration_dir/src/lib.rs` /// and returns that path if found. /// /// If `src` is not found, it will look directly in `migration_dir` for `lib.rs`. /// /// If `lib.rs` is not found, it will look for `mod.rs` instead, /// e.g. `migration_dir/mod.rs`. /// /// This way, `src` doesn't need to be appended in the standard case where /// migrations are in their own crate (with a file `lib.rs`). If the /// migrations are in a submodule of another crate (with a file `mod.rs`), /// `migration_dir` can point directly to that module. fn get_migrator_filepath(migration_dir: &str) -> PathBuf { let full_migration_dir = get_full_migration_dir(migration_dir); let with_lib = full_migration_dir.join("lib.rs"); match () { _ if with_lib.is_file() => with_lib, _ => full_migration_dir.join("mod.rs"), } } fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box> { let migrator_filepath = get_migrator_filepath(migration_dir); println!( "Adding migration `{}` to `{}`", migration_name, migrator_filepath.display() ); let migrator_content = fs::read_to_string(&migrator_filepath)?; let mut updated_migrator_content = migrator_content.clone(); // create a backup of the migrator file in case something goes wrong let migrator_backup_filepath = migrator_filepath.with_extension("rs.bak"); fs::copy(&migrator_filepath, &migrator_backup_filepath)?; let mut migrator_file = fs::File::create(&migrator_filepath)?; // find existing mod declarations, add new line let mod_regex = Regex::new(r"mod\s+(?Pm\d{8}_\d{6}_\w+);")?; let mods: Vec<_> = mod_regex.captures_iter(&migrator_content).collect(); let mods_end = if let Some(last_match) = mods.last() { last_match.get(0).unwrap().end() + 1 } else { migrator_content.len() }; updated_migrator_content.insert_str(mods_end, format!("mod {migration_name};\n").as_str()); // build new vector from declared migration modules let mut migrations: Vec<&str> = mods .iter() .map(|cap| cap.name("name").unwrap().as_str()) .collect(); migrations.push(migration_name); let mut boxed_migrations = migrations .iter() .map(|migration| format!(" Box::new({migration}::Migration),")) .collect::>() .join("\n"); boxed_migrations.push('\n'); let boxed_migrations = format!("vec![\n{boxed_migrations} ]\n"); let vec_regex = Regex::new(r"vec!\[[\s\S]+\]\n")?; let updated_migrator_content = vec_regex.replace(&updated_migrator_content, &boxed_migrations); migrator_file.write_all(updated_migrator_content.as_bytes())?; fs::remove_file(&migrator_backup_filepath)?; Ok(()) } #[derive(Debug)] enum MigrationCommandError { InvalidName(String), } impl Display for MigrationCommandError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { MigrationCommandError::InvalidName(name) => { write!(f, "Invalid migration name: {name}") } } } } impl Error for MigrationCommandError {} #[cfg(test)] mod tests { use super::*; #[test] fn test_create_new_migration() { let migration_name = "test_name"; let migration_dir = "/tmp/sea_orm_cli_test_new_migration/"; fs::create_dir_all(format!("{migration_dir}src")).unwrap(); create_new_migration(migration_name, migration_dir).unwrap(); let migration_filepath = Path::new(migration_dir) .join("src") .join(format!("{migration_name}.rs")); assert!(migration_filepath.exists()); let migration_content = fs::read_to_string(migration_filepath).unwrap(); assert_eq!( &migration_content, include_str!("../../template/migration/src/m20220101_000001_create_table.rs") ); fs::remove_dir_all("/tmp/sea_orm_cli_test_new_migration/").unwrap(); } #[test] fn test_update_migrator() { let migration_name = "test_name"; let migration_dir = "/tmp/sea_orm_cli_test_update_migrator/"; fs::create_dir_all(format!("{migration_dir}src")).unwrap(); let migrator_filepath = Path::new(migration_dir).join("src").join("lib.rs"); fs::copy("./template/migration/src/lib.rs", &migrator_filepath).unwrap(); update_migrator(migration_name, migration_dir).unwrap(); assert!(&migrator_filepath.exists()); let migrator_content = fs::read_to_string(&migrator_filepath).unwrap(); let mod_regex = Regex::new(r"mod (?P\w+);").unwrap(); let migrations: Vec<&str> = mod_regex .captures_iter(&migrator_content) .map(|cap| cap.name("name").unwrap().as_str()) .collect(); assert_eq!(migrations.len(), 2); assert_eq!( *migrations.first().unwrap(), "m20220101_000001_create_table" ); assert_eq!(migrations.last().unwrap(), &migration_name); let boxed_regex = Regex::new(r"Box::new\((?P\S+)::Migration\)").unwrap(); let migrations: Vec<&str> = boxed_regex .captures_iter(&migrator_content) .map(|cap| cap.name("name").unwrap().as_str()) .collect(); assert_eq!(migrations.len(), 2); assert_eq!( *migrations.first().unwrap(), "m20220101_000001_create_table" ); assert_eq!(migrations.last().unwrap(), &migration_name); fs::remove_dir_all("/tmp/sea_orm_cli_test_update_migrator/").unwrap(); } } ================================================ FILE: sea-orm-cli/src/commands/mod.rs ================================================ use std::fmt::Display; #[cfg(feature = "codegen")] pub mod generate; pub mod migrate; #[cfg(feature = "codegen")] pub use generate::*; pub use migrate::*; pub fn handle_error(error: E) where E: Display, { eprintln!("{error}"); ::std::process::exit(1); } ================================================ FILE: sea-orm-cli/src/lib.rs ================================================ #[cfg(feature = "cli")] pub mod cli; pub mod commands; #[cfg(feature = "cli")] pub use cli::*; pub use commands::*; ================================================ FILE: sea-orm-cli/template/migration/README.md ================================================ # Running Migrator CLI - Generate a new migration file ```sh cargo run -- generate MIGRATION_NAME ``` - Apply all pending migrations ```sh cargo run ``` ```sh cargo run -- up ``` - Apply first 10 pending migrations ```sh cargo run -- up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- down ``` - Rollback last 10 applied migrations ```sh cargo run -- down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- refresh ``` - Rollback all applied migrations ```sh cargo run -- reset ``` - Check the status of all migrations ```sh cargo run -- status ``` ================================================ FILE: sea-orm-cli/template/migration/_Cargo.toml ================================================ [package] edition = "2024" name = "migration" publish = false rust-version = "1.85.0" version = "0.1.0" [lib] name = "migration" path = "src/lib.rs" [dependencies] tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } [dependencies.sea-orm-migration] features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. # e.g. # "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature # "sqlx-postgres", # `DATABASE_DRIVER` feature ] version = "" ================================================ FILE: sea-orm-cli/template/migration/_gitignore ================================================ /target ================================================ FILE: sea-orm-cli/template/migration/src/lib.rs ================================================ pub use sea_orm_migration::prelude::*; mod m20220101_000001_create_table; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![Box::new(m20220101_000001_create_table::Migration)] } } ================================================ FILE: sea-orm-cli/template/migration/src/m20220101_000001_create_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // Replace the sample below with your own migration scripts todo!(); manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { // Replace the sample below with your own migration scripts todo!(); manager .drop_table(Table::drop().table("post").to_owned()) .await } } ================================================ FILE: sea-orm-cli/template/migration/src/main.rs ================================================ use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; } ================================================ FILE: sea-orm-codegen/Cargo.toml ================================================ [package] authors = ["Billy Chan "] categories = ["database"] description = "Code Generator for SeaORM" documentation = "https://docs.rs/sea-orm" edition = "2024" homepage = "https://www.sea-ql.org/SeaORM" keywords = ["sql", "mysql", "postgres", "sqlite"] license = "MIT OR Apache-2.0" name = "sea-orm-codegen" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "2.0.0-rc.37" [lib] name = "sea_orm_codegen" path = "src/lib.rs" [dependencies] heck = { version = "0.5", default-features = false } pluralizer = { version = "0.5" } prettyplease = { version = "0.2", default-features = false, features = [ "verbatim", ] } proc-macro2 = { version = "1", default-features = false } quote = { version = "1", default-features = false } sea-query = { version = "1.0.0-rc.1", default-features = false, features = [ "thread-safe", ] } syn = { version = "2", default-features = false, features = [ "parsing", "proc-macro", "derive", "printing", "clone-impls", "extra-traits", "full", "visit", "visit-mut", "fold", ] } tracing = { version = "0.1", default-features = false, features = ["log"] } [dev-dependencies] indoc = "2.0.6" pretty_assertions = { version = "0.7" } sea-orm = { path = "../", default-features = false, features = ["macros"] } ================================================ FILE: sea-orm-codegen/README.md ================================================ # SeaORM Codegen ================================================ FILE: sea-orm-codegen/rustfmt.toml ================================================ ignore = [ "tests/compact/*.rs", "tests/expanded/*.rs", "tests_cfg", ] ================================================ FILE: sea-orm-codegen/src/entity/active_enum.rs ================================================ use heck::ToUpperCamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use sea_query::DynIden; use crate::{EntityFormat, WithSerde}; #[derive(Clone, Debug)] pub struct ActiveEnum { pub(crate) enum_name: DynIden, pub(crate) values: Vec, } impl ActiveEnum { pub fn impl_active_enum( &self, with_serde: &WithSerde, with_copy_enums: bool, extra_derives: &TokenStream, extra_attributes: &TokenStream, entity_format: EntityFormat, ) -> TokenStream { let enum_name = &self.enum_name.to_string(); let enum_iden = format_ident!("{}", enum_name.to_upper_camel_case()); let values: Vec = self.values.iter().map(|v| v.to_string()).collect(); let variants = values.iter().map(|v| v.trim()).map(|v| { if v.is_empty() { println!("Warning: item in the enumeration '{enum_name}' is an empty string, it will be converted to `__EmptyString`. You can modify it later as needed."); return format_ident!("__EmptyString"); } let is_leading_digit = v.chars().next().is_some_and(|c| c.is_ascii_digit()); let is_valid_char = |c: char| c.is_ascii_alphanumeric() || c == '_'; let is_passthrough = |c: char| matches!(c, '-' | ' '); let needs_utf8_escape = |c: char| !is_valid_char(c) && !is_passthrough(c); if v.chars().any(needs_utf8_escape) { println!("Warning: item '{v}' in the enumeration '{enum_name}' cannot be converted into a valid Rust enum member name. It will be converted to its corresponding UTF-8 encoding. You can modify it later as needed."); let mut buf = String::new(); if is_leading_digit { buf.push('_'); } for c in v.chars() { if is_passthrough(c) { continue; } else if is_valid_char(c) || c.len_utf8() > 1 { buf.push(c); } else { buf.push_str(&format!("U{:04X}", c as u32)); } } return format_ident!("{buf}"); } if is_leading_digit { let sanitized = v.chars().filter(|&c| is_valid_char(c)).collect::(); format_ident!("_{sanitized}") } else { format_ident!("{}", v.to_upper_camel_case()) } }); let serde_derive = with_serde.extra_derive(); let copy_derive = if with_copy_enums { quote! { , Copy } } else { quote! {} }; if entity_format == EntityFormat::Frontend { quote! { #[derive(Debug, Clone, PartialEq, Eq #copy_derive #serde_derive #extra_derives)] #extra_attributes pub enum #enum_iden { #( #variants, )* } } } else { quote! { #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum #copy_derive #serde_derive #extra_derives)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = #enum_name)] #extra_attributes pub enum #enum_iden { #( #[sea_orm(string_value = #values)] #variants, )* } } } } } #[cfg(test)] mod tests { use super::*; use crate::entity::writer::{bonus_attributes, bonus_derive}; use pretty_assertions::assert_eq; use sea_query::{Alias, IntoIden}; #[test] fn test_enum_variant_starts_with_empty_string() { assert_eq!( ActiveEnum { enum_name: Alias::new("media_type").into_iden(), values: vec![""] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), } .impl_active_enum( &WithSerde::None, true, &TokenStream::new(), &TokenStream::new(), EntityFormat::Compact, ) .to_string(), quote!( #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "media_type")] pub enum MediaType { #[sea_orm(string_value = "")] __EmptyString, } ) .to_string() ) } #[test] fn test_enum_variant_starts_with_number() { assert_eq!( ActiveEnum { enum_name: Alias::new("media_type").into_iden(), values: vec![ "UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D", "2-D" ] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), } .impl_active_enum( &WithSerde::None, true, &TokenStream::new(), &TokenStream::new(), EntityFormat::Compact, ) .to_string(), quote!( #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "media_type")] pub enum MediaType { #[sea_orm(string_value = "UNKNOWN")] Unknown, #[sea_orm(string_value = "BITMAP")] Bitmap, #[sea_orm(string_value = "DRAWING")] Drawing, #[sea_orm(string_value = "AUDIO")] Audio, #[sea_orm(string_value = "VIDEO")] Video, #[sea_orm(string_value = "MULTIMEDIA")] Multimedia, #[sea_orm(string_value = "OFFICE")] Office, #[sea_orm(string_value = "TEXT")] Text, #[sea_orm(string_value = "EXECUTABLE")] Executable, #[sea_orm(string_value = "ARCHIVE")] Archive, #[sea_orm(string_value = "3D")] _3D, #[sea_orm(string_value = "2-D")] _2D, } ) .to_string() ) } #[test] fn test_enum_extra_derives() { assert_eq!( ActiveEnum { enum_name: Alias::new("media_type").into_iden(), values: vec!["UNKNOWN", "BITMAP",] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), } .impl_active_enum( &WithSerde::None, true, &bonus_derive(["specta::Type", "ts_rs::TS"]), &TokenStream::new(), EntityFormat::Compact, ) .to_string(), build_generated_enum(), ); #[rustfmt::skip] fn build_generated_enum() -> String { quote!( #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy, specta :: Type, ts_rs :: TS)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "media_type")] pub enum MediaType { #[sea_orm(string_value = "UNKNOWN")] Unknown, #[sea_orm(string_value = "BITMAP")] Bitmap, } ) .to_string() } } #[test] fn test_enum_extra_attributes() { assert_eq!( ActiveEnum { enum_name: Alias::new("coinflip_result_type").into_iden(), values: vec!["HEADS", "TAILS"] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), } .impl_active_enum( &WithSerde::None, true, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#]), EntityFormat::Compact, ) .to_string(), quote!( #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy)] #[sea_orm( rs_type = "String", db_type = "Enum", enum_name = "coinflip_result_type" )] #[serde(rename_all = "camelCase")] pub enum CoinflipResultType { #[sea_orm(string_value = "HEADS")] Heads, #[sea_orm(string_value = "TAILS")] Tails, } ) .to_string() ); assert_eq!( ActiveEnum { enum_name: Alias::new("coinflip_result_type").into_iden(), values: vec!["HEADS", "TAILS"] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), } .impl_active_enum( &WithSerde::None, true, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]), EntityFormat::Compact, ) .to_string(), quote!( #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy)] #[sea_orm( rs_type = "String", db_type = "Enum", enum_name = "coinflip_result_type" )] #[serde(rename_all = "camelCase")] #[ts(export)] pub enum CoinflipResultType { #[sea_orm(string_value = "HEADS")] Heads, #[sea_orm(string_value = "TAILS")] Tails, } ) .to_string() ) } #[test] fn test_enum_variant_utf8_encode() { assert_eq!( ActiveEnum { enum_name: Alias::new("ty").into_iden(), values: vec![ "Question", "QuestionsAdditional", "Answer", "Other", "/", "//", "A-B-C", "你好", "0/", "0//", "0A-B-C", "0你好", ] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), } .impl_active_enum( &WithSerde::None, true, &TokenStream::new(), &TokenStream::new(), EntityFormat::Compact, ) .to_string(), quote!( #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "ty")] pub enum Ty { #[sea_orm(string_value = "Question")] Question, #[sea_orm(string_value = "QuestionsAdditional")] QuestionsAdditional, #[sea_orm(string_value = "Answer")] Answer, #[sea_orm(string_value = "Other")] Other, #[sea_orm(string_value = "/")] U002F, #[sea_orm(string_value = "//")] U002FU002F, #[sea_orm(string_value = "A-B-C")] ABC, #[sea_orm(string_value = "你好")] 你好, #[sea_orm(string_value = "0/")] _0U002F, #[sea_orm(string_value = "0//")] _0U002FU002F, #[sea_orm(string_value = "0A-B-C")] _0ABC, #[sea_orm(string_value = "0你好")] _0你好, } ) .to_string() ) } } ================================================ FILE: sea-orm-codegen/src/entity/base_entity.rs ================================================ use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{Ident, TokenStream}; use quote::format_ident; use quote::quote; use sea_query::ColumnType; use crate::{ Column, ColumnOption, ConjunctRelation, PrimaryKey, Relation, util::escape_rust_keyword, }; #[derive(Clone, Debug)] pub struct Entity { pub(crate) table_name: String, pub(crate) columns: Vec, pub(crate) relations: Vec, pub(crate) conjunct_relations: Vec, pub(crate) primary_keys: Vec, } impl Entity { pub fn get_table_name_snake_case(&self) -> String { self.table_name.to_snake_case() } pub fn get_table_name_camel_case(&self) -> String { self.table_name.to_upper_camel_case() } pub fn get_table_name_snake_case_ident(&self) -> Ident { format_ident!("{}", escape_rust_keyword(self.get_table_name_snake_case())) } pub fn get_table_name_camel_case_ident(&self) -> Ident { format_ident!("{}", escape_rust_keyword(self.get_table_name_camel_case())) } pub fn get_column_names_snake_case(&self) -> Vec { self.columns .iter() .map(|col| col.get_name_snake_case()) .collect() } pub fn get_column_names_camel_case(&self) -> Vec { self.columns .iter() .map(|col| col.get_name_camel_case()) .collect() } pub fn get_column_rs_types(&self, opt: &ColumnOption) -> Vec { self.columns .clone() .into_iter() .map(|col| col.get_rs_type(opt)) .collect() } pub fn get_column_defs(&self) -> Vec { self.columns .clone() .into_iter() .map(|col| col.get_def()) .collect() } pub fn get_primary_key_names_snake_case(&self) -> Vec { self.primary_keys .iter() .map(|pk| pk.get_name_snake_case()) .collect() } pub fn get_primary_key_names_camel_case(&self) -> Vec { self.primary_keys .iter() .map(|pk| pk.get_name_camel_case()) .collect() } pub fn get_relation_module_name(&self) -> Vec> { self.relations .iter() .map(|rel| rel.get_module_name()) .collect() } pub fn get_relation_enum_name(&self) -> Vec { self.relations .iter() .map(|rel| rel.get_enum_name()) .collect() } /// Used to generate the names for the `enum RelatedEntity` that is useful to the Seaography project pub fn get_related_entity_enum_name(&self) -> Vec { // 1st step get conjunct relations data let conjunct_related_names = self.get_conjunct_relations_to_upper_camel_case(); // 2nd step get reverse self relations data let self_relations_reverse = self .relations .iter() .filter(|rel| rel.self_referencing) .map(|rel| format_ident!("{}Reverse", rel.get_enum_name())); // 3rd step get normal relations data self.get_relation_enum_name() .into_iter() .chain(self_relations_reverse) .chain(conjunct_related_names) .collect() } pub fn get_relation_defs(&self) -> Vec { self.relations.iter().map(|rel| rel.get_def()).collect() } pub fn get_relation_attrs(&self) -> Vec { self.relations.iter().map(|rel| rel.get_attrs()).collect() } /// Trimmed get_related_entity_attrs down to just the entity module pub fn get_related_entity_modules(&self) -> Vec { // 1st step get conjunct relations data let conjunct_related_attrs = self .conjunct_relations .iter() .map(|conj| conj.get_to_snake_case()); // helper function that generates attributes for `Relation` data let produce_relation_attrs = |rel: &Relation, _reverse: bool| match rel.get_module_name() { Some(module_name) => module_name, None => format_ident!("self"), }; // 2nd step get reverse self relations data let self_relations_reverse_attrs = self .relations .iter() .filter(|rel| rel.self_referencing) .map(|rel| produce_relation_attrs(rel, true)); // 3rd step get normal relations data self.relations .iter() .map(|rel| produce_relation_attrs(rel, false)) .chain(self_relations_reverse_attrs) .chain(conjunct_related_attrs) .collect() } /// Used to generate the attributes for the `enum RelatedEntity` that is useful to the Seaography project pub fn get_related_entity_attrs(&self) -> Vec { // 1st step get conjunct relations data let conjunct_related_attrs = self.conjunct_relations.iter().map(|conj| { let entity = format!("super::{}::Entity", conj.get_to_snake_case()); quote! { #[sea_orm( entity = #entity )] } }); // helper function that generates attributes for `Relation` data let produce_relation_attrs = |rel: &Relation, reverse: bool| { let entity = match rel.get_module_name() { Some(module_name) => format!("super::{module_name}::Entity"), None => String::from("Entity"), }; if rel.self_referencing || !rel.impl_related || rel.num_suffix > 0 { let def = if reverse { format!("Relation::{}.def().rev()", rel.get_enum_name()) } else { format!("Relation::{}.def()", rel.get_enum_name()) }; quote! { #[sea_orm( entity = #entity, def = #def )] } } else { quote! { #[sea_orm( entity = #entity )] } } }; // 2nd step get reverse self relations data let self_relations_reverse_attrs = self .relations .iter() .filter(|rel| rel.self_referencing) .map(|rel| produce_relation_attrs(rel, true)); // 3rd step get normal relations data self.relations .iter() .map(|rel| produce_relation_attrs(rel, false)) .chain(self_relations_reverse_attrs) .chain(conjunct_related_attrs) .collect() } pub fn get_primary_key_auto_increment(&self) -> Ident { let auto_increment = self.columns.iter().any(|col| col.auto_increment); format_ident!("{}", auto_increment) } pub fn get_primary_key_rs_type(&self, opt: &ColumnOption) -> TokenStream { let types = self .primary_keys .iter() .map(|primary_key| { self.columns .iter() .find(|col| col.name.eq(&primary_key.name)) .unwrap() .get_rs_type(opt) .to_string() }) .collect::>(); if !types.is_empty() { let value_type = if types.len() > 1 { vec!["(".to_owned(), types.join(", "), ")".to_owned()] } else { types }; value_type.join("").parse().unwrap() } else { TokenStream::new() } } pub fn get_conjunct_relations_via_snake_case(&self) -> Vec { self.conjunct_relations .iter() .map(|con_rel| con_rel.get_via_snake_case()) .collect() } pub fn get_conjunct_relations_to_snake_case(&self) -> Vec { self.conjunct_relations .iter() .map(|con_rel| con_rel.get_to_snake_case()) .collect() } pub fn get_conjunct_relations_to_upper_camel_case(&self) -> Vec { self.conjunct_relations .iter() .map(|con_rel| con_rel.get_to_upper_camel_case()) .collect() } pub fn get_eq_needed(&self) -> TokenStream { fn is_floats(col_type: &ColumnType) -> bool { match col_type { ColumnType::Float | ColumnType::Double => true, ColumnType::Array(col_type) => is_floats(col_type), ColumnType::Vector(_) => true, _ => false, } } self.columns .iter() .find(|column| is_floats(&column.col_type)) // check if float or double exist. // if exist, return nothing .map_or(quote! {, Eq}, |_| quote! {}) } pub fn get_column_serde_attributes( &self, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, ) -> Vec { self.columns .iter() .map(|col| { let is_primary_key = self.primary_keys.iter().any(|pk| pk.name == col.name); col.get_serde_attribute( is_primary_key, serde_skip_deserializing_primary_key, serde_skip_hidden_column, ) }) .collect() } } #[cfg(test)] mod tests { use quote::{format_ident, quote}; use sea_query::{ColumnType, ForeignKeyAction, StringLen}; use crate::{Column, ColumnOption, Entity, PrimaryKey, Relation, RelationType}; fn setup() -> Entity { Entity { table_name: "special_cake".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: false, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::String(StringLen::None), auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![ Relation { ref_table: "fruit".to_owned(), columns: vec!["id".to_owned()], ref_columns: vec!["cake_id".to_owned()], rel_type: RelationType::HasOne, on_delete: Some(ForeignKeyAction::Cascade), on_update: Some(ForeignKeyAction::Cascade), self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "filling".to_owned(), columns: vec!["id".to_owned()], ref_columns: vec!["cake_id".to_owned()], rel_type: RelationType::HasOne, on_delete: Some(ForeignKeyAction::Cascade), on_update: Some(ForeignKeyAction::Cascade), self_referencing: false, num_suffix: 0, impl_related: true, }, ], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], } } #[test] fn test_get_table_name_snake_case() { let entity = setup(); assert_eq!( entity.get_table_name_snake_case(), "special_cake".to_owned() ); } #[test] fn test_get_table_name_camel_case() { let entity = setup(); assert_eq!(entity.get_table_name_camel_case(), "SpecialCake".to_owned()); } #[test] fn test_get_table_name_snake_case_ident() { let entity = setup(); assert_eq!( entity.get_table_name_snake_case_ident(), format_ident!("{}", "special_cake") ); } #[test] fn test_get_table_name_camel_case_ident() { let entity = setup(); assert_eq!( entity.get_table_name_camel_case_ident(), format_ident!("{}", "SpecialCake") ); } #[test] fn test_get_column_names_snake_case() { let entity = setup(); for (i, elem) in entity.get_column_names_snake_case().into_iter().enumerate() { assert_eq!(elem, entity.columns[i].get_name_snake_case()); } } #[test] fn test_get_column_names_camel_case() { let entity = setup(); for (i, elem) in entity.get_column_names_camel_case().into_iter().enumerate() { assert_eq!(elem, entity.columns[i].get_name_camel_case()); } } #[test] fn test_get_column_rs_types() { let entity = setup(); let opt = ColumnOption::default(); for (i, elem) in entity.get_column_rs_types(&opt).into_iter().enumerate() { assert_eq!( elem.to_string(), entity.columns[i].get_rs_type(&opt).to_string() ); } } #[test] fn test_get_column_defs() { let entity = setup(); for (i, elem) in entity.get_column_defs().into_iter().enumerate() { assert_eq!(elem.to_string(), entity.columns[i].get_def().to_string()); } } #[test] fn test_get_primary_key_names_snake_case() { let entity = setup(); for (i, elem) in entity .get_primary_key_names_snake_case() .into_iter() .enumerate() { assert_eq!(elem, entity.primary_keys[i].get_name_snake_case()); } } #[test] fn test_get_primary_key_names_camel_case() { let entity = setup(); for (i, elem) in entity .get_primary_key_names_camel_case() .into_iter() .enumerate() { assert_eq!(elem, entity.primary_keys[i].get_name_camel_case()); } } #[test] fn test_get_relation_module_name() { let entity = setup(); for (i, elem) in entity.get_relation_module_name().into_iter().enumerate() { assert_eq!(elem, entity.relations[i].get_module_name()); } } #[test] fn test_get_relation_enum_name() { let entity = setup(); for (i, elem) in entity.get_relation_enum_name().into_iter().enumerate() { assert_eq!(elem, entity.relations[i].get_enum_name()); } } #[test] fn test_get_relation_defs() { let entity = setup(); for (i, elem) in entity.get_relation_defs().into_iter().enumerate() { assert_eq!(elem.to_string(), entity.relations[i].get_def().to_string()); } } #[test] fn test_get_relation_attrs() { let entity = setup(); for (i, elem) in entity.get_relation_attrs().into_iter().enumerate() { assert_eq!( elem.to_string(), entity.relations[i].get_attrs().to_string() ); } } #[test] fn test_get_primary_key_auto_increment() { let mut entity = setup(); assert_eq!( entity.get_primary_key_auto_increment(), format_ident!("{}", false) ); entity.columns[0].auto_increment = true; assert_eq!( entity.get_primary_key_auto_increment(), format_ident!("{}", true) ); } #[test] fn test_get_primary_key_rs_type() { let entity = setup(); let opt = Default::default(); assert_eq!( entity.get_primary_key_rs_type(&opt).to_string(), entity.columns[0].get_rs_type(&opt).to_string() ); } #[test] fn test_get_conjunct_relations_via_snake_case() { let entity = setup(); for (i, elem) in entity .get_conjunct_relations_via_snake_case() .into_iter() .enumerate() { assert_eq!(elem, entity.conjunct_relations[i].get_via_snake_case()); } } #[test] fn test_get_conjunct_relations_to_snake_case() { let entity = setup(); for (i, elem) in entity .get_conjunct_relations_to_snake_case() .into_iter() .enumerate() { assert_eq!(elem, entity.conjunct_relations[i].get_to_snake_case()); } } #[test] fn test_get_conjunct_relations_to_upper_camel_case() { let entity = setup(); for (i, elem) in entity .get_conjunct_relations_to_upper_camel_case() .into_iter() .enumerate() { assert_eq!(elem, entity.conjunct_relations[i].get_to_upper_camel_case()); } } #[test] fn test_get_eq_needed() { let entity = setup(); let expected = quote! {, Eq}; assert_eq!(entity.get_eq_needed().to_string(), expected.to_string()); } } ================================================ FILE: sea-orm-codegen/src/entity/column.rs ================================================ use crate::{BigIntegerType, DateTimeCrate, util::escape_rust_keyword}; use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use sea_query::{ColumnDef, ColumnType, StringLen}; use std::fmt::Write as FmtWrite; #[derive(Debug, Clone)] pub struct Column { pub(crate) name: String, pub(crate) col_type: ColumnType, pub(crate) auto_increment: bool, pub(crate) not_null: bool, pub(crate) unique: bool, pub(crate) unique_key: Option, } #[derive(Debug, Default, Copy, Clone)] pub struct ColumnOption { pub(crate) date_time_crate: DateTimeCrate, pub(crate) big_integer_type: BigIntegerType, } impl Column { pub fn get_name_snake_case(&self) -> Ident { format_ident!("{}", escape_rust_keyword(self.name.to_snake_case())) } pub fn get_name_camel_case(&self) -> Ident { format_ident!("{}", escape_rust_keyword(self.name.to_upper_camel_case())) } pub fn is_snake_case_name(&self) -> bool { self.name.to_snake_case() == self.name } pub fn get_rs_type(&self, opt: &ColumnOption) -> TokenStream { fn write_rs_type(col_type: &ColumnType, opt: &ColumnOption) -> String { #[allow(unreachable_patterns)] match col_type { ColumnType::Char(_) | ColumnType::String(_) | ColumnType::Text | ColumnType::Custom(_) => "String".to_owned(), ColumnType::TinyInteger => "i8".to_owned(), ColumnType::SmallInteger => "i16".to_owned(), ColumnType::Integer => "i32".to_owned(), ColumnType::BigInteger => match opt.big_integer_type { BigIntegerType::I64 => "i64", BigIntegerType::I32 => "i32", } .to_owned(), ColumnType::TinyUnsigned => "u8".to_owned(), ColumnType::SmallUnsigned => "u16".to_owned(), ColumnType::Unsigned => "u32".to_owned(), ColumnType::BigUnsigned => "u64".to_owned(), ColumnType::Float => "f32".to_owned(), ColumnType::Double => "f64".to_owned(), ColumnType::Json | ColumnType::JsonBinary => "Json".to_owned(), ColumnType::Date => match opt.date_time_crate { DateTimeCrate::Chrono => "Date".to_owned(), DateTimeCrate::Time => "TimeDate".to_owned(), }, ColumnType::Time => match opt.date_time_crate { DateTimeCrate::Chrono => "Time".to_owned(), DateTimeCrate::Time => "TimeTime".to_owned(), }, ColumnType::DateTime => match opt.date_time_crate { DateTimeCrate::Chrono => "DateTime".to_owned(), DateTimeCrate::Time => "TimeDateTime".to_owned(), }, ColumnType::Timestamp => match opt.date_time_crate { DateTimeCrate::Chrono => "DateTimeUtc".to_owned(), DateTimeCrate::Time => "TimeDateTime".to_owned(), }, ColumnType::TimestampWithTimeZone => match opt.date_time_crate { DateTimeCrate::Chrono => "DateTimeWithTimeZone".to_owned(), DateTimeCrate::Time => "TimeDateTimeWithTimeZone".to_owned(), }, ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal".to_owned(), ColumnType::Uuid => "Uuid".to_owned(), ColumnType::Binary(_) | ColumnType::VarBinary(_) | ColumnType::Blob => { "Vec".to_owned() } ColumnType::Boolean => "bool".to_owned(), ColumnType::Enum { name, .. } => name.to_string().to_upper_camel_case(), ColumnType::Array(column_type) => { format!("Vec<{}>", write_rs_type(column_type, opt)) } ColumnType::Vector(_) => "PgVector".to_owned(), ColumnType::Bit(None | Some(1)) => "bool".to_owned(), ColumnType::Bit(_) | ColumnType::VarBit(_) => "Vec".to_owned(), ColumnType::Year => "i32".to_owned(), ColumnType::Cidr | ColumnType::Inet => "IpNetwork".to_owned(), ColumnType::Interval(_, _) | ColumnType::MacAddr | ColumnType::LTree => { "String".to_owned() } _ => unimplemented!(), } } let ident: TokenStream = write_rs_type(&self.col_type, opt).parse().unwrap(); match self.not_null { true => quote! { #ident }, false => quote! { Option<#ident> }, } } pub fn get_col_type_attrs(&self) -> Option { let col_type = match &self.col_type { ColumnType::Float => Some("Float".to_owned()), ColumnType::Double => Some("Double".to_owned()), ColumnType::Decimal(Some((p, s))) => Some(format!("Decimal(Some(({p}, {s})))")), ColumnType::Money(Some((p, s))) => Some(format!("Money(Some({p}, {s}))")), ColumnType::Text => Some("Text".to_owned()), ColumnType::JsonBinary => Some("JsonBinary".to_owned()), ColumnType::Custom(iden) => { let ty = format!("custom(\"{iden}\")"); return Some(quote! ( ignore, column_type = #ty, select_as = "text" )); } ColumnType::Binary(s) => Some(format!("Binary({s})")), ColumnType::VarBinary(s) => match s { StringLen::N(s) => Some(format!("VarBinary(StringLen::N({s}))")), StringLen::None => Some("VarBinary(StringLen::None)".to_owned()), StringLen::Max => Some("VarBinary(StringLen::Max)".to_owned()), }, ColumnType::Blob => Some("Blob".to_owned()), ColumnType::Cidr => Some("Cidr".to_owned()), _ => None, }; col_type.map(|ty| quote! { column_type = #ty }) } pub fn get_def(&self) -> TokenStream { fn write_col_def(col_type: &ColumnType) -> TokenStream { match col_type { ColumnType::Char(s) => match s { Some(s) => quote! { ColumnType::Char(Some(#s)) }, None => quote! { ColumnType::Char(None) }, }, ColumnType::String(s) => match s { StringLen::N(s) => quote! { ColumnType::String(StringLen::N(#s)) }, StringLen::None => quote! { ColumnType::String(StringLen::None) }, StringLen::Max => quote! { ColumnType::String(StringLen::Max) }, }, ColumnType::Text => quote! { ColumnType::Text }, ColumnType::TinyInteger => quote! { ColumnType::TinyInteger }, ColumnType::SmallInteger => quote! { ColumnType::SmallInteger }, ColumnType::Integer => quote! { ColumnType::Integer }, ColumnType::BigInteger => quote! { ColumnType::BigInteger }, ColumnType::TinyUnsigned => quote! { ColumnType::TinyUnsigned }, ColumnType::SmallUnsigned => quote! { ColumnType::SmallUnsigned }, ColumnType::Unsigned => quote! { ColumnType::Unsigned }, ColumnType::BigUnsigned => quote! { ColumnType::BigUnsigned }, ColumnType::Float => quote! { ColumnType::Float }, ColumnType::Double => quote! { ColumnType::Double }, ColumnType::Decimal(s) => match s { Some((s1, s2)) => quote! { ColumnType::Decimal(Some((#s1, #s2))) }, None => quote! { ColumnType::Decimal(None) }, }, ColumnType::DateTime => quote! { ColumnType::DateTime }, ColumnType::Timestamp => quote! { ColumnType::Timestamp }, ColumnType::TimestampWithTimeZone => { quote! { ColumnType::TimestampWithTimeZone } } ColumnType::Time => quote! { ColumnType::Time }, ColumnType::Date => quote! { ColumnType::Date }, ColumnType::Binary(s) => { quote! { ColumnType::Binary(#s) } } ColumnType::VarBinary(s) => match s { StringLen::N(s) => quote! { ColumnType::VarBinary(StringLen::N(#s)) }, StringLen::None => quote! { ColumnType::VarBinary(StringLen::None) }, StringLen::Max => quote! { ColumnType::VarBinary(StringLen::Max) }, }, ColumnType::Blob => quote! { ColumnType::Blob }, ColumnType::Boolean => quote! { ColumnType::Boolean }, ColumnType::Money(s) => match s { Some((s1, s2)) => quote! { ColumnType::Money(Some((#s1, #s2))) }, None => quote! { ColumnType::Money(None) }, }, ColumnType::Json => quote! { ColumnType::Json }, ColumnType::JsonBinary => quote! { ColumnType::JsonBinary }, ColumnType::Uuid => quote! { ColumnType::Uuid }, ColumnType::Cidr => quote! { ColumnType::Cidr }, ColumnType::Inet => quote! { ColumnType::Inet }, ColumnType::Custom(s) => { let s = s.to_string(); quote! { ColumnType::custom(#s) } } ColumnType::Enum { name, .. } => { let enum_ident = format_ident!("{}", name.to_string().to_upper_camel_case()); quote! { #enum_ident::db_type() .get_column_type() .to_owned() } } ColumnType::Array(column_type) => { let column_type = write_col_def(column_type); quote! { ColumnType::Array(RcOrArc::new(#column_type)) } } ColumnType::Vector(size) => match size { Some(size) => quote! { ColumnType::Vector(Some(#size)) }, None => quote! { ColumnType::Vector(None) }, }, #[allow(unreachable_patterns)] _ => unimplemented!(), } } let mut col_def = write_col_def(&self.col_type); col_def.extend(quote! { .def() }); if !self.not_null { col_def.extend(quote! { .null() }); } if self.unique { col_def.extend(quote! { .unique() }); } col_def } pub fn get_info(&self, opt: &ColumnOption) -> String { let mut info = String::new(); let type_info = self.get_rs_type(opt).to_string().replace(' ', ""); let col_info = self.col_info(); write!( &mut info, "Column `{}`: {}{}", self.name, type_info, col_info ) .unwrap(); info } fn col_info(&self) -> String { let mut info = String::new(); if self.auto_increment { write!(&mut info, ", auto_increment").unwrap(); } if self.not_null { write!(&mut info, ", not_null").unwrap(); } if self.unique { write!(&mut info, ", unique").unwrap(); } info } pub fn get_serde_attribute( &self, is_primary_key: bool, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, ) -> TokenStream { if self.name.starts_with('_') && serde_skip_hidden_column { quote! { #[serde(skip)] } } else if serde_skip_deserializing_primary_key && is_primary_key { quote! { #[serde(skip_deserializing)] } } else { quote! {} } } pub fn get_inner_col_type(&self) -> &ColumnType { match &self.col_type { ColumnType::Array(inner_col_type) => inner_col_type.as_ref(), _ => &self.col_type, } } } impl From for Column { fn from(col_def: ColumnDef) -> Self { (&col_def).into() } } impl From<&ColumnDef> for Column { fn from(col_def: &ColumnDef) -> Self { let name = col_def.get_column_name(); let col_type = match col_def.get_column_type() { Some(ty) => ty.clone(), None => panic!("ColumnType should not be empty"), }; let auto_increment = col_def.get_column_spec().auto_increment; let not_null = match col_def.get_column_spec().nullable { Some(nullable) => !nullable, None => false, }; let unique = col_def.get_column_spec().unique; Self { name, col_type, auto_increment, not_null, unique, unique_key: None, } } } #[cfg(test)] mod tests { use crate::{Column, ColumnOption, DateTimeCrate}; use proc_macro2::TokenStream; use quote::quote; use sea_query::{Alias, ColumnDef, ColumnType, SeaRc, StringLen}; fn date_time_crate_chrono() -> ColumnOption { ColumnOption { date_time_crate: DateTimeCrate::Chrono, big_integer_type: Default::default(), } } fn date_time_crate_time() -> ColumnOption { ColumnOption { date_time_crate: DateTimeCrate::Time, big_integer_type: Default::default(), } } fn setup() -> Vec { macro_rules! make_col { ($name:expr, $col_type:expr) => { Column { name: $name.to_owned(), col_type: $col_type, auto_increment: false, not_null: false, unique: false, unique_key: None, } }; } vec![ make_col!("id", ColumnType::String(StringLen::N(255))), make_col!("id", ColumnType::String(StringLen::None)), make_col!( "cake_id", ColumnType::Custom(SeaRc::new(Alias::new("cus_col"))) ), make_col!("CakeId", ColumnType::TinyInteger), make_col!("CakeId", ColumnType::TinyUnsigned), make_col!("CakeId", ColumnType::SmallInteger), make_col!("CakeId", ColumnType::SmallUnsigned), make_col!("CakeId", ColumnType::Integer), make_col!("CakeId", ColumnType::Unsigned), make_col!("CakeFillingId", ColumnType::BigInteger), make_col!("CakeFillingId", ColumnType::BigUnsigned), make_col!("cake-filling-id", ColumnType::Float), make_col!("CAKE_FILLING_ID", ColumnType::Double), make_col!("CAKE-FILLING-ID", ColumnType::Binary(10)), make_col!("CAKE-FILLING-ID", ColumnType::VarBinary(StringLen::None)), make_col!("CAKE-FILLING-ID", ColumnType::VarBinary(StringLen::N(10))), make_col!("CAKE-FILLING-ID", ColumnType::VarBinary(StringLen::Max)), make_col!("CAKE", ColumnType::Boolean), make_col!("date", ColumnType::Date), make_col!("time", ColumnType::Time), make_col!("date_time", ColumnType::DateTime), make_col!("timestamp", ColumnType::Timestamp), make_col!("timestamp_tz", ColumnType::TimestampWithTimeZone), ] } #[test] fn test_get_name_snake_case() { let columns = setup(); let snack_cases = vec![ "id", "id", "cake_id", "cake_id", "cake_id", "cake_id", "cake_id", "cake_id", "cake_id", "cake_filling_id", "cake_filling_id", "cake_filling_id", "cake_filling_id", "cake_filling_id", "cake_filling_id", "cake_filling_id", "cake_filling_id", "cake", "date", "time", "date_time", "timestamp", "timestamp_tz", ]; for (col, snack_case) in columns.into_iter().zip(snack_cases) { assert_eq!(col.get_name_snake_case().to_string(), snack_case); } } #[test] fn test_get_name_camel_case() { let columns = setup(); let camel_cases = vec![ "Id", "Id", "CakeId", "CakeId", "CakeId", "CakeId", "CakeId", "CakeId", "CakeId", "CakeFillingId", "CakeFillingId", "CakeFillingId", "CakeFillingId", "CakeFillingId", "CakeFillingId", "CakeFillingId", "CakeFillingId", "Cake", "Date", "Time", "DateTime", "Timestamp", "TimestampTz", ]; for (col, camel_case) in columns.into_iter().zip(camel_cases) { assert_eq!(col.get_name_camel_case().to_string(), camel_case); } } #[test] fn test_get_rs_type_with_chrono() { let columns = setup(); let rs_types = vec![ "String", "String", "String", "i8", "u8", "i16", "u16", "i32", "u32", "i64", "u64", "f32", "f64", "Vec", "Vec", "Vec", "Vec", "bool", "Date", "Time", "DateTime", "DateTimeUtc", "DateTimeWithTimeZone", ]; for (mut col, rs_type) in columns.into_iter().zip(rs_types) { let rs_type: TokenStream = rs_type.parse().unwrap(); col.not_null = true; assert_eq!( col.get_rs_type(&date_time_crate_chrono()).to_string(), quote!(#rs_type).to_string() ); col.not_null = false; assert_eq!( col.get_rs_type(&date_time_crate_chrono()).to_string(), quote!(Option<#rs_type>).to_string() ); } } #[test] fn test_get_rs_type_with_time() { let columns = setup(); let rs_types = vec![ "String", "String", "String", "i8", "u8", "i16", "u16", "i32", "u32", "i64", "u64", "f32", "f64", "Vec", "Vec", "Vec", "Vec", "bool", "TimeDate", "TimeTime", "TimeDateTime", "TimeDateTime", "TimeDateTimeWithTimeZone", ]; for (mut col, rs_type) in columns.into_iter().zip(rs_types) { let rs_type: TokenStream = rs_type.parse().unwrap(); col.not_null = true; assert_eq!( col.get_rs_type(&date_time_crate_time()).to_string(), quote!(#rs_type).to_string() ); col.not_null = false; assert_eq!( col.get_rs_type(&date_time_crate_time()).to_string(), quote!(Option<#rs_type>).to_string() ); } } #[test] fn test_get_def() { let columns = setup(); let col_defs = vec![ "ColumnType::String(StringLen::N(255u32)).def()", "ColumnType::String(StringLen::None).def()", "ColumnType::custom(\"cus_col\").def()", "ColumnType::TinyInteger.def()", "ColumnType::TinyUnsigned.def()", "ColumnType::SmallInteger.def()", "ColumnType::SmallUnsigned.def()", "ColumnType::Integer.def()", "ColumnType::Unsigned.def()", "ColumnType::BigInteger.def()", "ColumnType::BigUnsigned.def()", "ColumnType::Float.def()", "ColumnType::Double.def()", "ColumnType::Binary(10u32).def()", "ColumnType::VarBinary(StringLen::None).def()", "ColumnType::VarBinary(StringLen::N(10u32)).def()", "ColumnType::VarBinary(StringLen::Max).def()", "ColumnType::Boolean.def()", "ColumnType::Date.def()", "ColumnType::Time.def()", "ColumnType::DateTime.def()", "ColumnType::Timestamp.def()", "ColumnType::TimestampWithTimeZone.def()", ]; for (mut col, col_def) in columns.into_iter().zip(col_defs) { let mut col_def: TokenStream = col_def.parse().unwrap(); col.not_null = true; assert_eq!(col.get_def().to_string(), col_def.to_string()); col.not_null = false; col_def.extend(quote!(.null())); assert_eq!(col.get_def().to_string(), col_def.to_string()); col.unique = true; col_def.extend(quote!(.unique())); assert_eq!(col.get_def().to_string(), col_def.to_string()); } } #[test] fn test_get_info() { let column: Column = ColumnDef::new(Alias::new("id")).string().to_owned().into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `id`: Option" ); let column: Column = ColumnDef::new(Alias::new("id")) .string() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `id`: String, not_null" ); let column: Column = ColumnDef::new(Alias::new("id")) .string() .not_null() .unique_key() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `id`: String, not_null, unique" ); let column: Column = ColumnDef::new(Alias::new("id")) .string() .not_null() .unique_key() .auto_increment() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `id`: String, auto_increment, not_null, unique" ); let column: Column = ColumnDef::new(Alias::new("date_field")) .date() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `date_field`: Date, not_null" ); let column: Column = ColumnDef::new(Alias::new("date_field")) .date() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_time()).as_str(), "Column `date_field`: TimeDate, not_null" ); let column: Column = ColumnDef::new(Alias::new("time_field")) .time() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `time_field`: Time, not_null" ); let column: Column = ColumnDef::new(Alias::new("time_field")) .time() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_time()).as_str(), "Column `time_field`: TimeTime, not_null" ); let column: Column = ColumnDef::new(Alias::new("date_time_field")) .date_time() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `date_time_field`: DateTime, not_null" ); let column: Column = ColumnDef::new(Alias::new("date_time_field")) .date_time() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_time()).as_str(), "Column `date_time_field`: TimeDateTime, not_null" ); let column: Column = ColumnDef::new(Alias::new("timestamp_field")) .timestamp() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `timestamp_field`: DateTimeUtc, not_null" ); let column: Column = ColumnDef::new(Alias::new("timestamp_field")) .timestamp() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_time()).as_str(), "Column `timestamp_field`: TimeDateTime, not_null" ); let column: Column = ColumnDef::new(Alias::new("timestamp_with_timezone_field")) .timestamp_with_time_zone() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_chrono()).as_str(), "Column `timestamp_with_timezone_field`: DateTimeWithTimeZone, not_null" ); let column: Column = ColumnDef::new(Alias::new("timestamp_with_timezone_field")) .timestamp_with_time_zone() .not_null() .to_owned() .into(); assert_eq!( column.get_info(&date_time_crate_time()).as_str(), "Column `timestamp_with_timezone_field`: TimeDateTimeWithTimeZone, not_null" ); } #[test] fn test_from_column_def() { let column: Column = ColumnDef::new(Alias::new("id")).string().to_owned().into(); assert_eq!( column.get_def().to_string(), quote! { ColumnType::String(StringLen::None).def().null() } .to_string() ); let column: Column = ColumnDef::new(Alias::new("id")) .string() .not_null() .to_owned() .into(); assert!(column.not_null); let column: Column = ColumnDef::new(Alias::new("id")) .string() .unique_key() .not_null() .to_owned() .into(); assert!(column.unique); assert!(column.not_null); let column: Column = ColumnDef::new(Alias::new("id")) .string() .auto_increment() .unique_key() .not_null() .to_owned() .into(); assert!(column.auto_increment); assert!(column.unique); assert!(column.not_null); } } ================================================ FILE: sea-orm-codegen/src/entity/conjunct_relation.rs ================================================ use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::Ident; use quote::format_ident; use crate::util::escape_rust_keyword; #[derive(Clone, Debug)] pub struct ConjunctRelation { pub(crate) via: String, pub(crate) to: String, } impl ConjunctRelation { pub fn get_via_snake_case(&self) -> Ident { format_ident!("{}", escape_rust_keyword(self.via.to_snake_case())) } pub fn get_to_snake_case(&self) -> Ident { format_ident!("{}", escape_rust_keyword(self.to.to_snake_case())) } pub fn get_to_upper_camel_case(&self) -> Ident { format_ident!("{}", self.to.to_upper_camel_case()) } } #[cfg(test)] mod tests { use crate::ConjunctRelation; fn setup() -> Vec { vec![ ConjunctRelation { via: "cake_filling".to_owned(), to: "cake".to_owned(), }, ConjunctRelation { via: "cake_filling".to_owned(), to: "filling".to_owned(), }, ] } #[test] fn test_get_via_snake_case() { let conjunct_relations = setup(); let via_vec = vec!["cake_filling", "cake_filling"]; for (con_rel, via) in conjunct_relations.into_iter().zip(via_vec) { assert_eq!(con_rel.get_via_snake_case(), via); } } #[test] fn test_get_to_snake_case() { let conjunct_relations = setup(); let to_vec = vec!["cake", "filling"]; for (con_rel, to) in conjunct_relations.into_iter().zip(to_vec) { assert_eq!(con_rel.get_to_snake_case(), to); } } #[test] fn test_get_to_upper_camel_case() { let conjunct_relations = setup(); let to_vec = vec!["Cake", "Filling"]; for (con_rel, to) in conjunct_relations.into_iter().zip(to_vec) { assert_eq!(con_rel.get_to_upper_camel_case(), to); } } } ================================================ FILE: sea-orm-codegen/src/entity/mod.rs ================================================ mod active_enum; mod base_entity; mod column; mod conjunct_relation; mod primary_key; mod relation; mod transformer; mod writer; pub use active_enum::*; pub use base_entity::*; pub use column::*; pub use conjunct_relation::*; pub use primary_key::*; pub use relation::*; pub use transformer::*; pub use writer::*; ================================================ FILE: sea-orm-codegen/src/entity/primary_key.rs ================================================ use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::Ident; use quote::format_ident; #[derive(Clone, Debug)] pub struct PrimaryKey { pub(crate) name: String, } impl PrimaryKey { pub fn get_name_snake_case(&self) -> Ident { format_ident!("{}", self.name.to_snake_case()) } pub fn get_name_camel_case(&self) -> Ident { format_ident!("{}", self.name.to_upper_camel_case()) } } #[cfg(test)] mod tests { use crate::PrimaryKey; fn setup() -> PrimaryKey { PrimaryKey { name: "cake_id".to_owned(), } } #[test] fn test_get_name_snake_case() { let primary_key = setup(); assert_eq!(primary_key.get_name_snake_case(), "cake_id".to_owned()); } #[test] fn test_get_name_camel_case() { let primary_key = setup(); assert_eq!(primary_key.get_name_camel_case(), "CakeId".to_owned()); } } ================================================ FILE: sea-orm-codegen/src/entity/relation.rs ================================================ use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use sea_query::{ForeignKeyAction, TableForeignKey}; use syn::{punctuated::Punctuated, token::Comma}; use crate::util::escape_rust_keyword; #[derive(Debug, Clone, Copy)] pub enum RelationType { HasOne, HasMany, BelongsTo, } #[derive(Clone, Debug)] pub struct Relation { pub(crate) ref_table: String, pub(crate) columns: Vec, pub(crate) ref_columns: Vec, pub(crate) rel_type: RelationType, pub(crate) on_update: Option, pub(crate) on_delete: Option, pub(crate) self_referencing: bool, pub(crate) num_suffix: usize, pub(crate) impl_related: bool, } impl Relation { pub fn get_enum_name(&self) -> Ident { let name = if self.self_referencing { format_ident!("SelfRef") } else { format_ident!("{}", self.ref_table.to_upper_camel_case()) }; if self.num_suffix > 0 { format_ident!("{}{}", name, self.num_suffix) } else { name } } pub fn get_module_name(&self) -> Option { if self.self_referencing { None } else { Some(format_ident!( "{}", escape_rust_keyword(self.ref_table.to_snake_case()) )) } } pub fn get_def(&self) -> TokenStream { let rel_type = self.get_rel_type(); let module_name = self.get_module_name(); let ref_entity = if module_name.is_some() { quote! { super::#module_name::Entity } } else { quote! { Entity } }; match self.rel_type { RelationType::HasOne | RelationType::HasMany => { quote! { Entity::#rel_type(#ref_entity).into() } } RelationType::BelongsTo => { let map_src_column = |src_column: &Ident| { quote! { Column::#src_column } }; let map_ref_column = |ref_column: &Ident| { if module_name.is_some() { quote! { super::#module_name::Column::#ref_column } } else { quote! { Column::#ref_column } } }; let map_punctuated = |punctuated: Punctuated| match punctuated.len() { 0..=1 => quote! { #punctuated }, _ => quote! { (#punctuated) }, }; let (from, to) = self.get_src_ref_columns(map_src_column, map_ref_column, map_punctuated); quote! { Entity::#rel_type(#ref_entity) .from(#from) .to(#to) .into() } } } } pub fn get_attrs(&self) -> TokenStream { let rel_type = self.get_rel_type(); let module_name = if let Some(module_name) = self.get_module_name() { format!("super::{module_name}::") } else { String::new() }; let ref_entity = format!("{module_name}Entity"); match self.rel_type { RelationType::HasOne | RelationType::HasMany => { quote! { #[sea_orm(#rel_type = #ref_entity)] } } RelationType::BelongsTo => { let map_src_column = |src_column: &Ident| format!("Column::{src_column}"); let map_ref_column = |ref_column: &Ident| format!("{module_name}Column::{ref_column}"); let map_punctuated = |punctuated: Vec| { let len = punctuated.len(); let punctuated = punctuated.join(", "); match len { 0..=1 => punctuated, _ => format!("({punctuated})"), } }; let (from, to) = self.get_src_ref_columns(map_src_column, map_ref_column, map_punctuated); let on_update = if let Some(action) = &self.on_update { let action = Self::get_foreign_key_action(action); quote! { on_update = #action, } } else { quote! {} }; let on_delete = if let Some(action) = &self.on_delete { let action = Self::get_foreign_key_action(action); quote! { on_delete = #action, } } else { quote! {} }; quote! { #[sea_orm( #rel_type = #ref_entity, from = #from, to = #to, #on_update #on_delete )] } } } } pub fn get_rel_type(&self) -> Ident { match self.rel_type { RelationType::HasOne => format_ident!("has_one"), RelationType::HasMany => format_ident!("has_many"), RelationType::BelongsTo => format_ident!("belongs_to"), } } pub fn get_column_camel_case(&self) -> Vec { self.columns .iter() .map(|col| format_ident!("{}", col.to_upper_camel_case())) .collect() } pub fn get_ref_column_camel_case(&self) -> Vec { self.ref_columns .iter() .map(|col| format_ident!("{}", col.to_upper_camel_case())) .collect() } pub fn get_foreign_key_action(action: &ForeignKeyAction) -> String { action.variant_name().to_owned() } pub fn get_src_ref_columns( &self, map_src_column: F1, map_ref_column: F2, map_punctuated: F3, ) -> (T, T) where F1: Fn(&Ident) -> T, F2: Fn(&Ident) -> T, F3: Fn(I) -> T, I: Extend + Default, { let from: I = self.get_column_camel_case() .iter() .fold(I::default(), |mut acc, src_column| { acc.extend([map_src_column(src_column)]); acc }); let to: I = self.get_ref_column_camel_case() .iter() .fold(I::default(), |mut acc, ref_column| { acc.extend([map_ref_column(ref_column)]); acc }); (map_punctuated(from), map_punctuated(to)) } } impl From<&TableForeignKey> for Relation { fn from(tbl_fk: &TableForeignKey) -> Self { let ref_table = match tbl_fk.get_ref_table() { Some(s) => s.sea_orm_table().to_string(), None => panic!("RefTable should not be empty"), }; let columns = tbl_fk.get_columns(); let ref_columns = tbl_fk.get_ref_columns(); let rel_type = RelationType::BelongsTo; let on_delete = tbl_fk.get_on_delete(); let on_update = tbl_fk.get_on_update(); Self { ref_table, columns, ref_columns, rel_type, on_delete, on_update, self_referencing: false, num_suffix: 0, impl_related: true, } } } #[cfg(test)] mod tests { use crate::{Relation, RelationType}; use proc_macro2::TokenStream; use sea_query::ForeignKeyAction; fn setup() -> Vec { vec![ Relation { ref_table: "fruit".to_owned(), columns: vec!["id".to_owned()], ref_columns: vec!["cake_id".to_owned()], rel_type: RelationType::HasOne, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "filling".to_owned(), columns: vec!["filling_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: Some(ForeignKeyAction::Cascade), on_update: Some(ForeignKeyAction::Cascade), self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "filling".to_owned(), columns: vec!["filling_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::HasMany, on_delete: Some(ForeignKeyAction::Cascade), on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, ] } #[test] fn test_get_module_name() { let relations = setup(); let snake_cases = vec!["fruit", "filling", "filling"]; for (rel, snake_case) in relations.into_iter().zip(snake_cases) { assert_eq!(rel.get_module_name().unwrap().to_string(), snake_case); } } #[test] fn test_get_enum_name() { let relations = setup(); let camel_cases = vec!["Fruit", "Filling", "Filling"]; for (rel, camel_case) in relations.into_iter().zip(camel_cases) { assert_eq!(rel.get_enum_name().to_string(), camel_case); } } #[test] fn test_get_def() { let relations = setup(); let rel_defs = vec![ "Entity::has_one(super::fruit::Entity).into()", "Entity::belongs_to(super::filling::Entity) \ .from(Column::FillingId) \ .to(super::filling::Column::Id) \ .into()", "Entity::has_many(super::filling::Entity).into()", ]; for (rel, rel_def) in relations.into_iter().zip(rel_defs) { let rel_def: TokenStream = rel_def.parse().unwrap(); assert_eq!(rel.get_def().to_string(), rel_def.to_string()); } } #[test] fn test_get_rel_type() { let relations = setup(); let rel_types = vec!["has_one", "belongs_to", "has_many"]; for (rel, rel_type) in relations.into_iter().zip(rel_types) { assert_eq!(rel.get_rel_type(), rel_type); } } #[test] fn test_get_column_camel_case() { let relations = setup(); let cols = vec!["Id", "FillingId", "FillingId"]; for (rel, col) in relations.into_iter().zip(cols) { assert_eq!(rel.get_column_camel_case(), [col]); } } #[test] fn test_get_ref_column_camel_case() { let relations = setup(); let ref_cols = vec!["CakeId", "Id", "Id"]; for (rel, ref_col) in relations.into_iter().zip(ref_cols) { assert_eq!(rel.get_ref_column_camel_case(), [ref_col]); } } } ================================================ FILE: sea-orm-codegen/src/entity/transformer.rs ================================================ use crate::{ ActiveEnum, Column, ConjunctRelation, Entity, EntityWriter, Error, PrimaryKey, Relation, RelationType, }; use sea_query::TableCreateStatement; use std::collections::{BTreeMap, HashMap, HashSet}; #[derive(Clone, Debug)] pub struct EntityTransformer; impl EntityTransformer { pub fn transform(table_create_stmts: Vec) -> Result { let mut enums: BTreeMap = BTreeMap::new(); let mut inverse_relations: BTreeMap> = BTreeMap::new(); let mut entities = BTreeMap::new(); for table_create in table_create_stmts.into_iter() { let table_name = match table_create.get_table_name() { Some(table_ref) => table_ref.sea_orm_table().to_string(), None => { return Err(Error::TransformError( "Table name should not be empty".into(), )); } }; let mut primary_keys: Vec = Vec::new(); let mut columns: Vec = table_create .get_columns() .iter() .map(|col_def| { let primary_key = col_def.get_column_spec().primary_key; if primary_key { primary_keys.push(PrimaryKey { name: col_def.get_column_name(), }); } col_def.into() }) .map(|mut col: Column| { col.unique |= table_create .get_indexes() .iter() .filter(|index| index.is_unique_key()) .map(|index| index.get_index_spec().get_column_names()) .filter(|col_names| col_names.len() == 1 && col_names[0] == col.name) .count() > 0; col }) .inspect(|col| { if let sea_query::ColumnType::Enum { name, variants } = col.get_inner_col_type() { enums.insert( name.to_string(), ActiveEnum { enum_name: name.clone(), values: variants.clone(), }, ); } }) .collect(); for index in table_create.get_indexes().iter() { if index.is_unique_key() { let col_names = index.get_index_spec().get_column_names(); if col_names.len() > 1 { if let Some(mut key_name) = index.get_index_spec().get_name() { if let Some((_, suffix)) = key_name.rsplit_once('-') { key_name = suffix; } for col_name in col_names { for column in columns.iter_mut() { if column.name == col_name { column.unique_key = Some(key_name.to_owned()); } } } } } } } let mut ref_table_counts: BTreeMap = BTreeMap::new(); let relations: Vec = table_create .get_foreign_key_create_stmts() .iter() .map(|fk_create_stmt| fk_create_stmt.get_foreign_key()) .map(|tbl_fk| { let ref_tbl = tbl_fk.get_ref_table().unwrap().sea_orm_table().to_string(); if let Some(count) = ref_table_counts.get_mut(&ref_tbl) { if *count == 0 { *count = 1; } *count += 1; } else { ref_table_counts.insert(ref_tbl, 0); }; tbl_fk.into() }) .collect::>() .into_iter() .rev() .map(|mut rel: Relation| { rel.self_referencing = rel.ref_table == table_name; if let Some(count) = ref_table_counts.get_mut(&rel.ref_table) { rel.num_suffix = *count; if *count > 0 { *count -= 1; } } rel }) .rev() .collect(); primary_keys.extend( table_create .get_indexes() .iter() .filter(|index| index.is_primary_key()) .flat_map(|index| { index .get_index_spec() .get_column_names() .into_iter() .map(|name| PrimaryKey { name }) .collect::>() }), ); let entity = Entity { table_name: table_name.clone(), columns, relations: relations.clone(), conjunct_relations: vec![], primary_keys, }; entities.insert(table_name.clone(), entity.clone()); for mut rel in relations.into_iter() { // This will produce a duplicated relation if rel.self_referencing { continue; } // This will cause compile error on the many side, // got relation variant but without Related implemented if rel.num_suffix > 0 { continue; } let ref_table = rel.ref_table; let mut unique = true; for column in rel.columns.iter() { if !entity .columns .iter() .filter(|col| col.unique) .any(|col| col.name.as_str() == column) { unique = false; break; } } if rel.columns.len() == entity.primary_keys.len() { let mut count_pk = 0; for primary_key in entity.primary_keys.iter() { if rel.columns.contains(&primary_key.name) { count_pk += 1; } } if count_pk == entity.primary_keys.len() { unique = true; } } let rel_type = if unique { RelationType::HasOne } else { RelationType::HasMany }; rel.rel_type = rel_type; rel.ref_table = table_name.to_string(); rel.columns = Vec::new(); rel.ref_columns = Vec::new(); if let Some(vec) = inverse_relations.get_mut(&ref_table) { vec.push(rel); } else { inverse_relations.insert(ref_table, vec![rel]); } } } for (tbl_name, relations) in inverse_relations.into_iter() { if let Some(entity) = entities.get_mut(&tbl_name) { for relation in relations.into_iter() { let duplicate_relation = entity .relations .iter() .any(|rel| rel.ref_table == relation.ref_table); if !duplicate_relation { entity.relations.push(relation); } } } } // When codegen is fed with a subset of tables (e.g. via `sea-orm-cli generate entity --tables`), // we must not generate relations that point to entities outside this set, otherwise it will // produce invalid paths like `super::::Entity`. let table_names: HashSet = entities.keys().cloned().collect(); for entity in entities.values_mut() { entity .relations .retain(|rel| rel.self_referencing || table_names.contains(&rel.ref_table)); } for table_name in entities.clone().keys() { let relations = match entities.get(table_name) { Some(entity) => { let is_conjunct_relation = entity.relations.len() == 2 && entity.primary_keys.len() == 2; if !is_conjunct_relation { continue; } entity.relations.clone() } None => unreachable!(), }; for (i, rel) in relations.iter().enumerate() { let another_rel = relations.get((i == 0) as usize).unwrap(); if let Some(entity) = entities.get_mut(&rel.ref_table) { let conjunct_relation = ConjunctRelation { via: table_name.clone(), to: another_rel.ref_table.clone(), }; entity.conjunct_relations.push(conjunct_relation); } } } Ok(EntityWriter { entities: entities .into_values() .map(|mut v| { // Filter duplicated conjunct relations let duplicated_to: Vec<_> = v .conjunct_relations .iter() .fold(HashMap::new(), |mut acc, conjunct_relation| { acc.entry(conjunct_relation.to.clone()) .and_modify(|c| *c += 1) .or_insert(1); acc }) .into_iter() .filter(|(_, v)| v > &1) .map(|(k, _)| k) .collect(); v.conjunct_relations .retain(|conjunct_relation| !duplicated_to.contains(&conjunct_relation.to)); // Skip `impl Related ... { fn to() ... }` implementation block, // if the same related entity is being referenced by a conjunct relation v.relations.iter_mut().for_each(|relation| { if v.conjunct_relations .iter() .any(|conjunct_relation| conjunct_relation.to == relation.ref_table) { relation.impl_related = false; } }); // Sort relation vectors v.relations.sort_by(|a, b| a.ref_table.cmp(&b.ref_table)); v.conjunct_relations.sort_by(|a, b| a.to.cmp(&b.to)); v }) .collect(), enums, }) } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; use proc_macro2::TokenStream; use sea_orm::{DbBackend, Schema}; use sea_query::{ColumnDef, ForeignKey, Table}; use std::{ error::Error, io::{self, BufRead, BufReader}, }; #[test] fn duplicated_many_to_many_paths() -> Result<(), Box> { use crate::tests_cfg::duplicated_many_to_many_paths::*; let schema = Schema::new(DbBackend::Postgres); validate_compact_entities( vec![ schema.create_table_from_entity(bills::Entity), schema.create_table_from_entity(users::Entity), schema.create_table_from_entity(users_saved_bills::Entity), schema.create_table_from_entity(users_votes::Entity), ], vec![ ( "bills", include_str!("../tests_cfg/duplicated_many_to_many_paths/bills.rs"), ), ( "users", include_str!("../tests_cfg/duplicated_many_to_many_paths/users.rs"), ), ( "users_saved_bills", include_str!("../tests_cfg/duplicated_many_to_many_paths/users_saved_bills.rs"), ), ( "users_votes", include_str!("../tests_cfg/duplicated_many_to_many_paths/users_votes.rs"), ), ], ) } #[test] fn many_to_many() -> Result<(), Box> { use crate::tests_cfg::many_to_many::*; let schema = Schema::new(DbBackend::Postgres); validate_compact_entities( vec![ schema.create_table_from_entity(bills::Entity), schema.create_table_from_entity(users::Entity), schema.create_table_from_entity(users_votes::Entity), ], vec![ ("bills", include_str!("../tests_cfg/many_to_many/bills.rs")), ("users", include_str!("../tests_cfg/many_to_many/users.rs")), ( "users_votes", include_str!("../tests_cfg/many_to_many/users_votes.rs"), ), ], ) } #[test] fn many_to_many_multiple() -> Result<(), Box> { use crate::tests_cfg::many_to_many_multiple::*; let schema = Schema::new(DbBackend::Postgres); validate_compact_entities( vec![ schema.create_table_from_entity(bills::Entity), schema.create_table_from_entity(users::Entity), schema.create_table_from_entity(users_votes::Entity), ], vec![ ( "bills", include_str!("../tests_cfg/many_to_many_multiple/bills.rs"), ), ( "users", include_str!("../tests_cfg/many_to_many_multiple/users.rs"), ), ( "users_votes", include_str!("../tests_cfg/many_to_many_multiple/users_votes.rs"), ), ], ) } #[test] fn self_referencing() -> Result<(), Box> { use crate::tests_cfg::self_referencing::*; let schema = Schema::new(DbBackend::Postgres); validate_compact_entities( vec![ schema.create_table_from_entity(bills::Entity), schema.create_table_from_entity(users::Entity), ], vec![ ( "bills", include_str!("../tests_cfg/self_referencing/bills.rs"), ), ( "users", include_str!("../tests_cfg/self_referencing/users.rs"), ), ], ) } #[test] fn test_indexes_transform() -> Result<(), Box> { let schema = Schema::new(DbBackend::Postgres); validate_compact_entities( vec![ schema.create_table_with_index_from_entity( crate::tests_cfg::compact::indexes::Entity, ), ], vec![("indexes", include_str!("../tests_cfg/compact/indexes.rs"))], )?; validate_dense_entities( vec![ schema .create_table_with_index_from_entity(crate::tests_cfg::dense::indexes::Entity), ], vec![("indexes", include_str!("../tests_cfg/dense/indexes.rs"))], )?; Ok(()) } #[test] fn filter_relations_to_missing_entities() -> Result<(), Box> { let parent_stmt = || { Table::create() .table("parent") .col( ColumnDef::new("id") .integer() .not_null() .auto_increment() .primary_key(), ) .to_owned() }; let child_stmt = || { Table::create() .table("child") .col( ColumnDef::new("id") .integer() .not_null() .auto_increment() .primary_key(), ) .col(ColumnDef::new("parent_id").integer().not_null()) .foreign_key( ForeignKey::create() .name("fk-child-parent_id") .from("child", "parent_id") .to("parent", "id"), ) .to_owned() }; let entities: HashMap<_, _> = EntityTransformer::transform(vec![parent_stmt(), child_stmt()])? .entities .into_iter() .map(|entity| (entity.table_name.clone(), entity)) .collect(); let child = entities.get("child").expect("missing entity `child`"); assert_eq!(child.relations.len(), 1); assert_eq!(child.relations[0].ref_table, "parent"); let entities: HashMap<_, _> = EntityTransformer::transform(vec![child_stmt()])? .entities .into_iter() .map(|entity| (entity.table_name.clone(), entity)) .collect(); let child = entities.get("child").expect("missing entity `child`"); assert!(child.relations.is_empty()); Ok(()) } #[test] fn filter_conjunct_relations_to_missing_entities() -> Result<(), Box> { let user_stmt = || { Table::create() .table("user") .col( ColumnDef::new("id") .integer() .not_null() .auto_increment() .primary_key(), ) .to_owned() }; let role_stmt = || { Table::create() .table("role") .col( ColumnDef::new("id") .integer() .not_null() .auto_increment() .primary_key(), ) .to_owned() }; let user_role_stmt = || { Table::create() .table("user_role") .col(ColumnDef::new("user_id").integer().not_null().primary_key()) .col(ColumnDef::new("role_id").integer().not_null().primary_key()) .foreign_key( ForeignKey::create() .name("fk-user_role-user_id") .from("user_role", "user_id") .to("user", "id"), ) .foreign_key( ForeignKey::create() .name("fk-user_role-role_id") .from("user_role", "role_id") .to("role", "id"), ) .to_owned() }; let entities: HashMap<_, _> = EntityTransformer::transform(vec![user_stmt(), role_stmt(), user_role_stmt()])? .entities .into_iter() .map(|entity| (entity.table_name.clone(), entity)) .collect(); let user = entities.get("user").expect("missing entity `user`"); assert!(user.conjunct_relations.iter().any(|conjunct_relation| { conjunct_relation.via == "user_role" && conjunct_relation.to == "role" })); let entities: HashMap<_, _> = EntityTransformer::transform(vec![user_stmt(), user_role_stmt()])? .entities .into_iter() .map(|entity| (entity.table_name.clone(), entity)) .collect(); let user = entities.get("user").expect("missing entity `user`"); assert!(user.conjunct_relations.is_empty()); let user_role = entities .get("user_role") .expect("missing entity `user_role`"); assert_eq!(user_role.relations.len(), 1); assert_eq!(user_role.relations[0].ref_table, "user"); Ok(()) } macro_rules! validate_entities_fn { ($fn_name: ident, $method: ident) => { fn $fn_name( table_create_stmts: Vec, files: Vec<(&str, &str)>, ) -> Result<(), Box> { let entities: HashMap<_, _> = EntityTransformer::transform(table_create_stmts)? .entities .into_iter() .map(|entity| (entity.table_name.clone(), entity)) .collect(); for (entity_name, file_content) in files { let entity = entities .get(entity_name) .expect("Forget to add entity to the list"); assert_eq!( parse_from_file(file_content.as_bytes())?.to_string(), EntityWriter::$method( entity, &crate::WithSerde::None, &Default::default(), &None, false, false, &Default::default(), &Default::default(), &Default::default(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); } Ok(()) } }; } validate_entities_fn!(validate_compact_entities, gen_compact_code_blocks); validate_entities_fn!(validate_dense_entities, gen_dense_code_blocks); fn parse_from_file(inner: R) -> io::Result where R: io::Read, { let mut reader = BufReader::new(inner); let mut lines: Vec = Vec::new(); reader.read_until(b';', &mut Vec::new())?; let mut line = String::new(); while reader.read_line(&mut line)? > 0 { lines.push(line.to_owned()); line.clear(); } let content = lines.join(""); Ok(content.parse().unwrap()) } } ================================================ FILE: sea-orm-codegen/src/entity/writer/compact.rs ================================================ use super::*; impl EntityWriter { #[allow(clippy::too_many_arguments)] pub fn gen_compact_code_blocks( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, schema_name: &Option, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, _column_extra_derives: &TokenStream, seaography: bool, impl_active_model_behavior: bool, ) -> Vec { let mut imports = Self::gen_import(with_serde); imports.extend(Self::gen_import_active_enum(entity)); let mut code_blocks = vec![ imports, Self::gen_compact_model_struct( entity, with_serde, column_option, schema_name, serde_skip_deserializing_primary_key, serde_skip_hidden_column, model_extra_derives, model_extra_attributes, ), Self::gen_compact_relation_enum(entity), ]; code_blocks.extend(Self::gen_impl_related(entity)); code_blocks.extend(Self::gen_impl_conjunct_related(entity)); if impl_active_model_behavior { code_blocks.extend([Self::impl_active_model_behavior()]); } if seaography { code_blocks.extend([Self::gen_related_entity(entity)]); } code_blocks } #[allow(clippy::too_many_arguments)] pub fn gen_compact_model_struct( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, schema_name: &Option, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, ) -> TokenStream { let table_name = entity.table_name.as_str(); let column_names_snake_case = entity.get_column_names_snake_case(); let column_rs_types = entity.get_column_rs_types(column_option); let if_eq_needed = entity.get_eq_needed(); let primary_keys: Vec = entity .primary_keys .iter() .map(|pk| pk.name.clone()) .collect(); let attrs: Vec = entity .columns .iter() .map(|col| { let mut attrs: Punctuated<_, Comma> = Punctuated::new(); let is_primary_key = primary_keys.contains(&col.name); if !col.is_snake_case_name() { let column_name = &col.name; attrs.push(quote! { column_name = #column_name }); } if is_primary_key { attrs.push(quote! { primary_key }); if !col.auto_increment { attrs.push(quote! { auto_increment = false }); } } if let Some(ts) = col.get_col_type_attrs() { attrs.extend([ts]); if !col.not_null { attrs.push(quote! { nullable }); } }; if col.unique { attrs.push(quote! { unique }); } else if let Some(unique_key) = &col.unique_key { attrs.push(quote! { unique_key = #unique_key }); } let mut ts = quote! {}; if !attrs.is_empty() { for (i, attr) in attrs.into_iter().enumerate() { if i > 0 { ts = quote! { #ts, }; } ts = quote! { #ts #attr }; } ts = quote! { #[sea_orm(#ts)] }; } let serde_attribute = col.get_serde_attribute( is_primary_key, serde_skip_deserializing_primary_key, serde_skip_hidden_column, ); ts = quote! { #ts #serde_attribute }; ts }) .collect(); let schema_name = match Self::gen_schema_name(schema_name) { Some(schema_name) => quote! { schema_name = #schema_name, }, None => quote! {}, }; let extra_derive = with_serde.extra_derive(); quote! { #[derive(Clone, Debug, PartialEq #if_eq_needed, DeriveEntityModel #extra_derive #model_extra_derives)] #[sea_orm( #schema_name table_name = #table_name )] #model_extra_attributes pub struct Model { #( #attrs pub #column_names_snake_case: #column_rs_types, )* } } } pub fn gen_compact_relation_enum(entity: &Entity) -> TokenStream { let attrs = entity.get_relation_attrs(); let relation_enum_name = entity.get_relation_enum_name(); quote! { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #( #attrs #relation_enum_name, )* } } } } ================================================ FILE: sea-orm-codegen/src/entity/writer/dense.rs ================================================ use super::*; use crate::{Relation, RelationType}; use heck::ToSnakeCase; impl EntityWriter { #[allow(clippy::too_many_arguments)] pub fn gen_dense_code_blocks( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, schema_name: &Option, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, _column_extra_derives: &TokenStream, _seaography: bool, impl_active_model_behavior: bool, ) -> Vec { let mut imports = Self::gen_import(with_serde); imports.extend(Self::gen_import_active_enum(entity)); let mut code_blocks = vec![ imports, Self::gen_dense_model_struct( entity, with_serde, column_option, schema_name, serde_skip_deserializing_primary_key, serde_skip_hidden_column, model_extra_derives, model_extra_attributes, ), ]; if impl_active_model_behavior { code_blocks.push(Self::impl_active_model_behavior()); } code_blocks } #[allow(clippy::too_many_arguments)] pub fn gen_dense_model_struct( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, schema_name: &Option, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, ) -> TokenStream { let table_name = entity.table_name.as_str(); let column_names_snake_case = entity.get_column_names_snake_case(); let column_rs_types = entity.get_column_rs_types(column_option); let if_eq_needed = entity.get_eq_needed(); let primary_keys: Vec = entity .primary_keys .iter() .map(|pk| pk.name.clone()) .collect(); let attrs: Vec = entity .columns .iter() .map(|col| { let mut attrs: Punctuated<_, Comma> = Punctuated::new(); let is_primary_key = primary_keys.contains(&col.name); if !col.is_snake_case_name() { let column_name = &col.name; attrs.push(quote! { column_name = #column_name }); } if is_primary_key { attrs.push(quote! { primary_key }); if !col.auto_increment { attrs.push(quote! { auto_increment = false }); } } if let Some(ts) = col.get_col_type_attrs() { attrs.extend([ts]); if !col.not_null { attrs.push(quote! { nullable }); } }; if col.unique { attrs.push(quote! { unique }); } else if let Some(unique_key) = &col.unique_key { attrs.push(quote! { unique_key = #unique_key }); } let mut ts = quote! {}; if !attrs.is_empty() { for (i, attr) in attrs.into_iter().enumerate() { if i > 0 { ts = quote! { #ts, }; } ts = quote! { #ts #attr }; } ts = quote! { #[sea_orm(#ts)] }; } let serde_attribute = col.get_serde_attribute( is_primary_key, serde_skip_deserializing_primary_key, serde_skip_hidden_column, ); ts = quote! { #ts #serde_attribute }; ts }) .collect(); let schema_name = match Self::gen_schema_name(schema_name) { Some(schema_name) => quote! { schema_name = #schema_name, }, None => quote! {}, }; let extra_derive = with_serde.extra_derive(); let mut compound_objects: Punctuated<_, Comma> = Punctuated::new(); let map_col = |a: &syn::Ident| -> String { let a = a.to_string(); let b = a.to_snake_case(); if a != b.to_upper_camel_case() { // if roundtrip fails, use original a } else { b } }; let map_punctuated = |punctuated: Vec| -> String { let len = punctuated.len(); let punctuated = punctuated.join(", "); match len { 0..=1 => punctuated, _ => format!("({punctuated})"), } }; let via_entities = entity.get_conjunct_relations_via_snake_case(); for rel in entity.relations.iter() { if !rel.self_referencing && rel.impl_related { let (rel_type, sea_orm_attr) = match rel.rel_type { RelationType::HasOne => (format_ident!("HasOne"), quote!(#[sea_orm(has_one)])), RelationType::HasMany => { (format_ident!("HasMany"), quote!(#[sea_orm(has_many)])) } RelationType::BelongsTo => { let (from, to) = rel.get_src_ref_columns(map_col, map_col, map_punctuated); let on_update = if let Some(action) = &rel.on_update { let action = Relation::get_foreign_key_action(action); quote!(, on_update = #action) } else { quote!() }; let on_delete = if let Some(action) = &rel.on_delete { let action = Relation::get_foreign_key_action(action); quote!(, on_delete = #action) } else { quote!() }; let relation_enum = if rel.num_suffix > 0 { let relation_enum = rel.get_enum_name().to_string(); quote!(relation_enum = #relation_enum,) } else { quote!() }; ( format_ident!("HasOne"), quote!(#[sea_orm(belongs_to, #relation_enum from = #from, to = #to #on_update #on_delete)]), ) } }; if let Some(to_entity) = rel.get_module_name() { if !via_entities.contains(&to_entity) { // skip junctions let field = if matches!(rel.rel_type, RelationType::HasMany) { format_ident!( "{}", pluralizer::pluralize(&to_entity.to_string(), 2, false) ) } else { to_entity.clone() }; let field = if rel.num_suffix == 0 { field } else { format_ident!("{field}_{}", rel.num_suffix) }; compound_objects.push(quote! { #sea_orm_attr pub #field: #rel_type }); } } } else if rel.self_referencing { let (from, to) = rel.get_src_ref_columns(map_col, map_col, map_punctuated); let on_update = if let Some(action) = &rel.on_update { let action = Relation::get_foreign_key_action(action); quote!(, on_update = #action) } else { quote!() }; let on_delete = if let Some(action) = &rel.on_delete { let action = Relation::get_foreign_key_action(action); quote!(, on_delete = #action) } else { quote!() }; let relation_enum = rel.get_enum_name().to_string(); let field = format_ident!( "{}{}", entity.get_table_name_snake_case_ident(), if rel.num_suffix > 0 { format!("_{}", rel.num_suffix) } else { "".into() } ); compound_objects.push(quote! { #[sea_orm(self_ref, relation_enum = #relation_enum, from = #from, to = #to #on_update #on_delete)] pub #field: HasOne }); } } for (to_entity, via_entity) in entity .get_conjunct_relations_to_snake_case() .into_iter() .zip(via_entities) { let field = format_ident!( "{}", pluralizer::pluralize(&to_entity.to_string(), 2, false) ); let via_entity = via_entity.to_string(); compound_objects.push(quote! { #[sea_orm(has_many, via = #via_entity)] pub #field: HasMany }); } if !compound_objects.is_empty() { compound_objects.push_punct(::default()); } quote! { #[sea_orm::model] #[derive(Clone, Debug, PartialEq #if_eq_needed, DeriveEntityModel #extra_derive #model_extra_derives)] #[sea_orm( #schema_name table_name = #table_name )] #model_extra_attributes pub struct Model { #( #attrs pub #column_names_snake_case: #column_rs_types, )* #compound_objects } } } #[allow(dead_code)] fn gen_dense_related_entity(entity: &Entity) -> TokenStream { let via_entities = entity.get_conjunct_relations_via_snake_case(); let related_modules = entity.get_related_entity_modules(); let related_attrs = entity.get_related_entity_attrs(); let related_enum_names = entity.get_related_entity_enum_name(); let items = related_modules .into_iter() .zip(related_attrs) .zip(related_enum_names) .filter_map(|((related_module, related_attr), related_enum_name)| { if !via_entities.contains(&related_module) { // skip junctions Some(quote!(#related_attr #related_enum_name)) } else { None } }) .collect::>(); quote! { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity { #(#items),* } } } } #[cfg(test)] mod test { #[test] #[ignore] fn test_name() { panic!("{}", pluralizer::pluralize("filling", 2, false)); } } ================================================ FILE: sea-orm-codegen/src/entity/writer/expanded.rs ================================================ use super::*; impl EntityWriter { #[allow(clippy::too_many_arguments)] pub fn gen_expanded_code_blocks( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, schema_name: &Option, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, column_extra_derives: &TokenStream, seaography: bool, impl_active_model_behavior: bool, ) -> Vec { let mut imports = Self::gen_import(with_serde); imports.extend(Self::gen_import_active_enum(entity)); let mut code_blocks = vec![ imports, Self::gen_entity_struct(), Self::gen_impl_entity_name(entity, schema_name), Self::gen_expanded_model_struct( entity, with_serde, column_option, serde_skip_deserializing_primary_key, serde_skip_hidden_column, model_extra_derives, model_extra_attributes, ), Self::gen_column_enum(entity, column_extra_derives), Self::gen_primary_key_enum(entity), Self::gen_impl_primary_key(entity, column_option), Self::gen_relation_enum(entity), Self::gen_impl_column_trait(entity), Self::gen_impl_relation_trait(entity), ]; code_blocks.extend(Self::gen_impl_related(entity)); code_blocks.extend(Self::gen_impl_conjunct_related(entity)); if impl_active_model_behavior { code_blocks.extend([Self::impl_active_model_behavior()]); } if seaography { code_blocks.extend([Self::gen_related_entity(entity)]); } code_blocks } pub fn gen_expanded_model_struct( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, ) -> TokenStream { let column_names_snake_case = entity.get_column_names_snake_case(); let column_rs_types = entity.get_column_rs_types(column_option); let if_eq_needed = entity.get_eq_needed(); let serde_attributes = entity.get_column_serde_attributes( serde_skip_deserializing_primary_key, serde_skip_hidden_column, ); let extra_derive = with_serde.extra_derive(); quote! { #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel #if_eq_needed #extra_derive #model_extra_derives)] #model_extra_attributes pub struct Model { #( #serde_attributes pub #column_names_snake_case: #column_rs_types, )* } } } } ================================================ FILE: sea-orm-codegen/src/entity/writer/frontend.rs ================================================ use super::*; impl EntityWriter { #[allow(clippy::too_many_arguments)] pub fn gen_frontend_code_blocks( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, schema_name: &Option, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, _column_extra_derives: &TokenStream, _seaography: bool, _impl_active_model_behavior: bool, ) -> Vec { let mut imports = Self::gen_import_serde(with_serde); imports.extend(Self::gen_import_active_enum(entity)); let code_blocks = vec![ imports, Self::gen_frontend_model_struct( entity, with_serde, column_option, schema_name, serde_skip_deserializing_primary_key, serde_skip_hidden_column, model_extra_derives, model_extra_attributes, ), ]; code_blocks } #[allow(clippy::too_many_arguments)] pub fn gen_frontend_model_struct( entity: &Entity, with_serde: &WithSerde, column_option: &ColumnOption, _schema_name: &Option, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, ) -> TokenStream { let column_names_snake_case = entity.get_column_names_snake_case(); let column_rs_types = entity.get_column_rs_types(column_option); let if_eq_needed = entity.get_eq_needed(); let primary_keys: Vec = entity .primary_keys .iter() .map(|pk| pk.name.clone()) .collect(); let attrs: Vec = entity .columns .iter() .map(|col| { let is_primary_key = primary_keys.contains(&col.name); col.get_serde_attribute( is_primary_key, serde_skip_deserializing_primary_key, serde_skip_hidden_column, ) }) .collect(); let extra_derive = with_serde.extra_derive(); quote! { #[derive(Clone, Debug, PartialEq #if_eq_needed #extra_derive #model_extra_derives)] #model_extra_attributes pub struct Model { #( #attrs pub #column_names_snake_case: #column_rs_types, )* } } } } ================================================ FILE: sea-orm-codegen/src/entity/writer/mermaid.rs ================================================ use std::collections::{BTreeSet, HashSet}; use std::fmt::Write; use sea_query::ColumnType; use crate::{Entity, RelationType}; use super::EntityWriter; impl EntityWriter { pub fn generate_er_diagram(&self) -> String { let mut out = String::from("erDiagram\n"); let pk_sets: Vec> = self .entities .iter() .map(|e| e.primary_keys.iter().map(|pk| pk.name.as_str()).collect()) .collect(); let fk_sets: Vec> = self .entities .iter() .map(|e| { e.relations .iter() .filter(|r| matches!(r.rel_type, RelationType::BelongsTo)) .flat_map(|r| r.columns.iter().map(String::as_str)) .collect() }) .collect(); for (i, entity) in self.entities.iter().enumerate() { write_entity_block(&mut out, entity, &pk_sets[i], &fk_sets[i]); } let mut emitted: BTreeSet = BTreeSet::new(); for entity in &self.entities { write_relations(&mut out, entity, &mut emitted); } out } } fn write_entity_block(out: &mut String, entity: &Entity, pks: &HashSet<&str>, fks: &HashSet<&str>) { let _ = writeln!(out, " {} {{", entity.table_name); for col in &entity.columns { let type_name = col_type_name(&col.col_type); let is_pk = pks.contains(col.name.as_str()); let is_fk = fks.contains(col.name.as_str()); let is_uk = col.unique || col.unique_key.is_some(); let constraint = match (is_pk, is_fk, is_uk) { (true, true, _) => " PK,FK", (true, false, _) => " PK", (false, true, true) => " FK,UK", (false, true, false) => " FK", (false, false, true) => " UK", (false, false, false) => "", }; let _ = writeln!(out, " {} {}{}", type_name, col.name, constraint); } let _ = writeln!(out, " }}"); } fn write_relations(out: &mut String, entity: &Entity, emitted: &mut BTreeSet) { for rel in &entity.relations { let (left, right, cardinality, label) = match rel.rel_type { RelationType::BelongsTo => ( &entity.table_name, &rel.ref_table, "}o--||", rel.columns.join(", "), ), RelationType::HasOne => continue, RelationType::HasMany => continue, }; let key = format!("{left} {cardinality} {right} : \"{label}\""); if emitted.insert(key.clone()) { let _ = writeln!(out, " {key}"); } } for conj in &entity.conjunct_relations { let left = &entity.table_name; let right = &conj.to; let label = format!("[{}]", conj.via); let key = if left <= right { format!("{left} }}o--o{{ {right} : \"{label}\"") } else { format!("{right} }}o--o{{ {left} : \"{label}\"") }; if emitted.insert(key.clone()) { let _ = writeln!(out, " {key}"); } } } fn col_type_name(col_type: &ColumnType) -> &str { #[allow(unreachable_patterns)] match col_type { ColumnType::Char(_) => "char", ColumnType::String(_) => "varchar", ColumnType::Text => "text", ColumnType::TinyInteger => "tinyint", ColumnType::SmallInteger => "smallint", ColumnType::Integer => "int", ColumnType::BigInteger => "bigint", ColumnType::TinyUnsigned => "tinyint_unsigned", ColumnType::SmallUnsigned => "smallint_unsigned", ColumnType::Unsigned => "int_unsigned", ColumnType::BigUnsigned => "bigint_unsigned", ColumnType::Float => "float", ColumnType::Double => "double", ColumnType::Decimal(_) => "decimal", ColumnType::Money(_) => "money", ColumnType::DateTime => "datetime", ColumnType::Timestamp => "timestamp", ColumnType::TimestampWithTimeZone => "timestamptz", ColumnType::Time => "time", ColumnType::Date => "date", ColumnType::Year => "year", ColumnType::Binary(_) | ColumnType::VarBinary(_) | ColumnType::Blob => "blob", ColumnType::Boolean => "bool", ColumnType::Json | ColumnType::JsonBinary => "json", ColumnType::Uuid => "uuid", ColumnType::Enum { .. } => "enum", ColumnType::Array(_) => "array", ColumnType::Vector(_) => "vector", ColumnType::Bit(_) | ColumnType::VarBit(_) => "bit", ColumnType::Cidr => "cidr", ColumnType::Inet => "inet", ColumnType::MacAddr => "macaddr", ColumnType::LTree => "ltree", ColumnType::Interval(_, _) => "interval", ColumnType::Custom(_) => "custom", _ => "unknown", } } #[cfg(test)] mod tests { use std::collections::BTreeMap; use sea_query::{ColumnType, StringLen}; use crate::{ Column, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation, RelationType, }; fn setup_blog_schema() -> EntityWriter { EntityWriter { entities: vec![ Entity { table_name: "user".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::String(StringLen::N(255)), auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "email".to_owned(), col_type: ColumnType::String(StringLen::N(255)), auto_increment: false, not_null: true, unique: true, unique_key: None, }, Column { name: "parent_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![ Relation { ref_table: "post".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasMany, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "user".to_owned(), columns: vec!["parent_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: true, num_suffix: 0, impl_related: true, }, ], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "post".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "title".to_owned(), col_type: ColumnType::Text, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "user_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "user".to_owned(), columns: vec!["user_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![ConjunctRelation { via: "post_tag".to_owned(), to: "tag".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "tag".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::String(StringLen::N(100)), auto_increment: false, not_null: true, unique: true, unique_key: None, }, ], relations: vec![], conjunct_relations: vec![ConjunctRelation { via: "post_tag".to_owned(), to: "post".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "post_tag".to_owned(), columns: vec![ Column { name: "post_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "tag_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![ Relation { ref_table: "post".to_owned(), columns: vec!["post_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "tag".to_owned(), columns: vec!["tag_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, ], conjunct_relations: vec![], primary_keys: vec![ PrimaryKey { name: "post_id".to_owned(), }, PrimaryKey { name: "tag_id".to_owned(), }, ], }, ], enums: BTreeMap::new(), } } #[test] fn test_generate_er_diagram() { let writer = setup_blog_schema(); let diagram = writer.generate_er_diagram(); let expected = r#"erDiagram user { int id PK varchar name varchar email UK int parent_id FK } post { int id PK text title int user_id FK } tag { int id PK varchar name UK } post_tag { int post_id PK,FK int tag_id PK,FK } user }o--|| user : "parent_id" post }o--|| user : "user_id" post }o--o{ tag : "[post_tag]" post_tag }o--|| post : "post_id" post_tag }o--|| tag : "tag_id" "#; assert_eq!(diagram, expected); } #[test] fn test_er_diagram_deduplicates_m2m() { let writer = setup_blog_schema(); let diagram = writer.generate_er_diagram(); let m2m_count = diagram.matches("}o--o{").count(); assert_eq!(m2m_count, 1, "M-N relation should appear only once"); } } ================================================ FILE: sea-orm-codegen/src/entity/writer.rs ================================================ use crate::{ActiveEnum, ColumnOption, Entity, util::escape_rust_keyword}; use heck::ToUpperCamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::{collections::BTreeMap, str::FromStr}; use syn::{punctuated::Punctuated, token::Comma}; use tracing::info; mod compact; mod dense; mod expanded; mod frontend; mod mermaid; #[derive(Clone, Debug)] pub struct EntityWriter { pub(crate) entities: Vec, pub(crate) enums: BTreeMap, } pub struct WriterOutput { pub files: Vec, } pub struct OutputFile { pub name: String, pub content: String, } #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] pub enum WithPrelude { #[default] All, None, AllAllowUnusedImports, } #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] pub enum WithSerde { #[default] None, Serialize, Deserialize, Both, } #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] pub enum DateTimeCrate { #[default] Chrono, Time, } #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] pub enum BigIntegerType { #[default] I64, I32, } #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] pub enum EntityFormat { #[default] Compact, Expanded, Frontend, Dense, } #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] pub enum BannerVersion { Off, Major, #[default] Minor, Patch, } #[derive(Debug)] pub struct EntityWriterContext { pub(crate) entity_format: EntityFormat, pub(crate) with_prelude: WithPrelude, pub(crate) with_serde: WithSerde, pub(crate) with_copy_enums: bool, pub(crate) date_time_crate: DateTimeCrate, pub(crate) big_integer_type: BigIntegerType, pub(crate) schema_name: Option, pub(crate) lib: bool, pub(crate) serde_skip_hidden_column: bool, pub(crate) serde_skip_deserializing_primary_key: bool, pub(crate) model_extra_derives: TokenStream, pub(crate) model_extra_attributes: TokenStream, pub(crate) enum_extra_derives: TokenStream, pub(crate) enum_extra_attributes: TokenStream, pub(crate) column_extra_derives: TokenStream, pub(crate) seaography: bool, pub(crate) impl_active_model_behavior: bool, pub(crate) banner_version: BannerVersion, } impl WithSerde { pub fn extra_derive(&self) -> TokenStream { let mut extra_derive = match self { Self::None => { quote! {} } Self::Serialize => { quote! { Serialize } } Self::Deserialize => { quote! { Deserialize } } Self::Both => { quote! { Serialize, Deserialize } } }; if !extra_derive.is_empty() { extra_derive = quote! { , #extra_derive } } extra_derive } } /// Converts *_extra_derives argument to token stream pub(crate) fn bonus_derive(extra_derives: I) -> TokenStream where T: Into, I: IntoIterator, { extra_derives.into_iter().map(Into::::into).fold( TokenStream::default(), |acc, derive| { let tokens: TokenStream = derive.parse().unwrap(); quote! { #acc, #tokens } }, ) } /// convert *_extra_attributes argument to token stream pub(crate) fn bonus_attributes(attributes: I) -> TokenStream where T: Into, I: IntoIterator, { attributes.into_iter().map(Into::::into).fold( TokenStream::default(), |acc, attribute| { let tokens: TokenStream = attribute.parse().unwrap(); quote! { #acc #[#tokens] } }, ) } impl FromStr for WithPrelude { type Err = crate::Error; fn from_str(s: &str) -> Result { Ok(match s { "none" => Self::None, "all-allow-unused-imports" => Self::AllAllowUnusedImports, "all" => Self::All, v => { return Err(crate::Error::TransformError(format!( "Unsupported enum variant '{v}'" ))); } }) } } impl FromStr for EntityFormat { type Err = crate::Error; fn from_str(s: &str) -> Result { Ok(match s { "compact" => Self::Compact, "expanded" => Self::Expanded, "frontend" => Self::Frontend, "dense" => Self::Dense, v => { return Err(crate::Error::TransformError(format!( "Unsupported enum variant '{v}'" ))); } }) } } impl FromStr for WithSerde { type Err = crate::Error; fn from_str(s: &str) -> Result { Ok(match s { "none" => Self::None, "serialize" => Self::Serialize, "deserialize" => Self::Deserialize, "both" => Self::Both, v => { return Err(crate::Error::TransformError(format!( "Unsupported enum variant '{v}'" ))); } }) } } impl EntityWriterContext { #[allow(clippy::too_many_arguments)] pub fn new( entity_format: EntityFormat, with_prelude: WithPrelude, with_serde: WithSerde, with_copy_enums: bool, date_time_crate: DateTimeCrate, big_integer_type: BigIntegerType, schema_name: Option, lib: bool, serde_skip_deserializing_primary_key: bool, serde_skip_hidden_column: bool, model_extra_derives: Vec, model_extra_attributes: Vec, enum_extra_derives: Vec, enum_extra_attributes: Vec, column_extra_derives: Vec, seaography: bool, impl_active_model_behavior: bool, banner_version: BannerVersion, ) -> Self { Self { entity_format, with_prelude, with_serde, with_copy_enums, date_time_crate, big_integer_type, schema_name, lib, serde_skip_deserializing_primary_key, serde_skip_hidden_column, model_extra_derives: bonus_derive(model_extra_derives), model_extra_attributes: bonus_attributes(model_extra_attributes), enum_extra_derives: bonus_derive(enum_extra_derives), enum_extra_attributes: bonus_attributes(enum_extra_attributes), column_extra_derives: bonus_derive(column_extra_derives), seaography, impl_active_model_behavior, banner_version, } } fn column_option(&self) -> ColumnOption { ColumnOption { date_time_crate: self.date_time_crate, big_integer_type: self.big_integer_type, } } } impl EntityWriter { pub fn generate(self, context: &EntityWriterContext) -> WriterOutput { let mut files = Vec::new(); files.extend(self.write_entities(context)); let with_prelude = context.with_prelude != WithPrelude::None; files.push(self.write_index_file( context.lib, with_prelude, context.seaography, context.banner_version, )); if with_prelude { files.push(self.write_prelude( context.with_prelude, context.entity_format, context.banner_version, )); } if !self.enums.is_empty() { files.push(self.write_sea_orm_active_enums( &context.with_serde, context.with_copy_enums, &context.enum_extra_derives, &context.enum_extra_attributes, context.entity_format, context.banner_version, )); } WriterOutput { files } } pub fn write_entities(&self, context: &EntityWriterContext) -> Vec { self.entities .iter() .map(|entity| { let entity_file = format!("{}.rs", entity.get_table_name_snake_case()); let column_info = entity .columns .iter() .map(|column| column.get_info(&context.column_option())) .collect::>(); // Serde must be enabled to use this let serde_skip_deserializing_primary_key = context .serde_skip_deserializing_primary_key && matches!(context.with_serde, WithSerde::Both | WithSerde::Deserialize); let serde_skip_hidden_column = context.serde_skip_hidden_column && matches!( context.with_serde, WithSerde::Both | WithSerde::Serialize | WithSerde::Deserialize ); info!("Generating {}", entity_file); for info in column_info.iter() { info!(" > {}", info); } let mut lines = Vec::new(); Self::write_doc_comment(&mut lines, context.banner_version); let code_blocks = if context.entity_format == EntityFormat::Frontend { Self::gen_frontend_code_blocks( entity, &context.with_serde, &context.column_option(), &context.schema_name, serde_skip_deserializing_primary_key, serde_skip_hidden_column, &context.model_extra_derives, &context.model_extra_attributes, &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, ) } else if context.entity_format == EntityFormat::Expanded { Self::gen_expanded_code_blocks( entity, &context.with_serde, &context.column_option(), &context.schema_name, serde_skip_deserializing_primary_key, serde_skip_hidden_column, &context.model_extra_derives, &context.model_extra_attributes, &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, ) } else if context.entity_format == EntityFormat::Dense { Self::gen_dense_code_blocks( entity, &context.with_serde, &context.column_option(), &context.schema_name, serde_skip_deserializing_primary_key, serde_skip_hidden_column, &context.model_extra_derives, &context.model_extra_attributes, &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, ) } else { Self::gen_compact_code_blocks( entity, &context.with_serde, &context.column_option(), &context.schema_name, serde_skip_deserializing_primary_key, serde_skip_hidden_column, &context.model_extra_derives, &context.model_extra_attributes, &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, ) }; Self::write(&mut lines, code_blocks); OutputFile { name: entity_file, content: lines.join("\n\n"), } }) .collect() } pub fn write_index_file( &self, lib: bool, prelude: bool, seaography: bool, banner_version: BannerVersion, ) -> OutputFile { let mut lines = Vec::new(); Self::write_doc_comment(&mut lines, banner_version); let code_blocks: Vec = self.entities.iter().map(Self::gen_mod).collect(); if prelude { Self::write( &mut lines, vec![quote! { pub mod prelude; }], ); lines.push("".to_owned()); } Self::write(&mut lines, code_blocks); if !self.enums.is_empty() { Self::write( &mut lines, vec![quote! { pub mod sea_orm_active_enums; }], ); } if seaography { lines.push("".to_owned()); let ts = Self::gen_seaography_entity_mod(&self.entities, &self.enums); Self::write(&mut lines, vec![ts]); } let file_name = match lib { true => "lib.rs".to_owned(), false => "mod.rs".to_owned(), }; OutputFile { name: file_name, content: lines.join("\n"), } } pub fn write_prelude( &self, with_prelude: WithPrelude, entity_format: EntityFormat, banner_version: BannerVersion, ) -> OutputFile { let mut lines = Vec::new(); Self::write_doc_comment(&mut lines, banner_version); if with_prelude == WithPrelude::AllAllowUnusedImports { Self::write_allow_unused_imports(&mut lines) } let code_blocks = self .entities .iter() .map({ if entity_format == EntityFormat::Frontend { Self::gen_prelude_use_model } else { Self::gen_prelude_use } }) .collect(); Self::write(&mut lines, code_blocks); OutputFile { name: "prelude.rs".to_owned(), content: lines.join("\n"), } } pub fn write_sea_orm_active_enums( &self, with_serde: &WithSerde, with_copy_enums: bool, extra_derives: &TokenStream, extra_attributes: &TokenStream, entity_format: EntityFormat, banner_version: BannerVersion, ) -> OutputFile { let mut lines = Vec::new(); Self::write_doc_comment(&mut lines, banner_version); if entity_format == EntityFormat::Frontend { Self::write(&mut lines, vec![Self::gen_import_serde(with_serde)]); } else { Self::write(&mut lines, vec![Self::gen_import(with_serde)]); } lines.push("".to_owned()); let code_blocks = self .enums .values() .map(|active_enum| { active_enum.impl_active_enum( with_serde, with_copy_enums, extra_derives, extra_attributes, entity_format, ) }) .collect(); Self::write(&mut lines, code_blocks); OutputFile { name: "sea_orm_active_enums.rs".to_owned(), content: lines.join("\n"), } } pub fn write(lines: &mut Vec, code_blocks: Vec) { lines.extend( code_blocks .into_iter() .map(|code_block| code_block.to_string()) .collect::>(), ); } pub fn write_doc_comment(lines: &mut Vec, banner_version: BannerVersion) { let ver = env!("CARGO_PKG_VERSION"); let version_str = match banner_version { BannerVersion::Off => String::new(), BannerVersion::Patch => ver.to_owned(), _ => { let parts: Vec<&str> = ver.split('.').collect(); match banner_version { BannerVersion::Major => { parts.first().map(|x| (*x).to_owned()).unwrap_or_default() } BannerVersion::Minor => { if parts.len() >= 2 { format!("{}.{}", parts[0], parts[1]) } else { ver.to_owned() } } _ => unreachable!(), } } }; let comments = vec![format!( "//! `SeaORM` Entity, @generated by sea-orm-codegen {version_str}" )]; lines.extend(comments); lines.push("".to_owned()); } pub fn write_allow_unused_imports(lines: &mut Vec) { lines.extend(vec!["#![allow(unused_imports)]".to_string()]); lines.push("".to_owned()); } pub fn gen_import(with_serde: &WithSerde) -> TokenStream { let serde_import = Self::gen_import_serde(with_serde); quote! { use sea_orm::entity::prelude::*; #serde_import } } pub fn gen_import_serde(with_serde: &WithSerde) -> TokenStream { match with_serde { WithSerde::None => Default::default(), WithSerde::Serialize => { quote! { use serde::Serialize; } } WithSerde::Deserialize => { quote! { use serde::Deserialize; } } WithSerde::Both => { quote! { use serde::{Deserialize,Serialize}; } } } } pub fn gen_entity_struct() -> TokenStream { quote! { #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; } } pub fn gen_impl_entity_name(entity: &Entity, schema_name: &Option) -> TokenStream { let schema_name = match Self::gen_schema_name(schema_name) { Some(schema_name) => quote! { fn schema_name(&self) -> Option<&str> { Some(#schema_name) } }, None => quote! {}, }; let table_name = entity.table_name.as_str(); let table_name = quote! { fn table_name(&self) -> &'static str { #table_name } }; quote! { impl EntityName for Entity { #schema_name #table_name } } } pub fn gen_import_active_enum(entity: &Entity) -> TokenStream { entity .columns .iter() .fold( (TokenStream::new(), Vec::new()), |(mut ts, mut enums), col| { if let sea_query::ColumnType::Enum { name, .. } = col.get_inner_col_type() { if !enums.contains(&name) { enums.push(name); let enum_name = format_ident!("{}", name.to_string().to_upper_camel_case()); ts.extend([quote! { use super::sea_orm_active_enums::#enum_name; }]); } } (ts, enums) }, ) .0 } pub fn gen_column_enum(entity: &Entity, column_extra_derives: &TokenStream) -> TokenStream { let column_variants = entity.columns.iter().map(|col| { let variant = col.get_name_camel_case(); let mut variant = quote! { #variant }; if !col.is_snake_case_name() { let column_name = &col.name; variant = quote! { #[sea_orm(column_name = #column_name)] #variant }; } variant }); quote! { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn #column_extra_derives)] pub enum Column { #(#column_variants,)* } } } pub fn gen_primary_key_enum(entity: &Entity) -> TokenStream { let primary_key_names_camel_case = entity.get_primary_key_names_camel_case(); quote! { #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { #(#primary_key_names_camel_case,)* } } } pub fn gen_impl_primary_key(entity: &Entity, column_option: &ColumnOption) -> TokenStream { let primary_key_auto_increment = entity.get_primary_key_auto_increment(); let value_type = entity.get_primary_key_rs_type(column_option); quote! { impl PrimaryKeyTrait for PrimaryKey { type ValueType = #value_type; fn auto_increment() -> bool { #primary_key_auto_increment } } } } pub fn gen_relation_enum(entity: &Entity) -> TokenStream { let relation_enum_name = entity.get_relation_enum_name(); quote! { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { #(#relation_enum_name,)* } } } pub fn gen_impl_column_trait(entity: &Entity) -> TokenStream { let column_names_camel_case = entity.get_column_names_camel_case(); let column_defs = entity.get_column_defs(); quote! { impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { #(Self::#column_names_camel_case => #column_defs,)* } } } } } pub fn gen_impl_relation_trait(entity: &Entity) -> TokenStream { let relation_enum_name = entity.get_relation_enum_name(); let relation_defs = entity.get_relation_defs(); let quoted = if relation_enum_name.is_empty() { quote! { panic!("No RelationDef") } } else { quote! { match self { #(Self::#relation_enum_name => #relation_defs,)* } } }; quote! { impl RelationTrait for Relation { fn def(&self) -> RelationDef { #quoted } } } } pub fn gen_impl_related(entity: &Entity) -> Vec { entity .relations .iter() .filter(|rel| !rel.self_referencing && rel.num_suffix == 0 && rel.impl_related) .map(|rel| { let enum_name = rel.get_enum_name(); let module_name = rel.get_module_name(); let inner = quote! { fn to() -> RelationDef { Relation::#enum_name.def() } }; if module_name.is_some() { quote! { impl Related for Entity { #inner } } } else { quote! { impl Related for Entity { #inner } } } }) .collect() } /// Used to generate `enum RelatedEntity` that is useful to the Seaography project pub fn gen_related_entity(entity: &Entity) -> TokenStream { let related_enum_name = entity.get_related_entity_enum_name(); let related_attrs = entity.get_related_entity_attrs(); quote! { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity { #( #related_attrs #related_enum_name ),* } } } pub fn gen_impl_conjunct_related(entity: &Entity) -> Vec { let table_name_camel_case = entity.get_table_name_camel_case_ident(); let via_snake_case = entity.get_conjunct_relations_via_snake_case(); let to_snake_case = entity.get_conjunct_relations_to_snake_case(); let to_upper_camel_case = entity.get_conjunct_relations_to_upper_camel_case(); via_snake_case .into_iter() .zip(to_snake_case) .zip(to_upper_camel_case) .map(|((via_snake_case, to_snake_case), to_upper_camel_case)| { quote! { impl Related for Entity { fn to() -> RelationDef { super::#via_snake_case::Relation::#to_upper_camel_case.def() } fn via() -> Option { Some(super::#via_snake_case::Relation::#table_name_camel_case.def().rev()) } } } }) .collect() } pub fn impl_active_model_behavior() -> TokenStream { quote! { impl ActiveModelBehavior for ActiveModel {} } } pub fn gen_mod(entity: &Entity) -> TokenStream { let table_name_snake_case_ident = format_ident!( "{}", escape_rust_keyword(entity.get_table_name_snake_case_ident()) ); quote! { pub mod #table_name_snake_case_ident; } } pub fn gen_seaography_entity_mod( entities: &[Entity], enums: &BTreeMap, ) -> TokenStream { let mut ts = TokenStream::new(); for entity in entities { let table_name_snake_case_ident = format_ident!( "{}", escape_rust_keyword(entity.get_table_name_snake_case_ident()) ); ts = quote! { #ts #table_name_snake_case_ident, } } ts = quote! { seaography::register_entity_modules!([ #ts ]); }; let mut enum_ts = TokenStream::new(); for active_enum in enums.values() { let enum_name = &active_enum.enum_name.to_string(); let enum_iden = format_ident!("{}", enum_name.to_upper_camel_case()); enum_ts = quote! { #enum_ts sea_orm_active_enums::#enum_iden, } } if !enum_ts.is_empty() { ts = quote! { #ts seaography::register_active_enums!([ #enum_ts ]); }; } ts } pub fn gen_prelude_use(entity: &Entity) -> TokenStream { let table_name_snake_case_ident = entity.get_table_name_snake_case_ident(); let table_name_camel_case_ident = entity.get_table_name_camel_case_ident(); quote! { pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident; } } pub fn gen_prelude_use_model(entity: &Entity) -> TokenStream { let table_name_snake_case_ident = entity.get_table_name_snake_case_ident(); let table_name_camel_case_ident = entity.get_table_name_camel_case_ident(); quote! { pub use super::#table_name_snake_case_ident::Model as #table_name_camel_case_ident; } } pub fn gen_schema_name(schema_name: &Option) -> Option { schema_name .as_ref() .map(|schema_name| quote! { #schema_name }) } } #[cfg(test)] mod tests { use crate::{ Column, ColumnOption, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation, RelationType, WithSerde, entity::writer::{bonus_attributes, bonus_derive}, }; use pretty_assertions::assert_eq; use proc_macro2::TokenStream; use quote::quote; use sea_query::{Alias, ColumnType, ForeignKeyAction, RcOrArc, SeaRc, StringLen}; use std::io::{self, BufRead, BufReader, Read}; fn default_column_option() -> ColumnOption { Default::default() } fn setup() -> Vec { vec![ Entity { table_name: "cake".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::Text, auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "fruit".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasMany, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![ConjunctRelation { via: "cake_filling".to_owned(), to: "filling".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "_cake_filling_".to_owned(), columns: vec![ Column { name: "cake_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "filling_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![ Relation { ref_table: "cake".to_owned(), columns: vec!["cake_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: Some(ForeignKeyAction::Cascade), on_update: Some(ForeignKeyAction::Cascade), self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "filling".to_owned(), columns: vec!["filling_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: Some(ForeignKeyAction::Cascade), on_update: Some(ForeignKeyAction::Cascade), self_referencing: false, num_suffix: 0, impl_related: true, }, ], conjunct_relations: vec![], primary_keys: vec![ PrimaryKey { name: "cake_id".to_owned(), }, PrimaryKey { name: "filling_id".to_owned(), }, ], }, Entity { table_name: "cake_filling_price".to_owned(), columns: vec![ Column { name: "cake_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "filling_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "price".to_owned(), col_type: ColumnType::Decimal(None), auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "cake_filling".to_owned(), columns: vec!["cake_id".to_owned(), "filling_id".to_owned()], ref_columns: vec!["cake_id".to_owned(), "filling_id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![], primary_keys: vec![ PrimaryKey { name: "cake_id".to_owned(), }, PrimaryKey { name: "filling_id".to_owned(), }, ], }, Entity { table_name: "filling".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::String(StringLen::N(255)), auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![], conjunct_relations: vec![ConjunctRelation { via: "cake_filling".to_owned(), to: "cake".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "fruit".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::String(StringLen::N(255)), auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "cake_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![ Relation { ref_table: "cake".to_owned(), columns: vec!["cake_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "vendor".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasMany, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, ], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "vendor".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "_name_".to_owned(), col_type: ColumnType::String(StringLen::N(255)), auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "fruitId".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "fruit".to_owned(), columns: vec!["fruitId".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "rust_keyword".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "testing".to_owned(), col_type: ColumnType::TinyInteger, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "rust".to_owned(), col_type: ColumnType::TinyUnsigned, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "keywords".to_owned(), col_type: ColumnType::SmallInteger, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "type".to_owned(), col_type: ColumnType::SmallUnsigned, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "typeof".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "crate".to_owned(), col_type: ColumnType::Unsigned, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "self".to_owned(), col_type: ColumnType::BigInteger, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "self_id1".to_owned(), col_type: ColumnType::BigUnsigned, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "self_id2".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "fruit_id1".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "fruit_id2".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "cake_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![ Relation { ref_table: "rust_keyword".to_owned(), columns: vec!["self_id1".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: true, num_suffix: 1, impl_related: true, }, Relation { ref_table: "rust_keyword".to_owned(), columns: vec!["self_id2".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: true, num_suffix: 2, impl_related: true, }, Relation { ref_table: "fruit".to_owned(), columns: vec!["fruit_id1".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 1, impl_related: true, }, Relation { ref_table: "fruit".to_owned(), columns: vec!["fruit_id2".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 2, impl_related: true, }, Relation { ref_table: "cake".to_owned(), columns: vec!["cake_id".to_owned()], ref_columns: vec!["id".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, ], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "cake_with_float".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::Text, auto_increment: false, not_null: false, unique: false, unique_key: None, }, Column { name: "price".to_owned(), col_type: ColumnType::Float, auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "fruit".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasMany, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![ConjunctRelation { via: "cake_filling".to_owned(), to: "filling".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "cake_with_double".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::Text, auto_increment: false, not_null: false, unique: false, unique_key: None, }, Column { name: "price".to_owned(), col_type: ColumnType::Double, auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "fruit".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasMany, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![ConjunctRelation { via: "cake_filling".to_owned(), to: "filling".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "collection".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "integers".to_owned(), col_type: ColumnType::Array(RcOrArc::new(ColumnType::Integer)), auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "integers_opt".to_owned(), col_type: ColumnType::Array(RcOrArc::new(ColumnType::Integer)), auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "collection_float".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "floats".to_owned(), col_type: ColumnType::Array(RcOrArc::new(ColumnType::Float)), auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "doubles".to_owned(), col_type: ColumnType::Array(RcOrArc::new(ColumnType::Double)), auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "parent".to_owned(), columns: vec![ Column { name: "id1".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "id2".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "child".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasMany, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![], primary_keys: vec![ PrimaryKey { name: "id1".to_owned(), }, PrimaryKey { name: "id2".to_owned(), }, ], }, Entity { table_name: "child".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "parent_id1".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "parent_id2".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![Relation { ref_table: "parent".to_owned(), columns: vec!["parent_id1".to_owned(), "parent_id2".to_owned()], ref_columns: vec!["id1".to_owned(), "id2".to_owned()], rel_type: RelationType::BelongsTo, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, ] } fn parse_from_file(inner: R) -> io::Result where R: Read, { let mut reader = BufReader::new(inner); let mut lines: Vec = Vec::new(); reader.read_until(b';', &mut Vec::new())?; let mut line = String::new(); while reader.read_line(&mut line)? > 0 { lines.push(line.to_owned()); line.clear(); } let content = lines.join(""); Ok(content.parse().unwrap()) } fn parse_from_frontend_file(inner: R) -> io::Result where R: Read, { let mut reader = BufReader::new(inner); let mut lines: Vec = Vec::new(); reader.read_until(b'\n', &mut Vec::new())?; let mut line = String::new(); while reader.read_line(&mut line)? > 0 { lines.push(line.to_owned()); line.clear(); } let content = lines.join(""); Ok(content.parse().unwrap()) } #[test] fn test_gen_expanded_code_blocks() -> io::Result<()> { let entities = setup(); const ENTITY_FILES: [&str; 13] = [ include_str!("../../tests/expanded/cake.rs"), include_str!("../../tests/expanded/cake_filling.rs"), include_str!("../../tests/expanded/cake_filling_price.rs"), include_str!("../../tests/expanded/filling.rs"), include_str!("../../tests/expanded/fruit.rs"), include_str!("../../tests/expanded/vendor.rs"), include_str!("../../tests/expanded/rust_keyword.rs"), include_str!("../../tests/expanded/cake_with_float.rs"), include_str!("../../tests/expanded/cake_with_double.rs"), include_str!("../../tests/expanded/collection.rs"), include_str!("../../tests/expanded/collection_float.rs"), include_str!("../../tests/expanded/parent.rs"), include_str!("../../tests/expanded/child.rs"), ]; const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [ include_str!("../../tests/expanded_with_schema_name/cake.rs"), include_str!("../../tests/expanded_with_schema_name/cake_filling.rs"), include_str!("../../tests/expanded_with_schema_name/cake_filling_price.rs"), include_str!("../../tests/expanded_with_schema_name/filling.rs"), include_str!("../../tests/expanded_with_schema_name/fruit.rs"), include_str!("../../tests/expanded_with_schema_name/vendor.rs"), include_str!("../../tests/expanded_with_schema_name/rust_keyword.rs"), include_str!("../../tests/expanded_with_schema_name/cake_with_float.rs"), include_str!("../../tests/expanded_with_schema_name/cake_with_double.rs"), include_str!("../../tests/expanded_with_schema_name/collection.rs"), include_str!("../../tests/expanded_with_schema_name/collection_float.rs"), include_str!("../../tests/expanded_with_schema_name/parent.rs"), include_str!("../../tests/expanded_with_schema_name/child.rs"), ]; assert_eq!(entities.len(), ENTITY_FILES.len()); for (i, entity) in entities.iter().enumerate() { assert_eq!( parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(), EntityWriter::gen_expanded_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); assert_eq!( parse_from_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(), EntityWriter::gen_expanded_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &Some("schema_name".to_owned()), false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); } Ok(()) } #[test] fn test_gen_compact_code_blocks() -> io::Result<()> { let entities = setup(); const ENTITY_FILES: [&str; 13] = [ include_str!("../../tests/compact/cake.rs"), include_str!("../../tests/compact/cake_filling.rs"), include_str!("../../tests/compact/cake_filling_price.rs"), include_str!("../../tests/compact/filling.rs"), include_str!("../../tests/compact/fruit.rs"), include_str!("../../tests/compact/vendor.rs"), include_str!("../../tests/compact/rust_keyword.rs"), include_str!("../../tests/compact/cake_with_float.rs"), include_str!("../../tests/compact/cake_with_double.rs"), include_str!("../../tests/compact/collection.rs"), include_str!("../../tests/compact/collection_float.rs"), include_str!("../../tests/compact/parent.rs"), include_str!("../../tests/compact/child.rs"), ]; const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [ include_str!("../../tests/compact_with_schema_name/cake.rs"), include_str!("../../tests/compact_with_schema_name/cake_filling.rs"), include_str!("../../tests/compact_with_schema_name/cake_filling_price.rs"), include_str!("../../tests/compact_with_schema_name/filling.rs"), include_str!("../../tests/compact_with_schema_name/fruit.rs"), include_str!("../../tests/compact_with_schema_name/vendor.rs"), include_str!("../../tests/compact_with_schema_name/rust_keyword.rs"), include_str!("../../tests/compact_with_schema_name/cake_with_float.rs"), include_str!("../../tests/compact_with_schema_name/cake_with_double.rs"), include_str!("../../tests/compact_with_schema_name/collection.rs"), include_str!("../../tests/compact_with_schema_name/collection_float.rs"), include_str!("../../tests/compact_with_schema_name/parent.rs"), include_str!("../../tests/compact_with_schema_name/child.rs"), ]; assert_eq!(entities.len(), ENTITY_FILES.len()); for (i, entity) in entities.iter().enumerate() { assert_eq!( parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(), EntityWriter::gen_compact_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); assert_eq!( parse_from_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(), EntityWriter::gen_compact_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &Some("schema_name".to_owned()), false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); } Ok(()) } #[test] fn test_gen_frontend_code_blocks() -> io::Result<()> { let entities = setup(); const ENTITY_FILES: [&str; 13] = [ include_str!("../../tests/frontend/cake.rs"), include_str!("../../tests/frontend/cake_filling.rs"), include_str!("../../tests/frontend/cake_filling_price.rs"), include_str!("../../tests/frontend/filling.rs"), include_str!("../../tests/frontend/fruit.rs"), include_str!("../../tests/frontend/vendor.rs"), include_str!("../../tests/frontend/rust_keyword.rs"), include_str!("../../tests/frontend/cake_with_float.rs"), include_str!("../../tests/frontend/cake_with_double.rs"), include_str!("../../tests/frontend/collection.rs"), include_str!("../../tests/frontend/collection_float.rs"), include_str!("../../tests/frontend/parent.rs"), include_str!("../../tests/frontend/child.rs"), ]; const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 13] = [ include_str!("../../tests/frontend_with_schema_name/cake.rs"), include_str!("../../tests/frontend_with_schema_name/cake_filling.rs"), include_str!("../../tests/frontend_with_schema_name/cake_filling_price.rs"), include_str!("../../tests/frontend_with_schema_name/filling.rs"), include_str!("../../tests/frontend_with_schema_name/fruit.rs"), include_str!("../../tests/frontend_with_schema_name/vendor.rs"), include_str!("../../tests/frontend_with_schema_name/rust_keyword.rs"), include_str!("../../tests/frontend_with_schema_name/cake_with_float.rs"), include_str!("../../tests/frontend_with_schema_name/cake_with_double.rs"), include_str!("../../tests/frontend_with_schema_name/collection.rs"), include_str!("../../tests/frontend_with_schema_name/collection_float.rs"), include_str!("../../tests/frontend_with_schema_name/parent.rs"), include_str!("../../tests/frontend_with_schema_name/child.rs"), ]; assert_eq!(entities.len(), ENTITY_FILES.len()); for (i, entity) in entities.iter().enumerate() { assert_eq!( dbg!(parse_from_frontend_file(ENTITY_FILES[i].as_bytes())?.to_string()), EntityWriter::gen_frontend_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); assert_eq!( parse_from_frontend_file(ENTITY_FILES_WITH_SCHEMA_NAME[i].as_bytes())?.to_string(), EntityWriter::gen_frontend_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &Some("schema_name".to_owned()), false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); } Ok(()) } #[test] fn test_gen_with_serde() -> io::Result<()> { let cake_entity = setup().get(0).unwrap().clone(); assert_eq!(cake_entity.get_table_name_snake_case(), "cake"); // Compact code blocks assert_eq!( comparable_file_string(include_str!("../../tests/compact_with_serde/cake_none.rs"))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/compact_with_serde/cake_serialize.rs" ))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::Serialize, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/compact_with_serde/cake_deserialize.rs" ))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::Deserialize, &default_column_option(), &None, true, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!("../../tests/compact_with_serde/cake_both.rs"))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::Both, &default_column_option(), &None, true, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); // Expanded code blocks assert_eq!( comparable_file_string(include_str!("../../tests/expanded_with_serde/cake_none.rs"))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_serde/cake_serialize.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::Serialize, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_serde/cake_deserialize.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::Deserialize, &default_column_option(), &None, true, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!("../../tests/expanded_with_serde/cake_both.rs"))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::Both, &default_column_option(), &None, true, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); // Frontend code blocks assert_eq!( comparable_file_string(include_str!("../../tests/frontend_with_serde/cake_none.rs"))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_serde/cake_serialize.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::Serialize, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_serde/cake_deserialize.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::Deserialize, &default_column_option(), &None, true, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!("../../tests/frontend_with_serde/cake_both.rs"))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::Both, &default_column_option(), &None, true, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); Ok(()) } #[test] fn test_gen_with_seaography() -> io::Result<()> { let cake_entity = Entity { table_name: "cake".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "name".to_owned(), col_type: ColumnType::Text, auto_increment: false, not_null: false, unique: false, unique_key: None, }, Column { name: "base_id".to_owned(), col_type: ColumnType::Integer, auto_increment: false, not_null: false, unique: false, unique_key: None, }, ], relations: vec![ Relation { ref_table: "fruit".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasMany, on_delete: None, on_update: None, self_referencing: false, num_suffix: 0, impl_related: true, }, Relation { ref_table: "cake".to_owned(), columns: vec![], ref_columns: vec![], rel_type: RelationType::HasOne, on_delete: None, on_update: None, self_referencing: true, num_suffix: 0, impl_related: true, }, ], conjunct_relations: vec![ConjunctRelation { via: "cake_filling".to_owned(), to: "filling".to_owned(), }], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }; assert_eq!(cake_entity.get_table_name_snake_case(), "cake"); // Compact code blocks assert_eq!( comparable_file_string(include_str!("../../tests/with_seaography/cake.rs"))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), true, true, )) ); // Expanded code blocks assert_eq!( comparable_file_string(include_str!("../../tests/with_seaography/cake_expanded.rs"))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), true, true, )) ); // Frontend code blocks assert_eq!( comparable_file_string(include_str!("../../tests/with_seaography/cake_frontend.rs"))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), true, true, )) ); Ok(()) } #[test] fn test_gen_with_seaography_mod() -> io::Result<()> { use crate::ActiveEnum; use sea_query::IntoIden; let entities = setup(); let enums = vec![ ( "coinflip_result_type", ActiveEnum { enum_name: Alias::new("coinflip_result_type").into_iden(), values: vec!["HEADS", "TAILS"] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), }, ), ( "media_type", ActiveEnum { enum_name: Alias::new("media_type").into_iden(), values: vec![ "UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D", ] .into_iter() .map(|variant| Alias::new(variant).into_iden()) .collect(), }, ), ] .into_iter() .map(|(k, v)| (k.to_string(), v)) .collect(); assert_eq!( comparable_file_string(include_str!("../../tests/with_seaography/mod.rs"))?, generated_to_string(vec![EntityWriter::gen_seaography_entity_mod( &entities, &enums, )]) ); Ok(()) } #[test] fn test_gen_with_derives() -> io::Result<()> { let mut cake_entity = setup().get_mut(0).unwrap().clone(); assert_eq!(cake_entity.get_table_name_snake_case(), "cake"); // Compact code blocks assert_eq!( comparable_file_string(include_str!( "../../tests/compact_with_derives/cake_none.rs" ))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!("../../tests/compact_with_derives/cake_one.rs"))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &bonus_derive(["ts_rs::TS"]), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/compact_with_derives/cake_multiple.rs" ))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]), &TokenStream::new(), &TokenStream::new(), false, true, )) ); // Expanded code blocks assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_derives/cake_none.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_derives/cake_one.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &bonus_derive(["ts_rs::TS"]), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_derives/cake_multiple.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]), &TokenStream::new(), &TokenStream::new(), false, true, )) ); // Frontend code blocks assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_derives/cake_none.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_derives/cake_one.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &bonus_derive(["ts_rs::TS"]), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_derives/cake_multiple.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &bonus_derive(["ts_rs::TS", "utoipa::ToSchema"]), &TokenStream::new(), &TokenStream::new(), false, true, )) ); // Make the `name` column of `cake` entity as hidden column cake_entity.columns[1].name = "_name".into(); assert_serde_variant_results( &cake_entity, &( include_str!("../../tests/compact_with_serde/cake_serialize_with_hidden_column.rs"), WithSerde::Serialize, None, ), Box::new(EntityWriter::gen_compact_code_blocks), )?; assert_serde_variant_results( &cake_entity, &( include_str!( "../../tests/expanded_with_serde/cake_serialize_with_hidden_column.rs" ), WithSerde::Serialize, None, ), Box::new(EntityWriter::gen_expanded_code_blocks), )?; assert_serde_variant_results( &cake_entity, &( include_str!( "../../tests/frontend_with_serde/cake_serialize_with_hidden_column.rs" ), WithSerde::Serialize, None, ), Box::new(EntityWriter::gen_frontend_code_blocks), )?; Ok(()) } #[test] fn test_gen_with_column_derives() -> io::Result<()> { let cake_entity = setup().get_mut(0).unwrap().clone(); assert_eq!(cake_entity.get_table_name_snake_case(), "cake"); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_column_derives/cake_one.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &bonus_derive(["async_graphql::Enum"]), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_column_derives/cake_multiple.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &bonus_derive(["async_graphql::Enum", "Eq", "PartialEq"]), false, true, )) ); Ok(()) } #[allow(clippy::type_complexity)] fn assert_serde_variant_results( cake_entity: &Entity, entity_serde_variant: &(&str, WithSerde, Option), generator: Box< dyn Fn( &Entity, &WithSerde, &ColumnOption, &Option, bool, bool, &TokenStream, &TokenStream, &TokenStream, bool, bool, ) -> Vec, >, ) -> io::Result<()> { let mut reader = BufReader::new(entity_serde_variant.0.as_bytes()); let mut lines: Vec = Vec::new(); let serde_skip_deserializing_primary_key = matches!( entity_serde_variant.1, WithSerde::Both | WithSerde::Deserialize ); let serde_skip_hidden_column = matches!(entity_serde_variant.1, WithSerde::Serialize); reader.read_until(b'\n', &mut Vec::new())?; let mut line = String::new(); while reader.read_line(&mut line)? > 0 { lines.push(line.to_owned()); line.clear(); } let content = lines.join(""); let expected: TokenStream = content.parse().unwrap(); println!("{:?}", entity_serde_variant.1); let generated = generator( cake_entity, &entity_serde_variant.1, &default_column_option(), &entity_serde_variant.2, serde_skip_deserializing_primary_key, serde_skip_hidden_column, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }); assert_eq!(expected.to_string(), generated.to_string()); Ok(()) } #[test] fn test_gen_with_attributes() -> io::Result<()> { let cake_entity = setup().get(0).unwrap().clone(); assert_eq!(cake_entity.get_table_name_snake_case(), "cake"); // Compact code blocks assert_eq!( comparable_file_string(include_str!( "../../tests/compact_with_attributes/cake_none.rs" ))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/compact_with_attributes/cake_one.rs" ))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#]), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/compact_with_attributes/cake_multiple.rs" ))?, generated_to_string(EntityWriter::gen_compact_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]), &TokenStream::new(), false, true, )) ); // Expanded code blocks assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_attributes/cake_none.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_attributes/cake_one.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#]), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/expanded_with_attributes/cake_multiple.rs" ))?, generated_to_string(EntityWriter::gen_expanded_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]), &TokenStream::new(), false, true, )) ); // Frontend code blocks assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_attributes/cake_none.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_attributes/cake_one.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#]), &TokenStream::new(), false, true, )) ); assert_eq!( comparable_file_string(include_str!( "../../tests/frontend_with_attributes/cake_multiple.rs" ))?, generated_to_string(EntityWriter::gen_frontend_code_blocks( &cake_entity, &WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]), &TokenStream::new(), false, true, )) ); Ok(()) } fn generated_to_string(generated: Vec) -> String { generated .into_iter() .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() } fn comparable_file_string(file: &str) -> io::Result { let mut reader = BufReader::new(file.as_bytes()); let mut lines: Vec = Vec::new(); reader.read_until(b'\n', &mut Vec::new())?; let mut line = String::new(); while reader.read_line(&mut line)? > 0 { lines.push(line.to_owned()); line.clear(); } let content = lines.join(""); let expected: TokenStream = content.parse().unwrap(); Ok(expected.to_string()) } #[test] fn test_gen_postgres() -> io::Result<()> { let entities = vec![ // This tests that the JsonBinary column type is annotated // correctly in compact entity form. More information can be found // in this issue: // // https://github.com/SeaQL/sea-orm/issues/1344 Entity { table_name: "task".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "payload".to_owned(), col_type: ColumnType::Json, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "payload_binary".to_owned(), col_type: ColumnType::JsonBinary, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, ]; const ENTITY_FILES: [&str; 1] = [include_str!("../../tests/postgres/binary_json.rs")]; const ENTITY_FILES_EXPANDED: [&str; 1] = [include_str!("../../tests/postgres/binary_json_expanded.rs")]; assert_eq!(entities.len(), ENTITY_FILES.len()); for (i, entity) in entities.iter().enumerate() { assert_eq!( parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(), EntityWriter::gen_compact_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); assert_eq!( parse_from_file(ENTITY_FILES_EXPANDED[i].as_bytes())?.to_string(), EntityWriter::gen_expanded_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &Some("schema_name".to_owned()), false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); } Ok(()) } #[test] fn test_gen_import_active_enum() -> io::Result<()> { let entities = vec![ Entity { table_name: "tea_pairing".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "first_tea".to_owned(), col_type: ColumnType::Enum { name: SeaRc::new(Alias::new("tea_enum")), variants: vec![ SeaRc::new(Alias::new("everyday_tea")), SeaRc::new(Alias::new("breakfast_tea")), ], }, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "second_tea".to_owned(), col_type: ColumnType::Enum { name: SeaRc::new(Alias::new("tea_enum")), variants: vec![ SeaRc::new(Alias::new("everyday_tea")), SeaRc::new(Alias::new("breakfast_tea")), ], }, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, Entity { table_name: "tea_pairing_with_size".to_owned(), columns: vec![ Column { name: "id".to_owned(), col_type: ColumnType::Integer, auto_increment: true, not_null: true, unique: false, unique_key: None, }, Column { name: "first_tea".to_owned(), col_type: ColumnType::Enum { name: SeaRc::new(Alias::new("tea_enum")), variants: vec![ SeaRc::new(Alias::new("everyday_tea")), SeaRc::new(Alias::new("breakfast_tea")), ], }, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "second_tea".to_owned(), col_type: ColumnType::Enum { name: SeaRc::new(Alias::new("tea_enum")), variants: vec![ SeaRc::new(Alias::new("everyday_tea")), SeaRc::new(Alias::new("breakfast_tea")), ], }, auto_increment: false, not_null: true, unique: false, unique_key: None, }, Column { name: "size".to_owned(), col_type: ColumnType::Enum { name: SeaRc::new(Alias::new("tea_size")), variants: vec![ SeaRc::new(Alias::new("small")), SeaRc::new(Alias::new("medium")), SeaRc::new(Alias::new("huge")), ], }, auto_increment: false, not_null: true, unique: false, unique_key: None, }, ], relations: vec![], conjunct_relations: vec![], primary_keys: vec![PrimaryKey { name: "id".to_owned(), }], }, ]; assert_eq!( quote!( use super::sea_orm_active_enums::TeaEnum; ) .to_string(), EntityWriter::gen_import_active_enum(&entities[0]).to_string() ); assert_eq!( quote!( use super::sea_orm_active_enums::TeaEnum; use super::sea_orm_active_enums::TeaSize; ) .to_string(), EntityWriter::gen_import_active_enum(&entities[1]).to_string() ); Ok(()) } #[test] fn test_gen_dense_code_blocks() -> io::Result<()> { let entities = setup(); const ENTITY_FILES: [&str; 13] = [ include_str!("../../tests/dense/cake.rs"), include_str!("../../tests/dense/cake_filling.rs"), include_str!("../../tests/dense/cake_filling_price.rs"), include_str!("../../tests/dense/filling.rs"), include_str!("../../tests/dense/fruit.rs"), include_str!("../../tests/dense/vendor.rs"), include_str!("../../tests/dense/rust_keyword.rs"), include_str!("../../tests/dense/cake_with_float.rs"), include_str!("../../tests/dense/cake_with_double.rs"), include_str!("../../tests/dense/collection.rs"), include_str!("../../tests/dense/collection_float.rs"), include_str!("../../tests/dense/parent.rs"), include_str!("../../tests/dense/child.rs"), ]; assert_eq!(entities.len(), ENTITY_FILES.len()); for (i, entity) in entities.iter().enumerate() { assert_eq!( parse_from_file(ENTITY_FILES[i].as_bytes())?.to_string(), EntityWriter::gen_dense_code_blocks( entity, &crate::WithSerde::None, &default_column_option(), &None, false, false, &TokenStream::new(), &TokenStream::new(), &TokenStream::new(), false, true, ) .into_iter() .skip(1) .fold(TokenStream::new(), |mut acc, tok| { acc.extend(tok); acc }) .to_string() ); } Ok(()) } } ================================================ FILE: sea-orm-codegen/src/error.rs ================================================ use std::{error, fmt, io}; #[derive(Debug)] pub enum Error { StdIoError(io::Error), TransformError(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::StdIoError(e) => write!(f, "{e:?}"), Self::TransformError(e) => write!(f, "{e:?}"), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Self::StdIoError(e) => Some(e), Self::TransformError(_) => None, } } } impl From for Error { fn from(io_err: io::Error) -> Self { Self::StdIoError(io_err) } } ================================================ FILE: sea-orm-codegen/src/lib.rs ================================================ mod entity; mod error; mod merge; mod util; pub use entity::*; pub use error::*; pub use merge::*; #[cfg(test)] mod tests_cfg; ================================================ FILE: sea-orm-codegen/src/merge/extract.rs ================================================ use super::is_active_model_behavior_impl; use syn::File; use syn::Item; use syn::ItemEnum; use syn::ItemImpl; use syn::ItemStruct; use syn::ItemUse; pub(super) fn extract_active_model_behavior_impls(file: &File) -> impl Iterator { file.items.iter().filter_map(|item| match item { Item::Impl(item_impl) if is_active_model_behavior_impl(item_impl) => Some(item_impl), _ => None, }) } pub(super) fn extract_top_level_uses(file: &File) -> impl Iterator { file.items.iter().filter_map(|item| match item { Item::Use(item_use) => Some(item_use), _ => None, }) } pub(super) fn find_model_struct(file: &File) -> Option<&ItemStruct> { file.items.iter().find_map(|item| { if let Item::Struct(item_struct) = item { if item_struct.ident == "Model" { return Some(item_struct); } } None }) } pub(super) fn find_relation_enum(file: &File) -> Option<&ItemEnum> { file.items.iter().find_map(|item| { if let Item::Enum(item_enum) = item { if item_enum.ident == "Relation" { return Some(item_enum); } } None }) } ================================================ FILE: sea-orm-codegen/src/merge/mod.rs ================================================ use std::collections::{HashMap, HashSet}; use prettyplease::unparse; use syn::fold::{self, Fold}; use syn::{ Attribute, Fields, Ident, Item, ItemImpl, Path, parse::Parser, parse_file, punctuated::Punctuated, }; use syn::{ItemUse, Meta}; mod extract; use extract::*; #[derive(Default)] struct OldIndex<'a> { model_attrs: &'a [Attribute], model_field_attrs: HashMap<&'a Ident, &'a [Attribute]>, relation_attrs: &'a [Attribute], relation_variant_attrs: HashMap<&'a Ident, &'a [Attribute]>, } impl<'a> OldIndex<'a> { fn from_file(file: &'a syn::File) -> OldIndex<'a> { let mut idx = OldIndex::default(); if let Some(model) = find_model_struct(file) { idx.model_attrs = &model.attrs; if let Fields::Named(named) = &model.fields { for field in &named.named { if let Some(ident) = &field.ident { idx.model_field_attrs.insert(ident, &field.attrs); } } } } if let Some(relation) = find_relation_enum(file) { idx.relation_attrs = &relation.attrs; for variant in &relation.variants { idx.relation_variant_attrs .insert(&variant.ident, &variant.attrs); } } idx } } struct Merger<'a> { old: OldIndex<'a>, seen_use: HashSet, extra_uses: Vec<&'a syn::ItemUse>, old_behaviors: Vec<&'a syn::ItemImpl>, } impl<'a> Merger<'a> { fn new( old: OldIndex<'a>, extra_uses: Vec<&'a syn::ItemUse>, old_behaviors: Vec<&'a syn::ItemImpl>, ) -> Self { Self { old, seen_use: HashSet::new(), extra_uses, old_behaviors, } } } impl<'a> Fold for Merger<'a> { fn fold_item_struct(&mut self, i: syn::ItemStruct) -> syn::ItemStruct { let mut i = fold::fold_item_struct(self, i); if i.ident == "Model" { merge_item_attributes(&mut i.attrs, self.old.model_attrs); if let Fields::Named(named) = &mut i.fields { for field in &mut named.named { if let Some(ident) = &field.ident { if let Some(old_attrs) = self.old.model_field_attrs.get(ident) { merge_item_attributes(&mut field.attrs, old_attrs); } } } } } i } fn fold_item_enum(&mut self, i: syn::ItemEnum) -> syn::ItemEnum { let mut i = fold::fold_item_enum(self, i); if i.ident == "Relation" { merge_item_attributes(&mut i.attrs, self.old.relation_attrs); for variant in &mut i.variants { if let Some(old_attrs) = self.old.relation_variant_attrs.get(&variant.ident) { merge_item_attributes(&mut variant.attrs, old_attrs); } } } i } fn fold_file(&mut self, mut file: syn::File) -> syn::File { let mut uses: Vec = Vec::new(); let mut others: Vec = Vec::new(); for item in file.items { match item { Item::Use(u) => { let u = fold::fold_item_use(self, u); if self.seen_use.insert(u.clone()) { uses.push(Item::Use(u)); } } Item::Impl(i) => { if is_active_model_behavior_impl(&i) { continue; } let i = fold::fold_item_impl(self, i); others.push(Item::Impl(i)); } other => { let other = fold::fold_item(self, other); others.push(other); } } } for &u in &self.extra_uses { if self.seen_use.insert(u.clone()) { uses.push(Item::Use(u.clone())); } } for &i in &self.old_behaviors { others.push(Item::Impl(i.clone())); } file.items = { let mut v = Vec::with_capacity(uses.len() + others.len()); v.extend(uses); v.extend(others); v }; file } } #[derive(Debug, Default)] #[doc(hidden)] pub struct MergeReport { pub output: String, pub warnings: Vec, pub fallback_applied: bool, } impl MergeReport { fn fallback(old_src: &str, new_src: &str) -> Self { let mut warnings = Vec::new(); let mut appended = String::from(new_src); warnings.push( "Parsing failed. The previous file content has been preserved as comments at the end." .to_owned(), ); if !appended.ends_with("\n\n") { appended.push('\n'); } appended.push_str( "// --- Preserved original file content (could not be merged automatically) ---\n", ); appended.push_str("/*\n"); appended.push_str(old_src); if !old_src.ends_with('\n') { appended.push('\n'); } appended.push_str("*/\n\n"); MergeReport { output: appended, warnings, fallback_applied: true, } } } #[doc(hidden)] pub fn merge_entity_files(old_src: &str, new_src: &str) -> Result { let new_file = match parse_file(new_src) { Ok(file) => file, Err(err) => { let mut report = MergeReport::fallback(old_src, new_src); report.warnings.push(format!( "Unable to parse new file, generated fallback: {err}" )); return Err(report); } }; let old_file = match parse_file(old_src) { Ok(file) => file, Err(err) => { let mut report = MergeReport::fallback(old_src, new_src); report.warnings.push(format!( "Unable to parse old file, generated fallback: {err}" )); return Err(report); } }; let old_index = OldIndex::from_file(&old_file); let extra_uses = extract_top_level_uses(&old_file).collect::>(); let old_behaviors = extract_active_model_behavior_impls(&old_file).collect::>(); let mut folder = Merger::new(old_index, extra_uses, old_behaviors); let merged_file = folder.fold_file(new_file); let output = render_file_with_spacing(merged_file); Ok(output) } fn merge_item_attributes(new_attrs: &mut Vec, old_attrs: &[Attribute]) { merge_derives(new_attrs, old_attrs); let mut path_idx_map = HashMap::::new(); let mut doc_attr_set = HashSet::::new(); for (idx, attr) in new_attrs.iter().enumerate() { if is_doc_attribute(attr) { doc_attr_set.insert(attr.clone()); continue; } if attr.path().is_ident("derive") { continue; } path_idx_map.entry(attr.path().clone()).or_insert(idx); } for old_attr in old_attrs { if old_attr.path().is_ident("derive") { continue; } if is_doc_attribute(old_attr) { if !doc_attr_set.contains(old_attr) { doc_attr_set.insert(old_attr.clone()); new_attrs.push(old_attr.clone()); } continue; } if let Some(&idx) = path_idx_map.get(old_attr.path()) { let current = &new_attrs[idx]; if let Some(merged) = merge_non_derive_attribute(old_attr, current) { new_attrs[idx] = merged; } } else { path_idx_map.insert(old_attr.path().clone(), new_attrs.len()); new_attrs.push(old_attr.clone()); } } } fn merge_derives(new_attrs: &mut Vec, old_attrs: &[Attribute]) { let mut derive_paths: Vec = Vec::new(); let mut seen = HashSet::new(); let mut insert_index = None; let mut idx = 0usize; while idx < new_attrs.len() { if new_attrs[idx].path().is_ident("derive") { insert_index.get_or_insert(idx); if let Some(paths) = parse_derive_paths(&new_attrs[idx]) { for path in paths { if seen.insert(path.clone()) { derive_paths.push(path); } } } new_attrs.remove(idx); } else { idx += 1; } } for attr in old_attrs .iter() .filter(|attr| attr.path().is_ident("derive")) { if let Some(paths) = parse_derive_paths(attr) { for path in paths { if seen.insert(path.clone()) { derive_paths.push(path); } } } } if derive_paths.is_empty() { return; } if insert_index.is_none() { insert_index = Some( new_attrs .iter() .position(|attr| !is_doc_attribute(attr)) .unwrap_or(0), ); } let derive_attr: Attribute = syn::parse_quote!(#[derive(#(#derive_paths),*)]); new_attrs.insert(insert_index.unwrap_or(0), derive_attr); } fn parse_derive_paths(attr: &Attribute) -> Option> { attr.parse_args_with(Punctuated::::parse_terminated) .ok() .map(|punct| punct.into_iter().collect()) } fn merge_non_derive_attribute(old_attr: &Attribute, new_attr: &Attribute) -> Option { use syn::{Token, punctuated::Punctuated}; let parse_attr = |attr: &Attribute| { attr.parse_args_with(Punctuated::::parse_terminated) .ok() }; let old_args = parse_attr(old_attr)?; let new_args = parse_attr(new_attr)?; fn meta_key(meta: &Meta) -> &Path { match meta { Meta::Path(p) => p, Meta::NameValue(nv) => &nv.path, Meta::List(list) => &list.path, } } let mut seen_keys = HashSet::::new(); let mut merged: Vec = Vec::new(); for m in old_args.into_iter() { // Always preserve old attribute values on key conflict seen_keys.insert(meta_key(&m).clone()); merged.push(m); } for m in new_args.into_iter() { let key = meta_key(&m); if !seen_keys.contains(key) { seen_keys.insert(key.clone()); merged.push(m); } } let path = old_attr.path(); let tokens = quote::quote!(#[#path(#(#merged),*)]); Attribute::parse_outer .parse2(tokens) .ok() .and_then(|v| v.into_iter().next()) } fn is_active_model_behavior_impl(item_impl: &ItemImpl) -> bool { let trait_ident = item_impl .trait_ .as_ref() .and_then(|(_, path, _)| path.segments.last()) .map(|segment| &segment.ident); let self_ident = match item_impl.self_ty.as_ref() { syn::Type::Path(type_path) => type_path.path.segments.last().map(|seg| &seg.ident), _ => None, }; matches!(trait_ident, Some(ident) if ident == "ActiveModelBehavior") && matches!(self_ident, Some(ident) if ident == "ActiveModel") } fn is_doc_attribute(attr: &Attribute) -> bool { attr.path().is_ident("doc") } fn render_file_with_spacing(file: syn::File) -> String { fn trim_trailing_newlines(s: &str) -> &str { s.trim_end_matches(['\n', '\r']) } fn render_items_with_sep(items: &[Item], separator: &str) -> Option { let mut rendered_parts = Vec::with_capacity(items.len()); for item in items { let single_item_file = syn::File { shebang: None, attrs: Vec::new(), items: vec![item.clone()], }; let rendered = unparse(&single_item_file); let trimmed = trim_trailing_newlines(&rendered); if !trimmed.is_empty() { rendered_parts.push(trimmed.to_owned()); } } if rendered_parts.is_empty() { None } else { Some(rendered_parts.join(separator)) } } let syn::File { shebang, attrs, items, } = file; let (use_items, other_items): (Vec, Vec) = items.into_iter().partition(|it| matches!(it, Item::Use(_))); let mut out = String::new(); if let Some(s) = shebang { out.push_str(s.trim_end_matches('\n')); out.push('\n'); } if !attrs.is_empty() { if !out.is_empty() && !out.ends_with('\n') { out.push('\n'); } let attrs_file = syn::File { shebang: None, attrs, items: vec![], }; let rendered = unparse(&attrs_file); let trimmed = trim_trailing_newlines(&rendered); if !trimmed.is_empty() { out.push_str(trimmed); out.push('\n'); out.push('\n'); } } if let Some(block) = render_items_with_sep(&use_items, "\n") { if !out.is_empty() && !out.ends_with('\n') { out.push('\n'); } if !block.is_empty() { out.push_str(&block); out.push('\n'); } } if let Some(block) = render_items_with_sep(&other_items, "\n\n") { if !out.is_empty() && !out.ends_with("\n\n") { if out.ends_with('\n') { out.push('\n'); } else { out.push_str("\n\n"); } } out.push_str(&block); out.push('\n'); } if !out.ends_with('\n') { out.push('\n'); } out } #[cfg(test)] mod tests { use indoc::indoc; use super::*; #[test] fn merge_preserves_behavior_and_attributes() { let old_file = indoc! {r#" use sea_orm::entity::prelude::*; use crate::helpers::Helper; #[derive(Clone, Debug, DeriveEntityModel, serde::Serialize)] #[sea_orm(table_name = "posts")] #[serde(rename_all = "camelCase")] /// Old model docs pub struct Model { /// Old id docs #[serde(rename = "postId")] #[cfg(feature = "serde")] pub id: i32, pub title: String, } #[derive(DeriveRelation, Eq, PartialEq)] #[ts(export)] pub enum Relation { #[sea_orm(has_one = "super::super::MyEntity")] MyRelation, } impl ActiveModelBehavior for ActiveModel { fn custom(&self) -> Helper { Helper::default() } } "# }; let curr = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(Clone, Debug, DeriveEntityModel)] #[sea_orm(schema_name = "public")] pub struct Model { pub id: i32, pub title: String, } #[derive(DeriveRelation)] pub enum Relation { MyRelation, } impl ActiveModelBehavior for ActiveModel {} "# }; let merged = merge_entity_files(old_file, curr).expect("merge failed"); let expected = indoc! {r#" use sea_orm::entity::prelude::*; use crate::helpers::Helper; #[derive(Clone, Debug, DeriveEntityModel, serde::Serialize)] #[sea_orm(table_name = "posts", schema_name = "public")] #[serde(rename_all = "camelCase")] /// Old model docs pub struct Model { /// Old id docs #[serde(rename = "postId")] #[cfg(feature = "serde")] pub id: i32, pub title: String, } #[derive(DeriveRelation, Eq, PartialEq)] #[ts(export)] pub enum Relation { #[sea_orm(has_one = "super::super::MyEntity")] MyRelation, } impl ActiveModelBehavior for ActiveModel { fn custom(&self) -> Helper { Helper::default() } } "# }; assert_eq!(merged, expected); } #[test] fn merge_handles_field_changes() { let old_src = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { #[serde(default)] pub id: i32, #[serde(rename = "name")] pub name: String, #[serde(rename = "foo")] pub removed: bool, } impl ActiveModelBehavior for ActiveModel {} "#}; let new_src = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { pub id: i32, pub name: String, #[serde(rename = "bar")] pub added: String, } impl ActiveModelBehavior for ActiveModel {} "#}; let merged = merge_entity_files(old_src, new_src).expect("merge should succeed"); let expected = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { #[serde(default)] pub id: i32, #[serde(rename = "name")] pub name: String, #[serde(rename = "bar")] pub added: String, } impl ActiveModelBehavior for ActiveModel {} "# }; assert_eq!(merged, expected); } #[test] fn complex_use() { let old_src = indoc! {r#" use crate::{ B::{self, C}, A, D as E, }; use self::{ helper::Tool as ToolAlias, super::shared::Common, }; pub struct Placeholder; "# }; let new_src = indoc! {r#" use sea_orm::entity::prelude::*; pub struct Placeholder; "# }; let merged = merge_entity_files(old_src, new_src).expect("merge should succeed"); let expected = indoc! {r#" use sea_orm::entity::prelude::*; use crate::{ B::{self, C}, A, D as E, }; use self::{helper::Tool as ToolAlias, super::shared::Common}; pub struct Placeholder; "# }; assert_eq!(merged, expected); } #[test] fn conv_to_comment_fallback() { let old_src = indoc! {r#" this is not valid rust impl ActiveModelBehavior for ActiveModel { fn something(&self) {} } "# }; let new_src = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { pub id: i32, } impl ActiveModelBehavior for ActiveModel {} "# }; let report = merge_entity_files(old_src, new_src).unwrap_err(); assert!(report.fallback_applied); let expect = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { pub id: i32, } impl ActiveModelBehavior for ActiveModel {} // --- Preserved original file content (could not be merged automatically) --- /* this is not valid rust impl ActiveModelBehavior for ActiveModel { fn something(&self) {} } */ "#}; assert_eq!(report.output, expect) } #[test] fn conflict_attrs_should_never_be_overwritten() { let old_src = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] #[serde(rename_all = "camelCase")] pub struct Model { #[serde(rename_all = "foo")] pub id: i32, } impl ActiveModelBehavior for ActiveModel {} "#}; let new_src = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] #[serde(rename_all = "snake_case")] pub struct Model { #[serde(rename_all = "bar")] pub id: i32, } impl ActiveModelBehavior for ActiveModel {} "#}; let merged = merge_entity_files(old_src, new_src).expect("merge should succeed"); let expected = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] #[serde(rename_all = "camelCase")] pub struct Model { #[serde(rename_all = "foo")] pub id: i32, } impl ActiveModelBehavior for ActiveModel {} "#}; assert_eq!(merged, expected); } #[test] fn conflict_attrs_should_be_merged() { let old_src = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { #[serde(rename = "oldId")] pub id: i32, } impl ActiveModelBehavior for ActiveModel {} "#}; let new_src = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { #[serde(rename = "newId", default)] pub id: i32, } impl ActiveModelBehavior for ActiveModel {} "#}; let merged = merge_entity_files(old_src, new_src).expect("merge should succeed"); let expected = indoc! {r#" use sea_orm::entity::prelude::*; #[derive(DeriveEntityModel)] pub struct Model { #[serde(rename = "oldId", default)] pub id: i32, } impl ActiveModelBehavior for ActiveModel {} "#}; assert_eq!(merged, expected); } } ================================================ FILE: sea-orm-codegen/src/tests_cfg/compact/indexes.rs ================================================ //! An entity definition for testing table index creation. use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "indexes")] pub struct Model { #[sea_orm(primary_key)] pub indexes_id: i32, #[sea_orm(unique)] pub unique_attr: i32, #[sea_orm(unique_key = "my_unique")] pub unique_key_a: String, #[sea_orm(unique_key = "my_unique")] pub unique_key_b: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/compact/mod.rs ================================================ pub mod indexes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/dense/indexes.rs ================================================ //! An entity definition for testing table index creation. use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "indexes")] pub struct Model { #[sea_orm(primary_key)] pub indexes_id: i32, #[sea_orm(unique)] pub unique_attr: i32, #[sea_orm(unique_key = "my_unique")] pub unique_key_a: String, #[sea_orm(unique_key = "my_unique")] pub unique_key_b: String, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/dense/mod.rs ================================================ pub mod indexes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/duplicated_many_to_many_paths/bills.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "bills")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", on_update = "NoAction", on_delete = "NoAction", )] Users, #[sea_orm(has_many = "super::users_saved_bills::Entity")] UsersSavedBills, #[sea_orm(has_many = "super::users_votes::Entity")] UsersVotes, } impl Related for Entity { fn to() -> RelationDef { Relation::Users.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::UsersSavedBills.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::UsersVotes.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/duplicated_many_to_many_paths/mod.rs ================================================ pub mod prelude; pub mod bills; pub mod users; pub mod users_saved_bills; pub mod users_votes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/duplicated_many_to_many_paths/prelude.rs ================================================ pub use super::bills::Entity as Bills; pub use super::users::Entity as Users; pub use super::users_saved_bills::Entity as UsersSavedBills; pub use super::users_votes::Entity as UsersVotes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/duplicated_many_to_many_paths/users.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text")] pub email: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::bills::Entity")] Bills, #[sea_orm(has_many = "super::users_saved_bills::Entity")] UsersSavedBills, #[sea_orm(has_many = "super::users_votes::Entity")] UsersVotes, } impl Related for Entity { fn to() -> RelationDef { Relation::Bills.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::UsersSavedBills.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::UsersVotes.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/duplicated_many_to_many_paths/users_saved_bills.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users_saved_bills")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub user_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub bill_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::bills::Entity", from = "Column::BillId", to = "super::bills::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Bills, #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Users, } impl Related for Entity { fn to() -> RelationDef { Relation::Bills.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Users.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/duplicated_many_to_many_paths/users_votes.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users_votes")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub user_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub bill_id: i32, pub vote: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::bills::Entity", from = "Column::BillId", to = "super::bills::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Bills, #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Users, } impl Related for Entity { fn to() -> RelationDef { Relation::Bills.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Users.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many/bills.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "bills")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", on_update = "NoAction", on_delete = "NoAction", )] Users, #[sea_orm(has_many = "super::users_votes::Entity")] UsersVotes, } impl Related for Entity { fn to() -> RelationDef { Relation::UsersVotes.def() } } impl Related for Entity { fn to() -> RelationDef { super::users_votes::Relation::Users.def() } fn via() -> Option { Some(super::users_votes::Relation::Bills.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many/mod.rs ================================================ pub mod prelude; pub mod bills; pub mod users; pub mod users_votes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many/prelude.rs ================================================ pub use super::bills::Entity as Bills; pub use super::users::Entity as Users; pub use super::users_votes::Entity as UsersVotes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many/users.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text")] pub email: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::bills::Entity")] Bills, #[sea_orm(has_many = "super::users_votes::Entity")] UsersVotes, } impl Related for Entity { fn to() -> RelationDef { Relation::UsersVotes.def() } } impl Related for Entity { fn to() -> RelationDef { super::users_votes::Relation::Bills.def() } fn via() -> Option { Some(super::users_votes::Relation::Users.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many/users_votes.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users_votes")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub user_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub bill_id: i32, pub vote: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::bills::Entity", from = "Column::BillId", to = "super::bills::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Bills, #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Users, } impl Related for Entity { fn to() -> RelationDef { Relation::Bills.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Users.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many_multiple/bills.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "bills")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", on_update = "NoAction", on_delete = "NoAction", )] Users, } impl Related for Entity { fn to() -> RelationDef { Relation::Users.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many_multiple/mod.rs ================================================ pub mod prelude; pub mod bills; pub mod users; pub mod users_votes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many_multiple/prelude.rs ================================================ pub use super::bills::Entity as Bills; pub use super::users::Entity as Users; pub use super::users_votes::Entity as UsersVotes; ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many_multiple/users.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text")] pub email: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::bills::Entity")] Bills, } impl Related for Entity { fn to() -> RelationDef { Relation::Bills.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/many_to_many_multiple/users_votes.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users_votes")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub user_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub bill_id: i32, pub user_idd: Option , pub bill_idd: Option , pub vote: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::bills::Entity", from = "Column::BillIdd", to = "super::bills::Column::Id", )] Bills2, #[sea_orm( belongs_to = "super::bills::Entity", from = "Column::BillId", to = "super::bills::Column::Id", )] Bills1, #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserIdd", to = "super::users::Column::Id", )] Users2, #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", )] Users1, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/mod.rs ================================================ #![allow(unused_imports, dead_code)] pub mod compact; pub mod dense; pub mod duplicated_many_to_many_paths; pub mod many_to_many; pub mod many_to_many_multiple; pub mod self_referencing; ================================================ FILE: sea-orm-codegen/src/tests_cfg/self_referencing/bills.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "bills")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub self_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "Entity", from = "Column::SelfId", to = "Column::Id", )] SelfRef, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/tests_cfg/self_referencing/mod.rs ================================================ pub mod prelude; pub mod bills; pub mod users; ================================================ FILE: sea-orm-codegen/src/tests_cfg/self_referencing/prelude.rs ================================================ pub use super::bills::Entity as Bills; pub use super::users::Entity as Users; ================================================ FILE: sea-orm-codegen/src/tests_cfg/self_referencing/users.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub self_id: Option , pub self_idd: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "Entity", from = "Column::SelfId", to = "Column::Id", )] SelfRef2, #[sea_orm( belongs_to = "Entity", from = "Column::SelfIdd", to = "Column::Id", )] SelfRef1, } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/src/util.rs ================================================ pub(crate) fn escape_rust_keyword(string: T) -> String where T: ToString, { let string = string.to_string(); if RUST_KEYWORDS.iter().any(|s| s.eq(&string)) { format!("r#{string}") } else if RUST_SPECIAL_KEYWORDS.iter().any(|s| s.eq(&string)) { format!("{string}_") } else { string } } pub(crate) const RUST_KEYWORDS: [&str; 49] = [ "as", "async", "await", "break", "const", "continue", "dyn", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "static", "struct", "super", "trait", "true", "type", "union", "unsafe", "use", "where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "try", "typeof", "unsized", "virtual", "yield", ]; pub(crate) const RUST_SPECIAL_KEYWORDS: [&str; 3] = ["crate", "Self", "self"]; ================================================ FILE: sea-orm-codegen/tests/compact/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "_cake_filling_")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub filling_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Cake, #[sea_orm( belongs_to = "super::filling::Entity", from = "Column::FillingId", to = "super::filling::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Filling, } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Filling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/cake_filling_price.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake_filling_price")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub filling_id: i32, pub price: Decimal, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::cake_filling::Entity", from = "(Column::CakeId, Column::FillingId)", to = "(super::cake_filling::Column::CakeId, super::cake_filling::Column::FillingId)", )] CakeFilling, } impl Related for Entity { fn to() -> RelationDef { Relation::CakeFilling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/cake_with_double.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "cake_with_double")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , #[sea_orm(column_type = "Double", nullable)] pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithDouble.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/cake_with_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "cake_with_float")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , #[sea_orm(column_type = "Float", nullable)] pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithFloat.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/child.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "child")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub parent_id1: i32, pub parent_id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::parent::Entity", from = "(Column::ParentId1, Column::ParentId2)", to = "(super::parent::Column::Id1, super::parent::Column::Id2)", )] Parent, } impl Related for Entity { fn to() -> RelationDef { Relation::Parent.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/collection.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "collection")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub integers: Vec , pub integers_opt: Option > , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/collection_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "collection_float")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub floats: Vec , pub doubles: Vec , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "filling")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Cake.def() } fn via() -> Option { Some(super::cake_filling::Relation::Filling.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "fruit")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub cake_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id", )] Cake, #[sea_orm(has_many = "super::vendor::Entity")] Vendor, } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Vendor.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub mod prelude; pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod vendor; ================================================ FILE: sea-orm-codegen/tests/compact/parent.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "parent")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::child::Entity")] Child, } impl Related for Entity { fn to() -> RelationDef { Relation::Child.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; pub use super::vendor::Entity as Vendor; ================================================ FILE: sea-orm-codegen/tests/compact/rust_keyword.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "rust_keyword")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub testing: i8, pub rust: u8, pub keywords: i16, pub r#type: u16, pub r#typeof: i32, pub crate_: u32, pub self_: i64, pub self_id1: u64, pub self_id2: i32, pub fruit_id1: i32, pub fruit_id2: i32, pub cake_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "Entity", from = "Column::SelfId1", to = "Column::Id", )] SelfRef1, #[sea_orm( belongs_to = "Entity", from = "Column::SelfId2", to = "Column::Id", )] SelfRef2, #[sea_orm( belongs_to = "super::fruit::Entity", from = "Column::FruitId1", to = "super::fruit::Column::Id", )] Fruit1, #[sea_orm( belongs_to = "super::fruit::Entity", from = "Column::FruitId2", to = "super::fruit::Column::Id", )] Fruit2, #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id", )] Cake, } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact/vendor.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "vendor")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_name = "_name_")] pub name: String, #[sea_orm(column_name = "fruitId")] pub fruit_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::fruit::Entity", from = "Column::FruitId", to = "super::fruit::Column::Id", )] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_attributes/cake_multiple.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_attributes/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_attributes/cake_one.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] #[serde(rename_all = "camelCase")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_derives/cake_multiple.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, ts_rs::TS, utoipa::ToSchema)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_derives/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_derives/cake_one.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, ts_rs::TS)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "_cake_filling_")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub filling_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Cake, #[sea_orm( belongs_to = "super::filling::Entity", from = "Column::FillingId", to = "super::filling::Column::Id", on_update = "Cascade", on_delete = "Cascade", )] Filling, } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Filling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/cake_filling_price.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "cake_filling_price")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub filling_id: i32, pub price: Decimal, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::cake_filling::Entity", from = "(Column::CakeId, Column::FillingId)", to = "(super::cake_filling::Column::CakeId, super::cake_filling::Column::FillingId)", )] CakeFilling, } impl Related for Entity { fn to() -> RelationDef { Relation::CakeFilling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/cake_with_double.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "cake_with_double")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , #[sea_orm(column_type = "Double", nullable)] pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithDouble.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/cake_with_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "cake_with_float")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , #[sea_orm(column_type = "Float", nullable)] pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithFloat.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/child.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "child")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub parent_id1: i32, pub parent_id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::parent::Entity", from = "(Column::ParentId1, Column::ParentId2)", to = "(super::parent::Column::Id1, super::parent::Column::Id2)", )] Parent, } impl Related for Entity { fn to() -> RelationDef { Relation::Parent.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/collection.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "collection")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub integers: Vec , pub integers_opt: Option > , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/collection_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "collection_float")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub floats: Vec , pub doubles: Vec , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "filling")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Cake.def() } fn via() -> Option { Some(super::cake_filling::Relation::Filling.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "fruit")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub cake_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id", )] Cake, #[sea_orm(has_many = "super::vendor::Entity")] Vendor, } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Vendor.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub mod prelude; pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod vendor; ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/parent.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "parent")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::child::Entity")] Child, } impl Related for Entity { fn to() -> RelationDef { Relation::Child.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; pub use super::vendor::Entity as Vendor; ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/rust_keyword.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "rust_keyword")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub testing: i8, pub rust: u8, pub keywords: i16, pub r#type: u16, pub r#typeof: i32, pub crate_: u32, pub self_: i64, pub self_id1: u64, pub self_id2: i32, pub fruit_id1: i32, pub fruit_id2: i32, pub cake_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "Entity", from = "Column::SelfId1", to = "Column::Id", )] SelfRef1, #[sea_orm( belongs_to = "Entity", from = "Column::SelfId2", to = "Column::Id", )] SelfRef2, #[sea_orm( belongs_to = "super::fruit::Entity", from = "Column::FruitId1", to = "super::fruit::Column::Id", )] Fruit1, #[sea_orm( belongs_to = "super::fruit::Entity", from = "Column::FruitId2", to = "super::fruit::Column::Id", )] Fruit2, #[sea_orm( belongs_to = "super::cake::Entity", from = "Column::CakeId", to = "super::cake::Column::Id", )] Cake, } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_schema_name/vendor.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(schema_name = "schema_name", table_name = "vendor")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_name = "_name_")] pub name: String, #[sea_orm(column_name = "fruitId")] pub fruit_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::fruit::Entity", from = "Column::FruitId", to = "super::fruit::Column::Id", )] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_serde/cake_both.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_serde/cake_deserialize.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::Deserialize; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_serde/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_serde/cake_serialize.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::Serialize; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/compact_with_serde/cake_serialize_with_hidden_column.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::Serialize; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_name = "_name", column_type = "Text", nullable)] #[serde(skip)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , #[sea_orm(has_many)] pub fruits: HasMany , #[sea_orm(has_many, via = "cake_filling")] pub fillings: HasMany , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "_cake_filling_")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub filling_id: i32, #[sea_orm(belongs_to, from = "cake_id", to = "id", on_update = "Cascade", on_delete = "Cascade")] pub cake: HasOne , #[sea_orm(belongs_to, from = "filling_id", to = "id", on_update = "Cascade", on_delete = "Cascade")] pub filling: HasOne , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/cake_filling_price.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake_filling_price")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub cake_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub filling_id: i32, pub price: Decimal, #[sea_orm( belongs_to, from = "(cake_id, filling_id)", to = "(cake_id, filling_id)" )] pub cake_filling: HasOne , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/cake_with_double.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "cake_with_double")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , #[sea_orm(column_type = "Double", nullable)] pub price: Option , #[sea_orm(has_many)] pub fruits: HasMany , #[sea_orm(has_many, via = "cake_filling")] pub fillings: HasMany , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/cake_with_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "cake_with_float")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , #[sea_orm(column_type = "Float", nullable)] pub price: Option , #[sea_orm(has_many)] pub fruits: HasMany , #[sea_orm(has_many, via = "cake_filling")] pub fillings: HasMany , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/child.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "child")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub parent_id1: i32, pub parent_id2: i32, #[sea_orm(belongs_to, from = "(parent_id1, parent_id2)", to = "(id1, id2)")] pub parent: HasOne , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/collection.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "collection")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub integers: Vec , pub integers_opt: Option > , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/collection_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "collection_float")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub floats: Vec , pub doubles: Vec , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "filling")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(has_many, via = "cake_filling")] pub cakes: HasMany , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "fruit")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub cake_id: Option , #[sea_orm(belongs_to, from = "cake_id", to = "id")] pub cake: HasOne , #[sea_orm(has_many)] pub vendors: HasMany , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub mod prelude; pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod vendor; ================================================ FILE: sea-orm-codegen/tests/dense/parent.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "parent")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id1: i32, #[sea_orm(primary_key, auto_increment = false)] pub id2: i32, #[sea_orm(has_many)] pub children: HasMany , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; pub use super::vendor::Entity as Vendor; ================================================ FILE: sea-orm-codegen/tests/dense/rust_keyword.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "rust_keyword")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub testing: i8, pub rust: u8, pub keywords: i16, pub r#type: u16, pub r#typeof: i32, pub crate_: u32, pub self_: i64, pub self_id1: u64, pub self_id2: i32, pub fruit_id1: i32, pub fruit_id2: i32, pub cake_id: i32, #[sea_orm(self_ref, relation_enum = "SelfRef1", from = "self_id1", to = "id")] pub rust_keyword_1: HasOne , #[sea_orm(self_ref, relation_enum = "SelfRef2", from = "self_id2", to = "id")] pub rust_keyword_2: HasOne , #[sea_orm(belongs_to, relation_enum = "Fruit1", from = "fruit_id1", to = "id")] pub fruit_1: HasOne , #[sea_orm(belongs_to, relation_enum = "Fruit2", from = "fruit_id2", to = "id")] pub fruit_2: HasOne , #[sea_orm(belongs_to, from = "cake_id", to = "id")] pub cake: HasOne , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/dense/vendor.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "vendor")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_name = "_name_")] pub name: String, #[sea_orm(column_name = "fruitId")] pub fruit_id: Option , #[sea_orm(belongs_to, from = "fruit_id", to = "id")] pub fruit: HasOne , } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "_cake_filling_" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { CakeId, FillingId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { CakeId, FillingId, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = (i32, i32); fn auto_increment() -> bool { false } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, Filling, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::CakeId => ColumnType::Integer.def(), Self::FillingId => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), Self::Filling => Entity::belongs_to(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Filling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/cake_filling_price.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake_filling_price" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, pub price: Decimal, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { CakeId, FillingId, Price, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { CakeId, FillingId, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = (i32, i32); fn auto_increment() -> bool { false } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { CakeFilling, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::CakeId => ColumnType::Integer.def(), Self::FillingId => ColumnType::Integer.def(), Self::Price => ColumnType::Decimal(None).def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::CakeFilling => Entity::belongs_to(super::cake_filling::Entity) .from((Column::CakeId, Column::FillingId)) .to(( super::cake_filling::Column::CakeId, super::cake_filling::Column::FillingId )) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::CakeFilling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/cake_with_double.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake_with_double" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, Price, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), Self::Price => ColumnType::Double.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithDouble.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/cake_with_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake_with_float" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, Price, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), Self::Price => ColumnType::Float.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithFloat.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/child.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "child" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub parent_id1: i32, pub parent_id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, ParentId1, ParentId2, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Parent, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::ParentId1 => ColumnType::Integer.def(), Self::ParentId2 => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Parent => Entity::belongs_to(super::parent::Entity) .from((Column::ParentId1, Column::ParentId2)) .to((super::parent::Column::Id1, super::parent::Column::Id2)) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Parent.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/collection.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "collection" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub integers: Vec , pub integers_opt: Option > , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Integers, IntegersOpt, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Integers => ColumnType::Array(RcOrArc::new(ColumnType::Integer)).def(), Self::IntegersOpt => ColumnType::Array(RcOrArc::new(ColumnType::Integer)).def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/collection_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "collection_float" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] pub struct Model { pub id: i32, pub floats: Vec , pub doubles: Vec , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Floats, Doubles, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Floats => ColumnType::Array(RcOrArc::new(ColumnType::Float)).def(), Self::Doubles => ColumnType::Array(RcOrArc::new(ColumnType::Double)).def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "filling" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(StringLen::N(255u32)).def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Cake.def() } fn via() -> Option { Some(super::cake_filling::Relation::Filling.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "fruit" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: String, pub cake_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, CakeId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, Vendor, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(StringLen::N(255u32)).def(), Self::CakeId => ColumnType::Integer.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), Self::Vendor => Entity::has_many(super::vendor::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Vendor.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub mod prelude; pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod vendor; ================================================ FILE: sea-orm-codegen/tests/expanded/parent.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "parent" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id1: i32, pub id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id1, Id2, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id1, Id2, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = (i32, i32); fn auto_increment() -> bool { false } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Child, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id1 => ColumnType::Integer.def(), Self::Id2 => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Child => Entity::has_many(super::child::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Child.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; pub use super::vendor::Entity as Vendor; ================================================ FILE: sea-orm-codegen/tests/expanded/rust_keyword.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "rust_keyword" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub testing: i8, pub rust: u8, pub keywords: i16, pub r#type: u16, pub r#typeof: i32, pub crate_: u32, pub self_: i64, pub self_id1: u64, pub self_id2: i32, pub fruit_id1: i32, pub fruit_id2: i32, pub cake_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Testing, Rust, Keywords, Type, Typeof, Crate, Self_, SelfId1, SelfId2, FruitId1, FruitId2, CakeId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { SelfRef1, SelfRef2, Fruit1, Fruit2, Cake, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Testing => ColumnType::TinyInteger.def(), Self::Rust => ColumnType::TinyUnsigned.def(), Self::Keywords => ColumnType::SmallInteger.def(), Self::Type => ColumnType::SmallUnsigned.def(), Self::Typeof => ColumnType::Integer.def(), Self::Crate => ColumnType::Unsigned.def(), Self::Self_ => ColumnType::BigInteger.def(), Self::SelfId1 => ColumnType::BigUnsigned.def(), Self::SelfId2 => ColumnType::Integer.def(), Self::FruitId1 => ColumnType::Integer.def(), Self::FruitId2 => ColumnType::Integer.def(), Self::CakeId => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::SelfRef1 => Entity::belongs_to(Entity) .from(Column::SelfId1) .to(Column::Id) .into(), Self::SelfRef2 => Entity::belongs_to(Entity) .from(Column::SelfId2) .to(Column::Id) .into(), Self::Fruit1 => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId1) .to(super::fruit::Column::Id) .into(), Self::Fruit2 => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId2) .to(super::fruit::Column::Id) .into(), Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded/vendor.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "vendor" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: String, pub fruit_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, #[sea_orm(column_name = "_name_")] Name, #[sea_orm(column_name = "fruitId")] FruitId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(StringLen::N(255u32)).def(), Self::FruitId => ColumnType::Integer.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId) .to(super::fruit::Column::Id) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_attributes/cake_multiple.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_attributes/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_attributes/cake_one.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] #[serde(rename_all = "camelCase")] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_column_derives/cake_multiple.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn, async_graphql::Enum, Eq, PartialEq)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_column_derives/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_column_derives/cake_one.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn, async_graphql::Enum)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_derives/cake_multiple.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, ts_rs::TS, utoipa::ToSchema)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_derives/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_derives/cake_one.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, ts_rs::TS)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "_cake_filling_" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { CakeId, FillingId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { CakeId, FillingId, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = (i32, i32); fn auto_increment() -> bool { false } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, Filling, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::CakeId => ColumnType::Integer.def(), Self::FillingId => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), Self::Filling => Entity::belongs_to(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Filling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/cake_filling_price.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "cake_filling_price" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, pub price: Decimal, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { CakeId, FillingId, Price, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { CakeId, FillingId, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = (i32, i32); fn auto_increment() -> bool { false } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { CakeFilling, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::CakeId => ColumnType::Integer.def(), Self::FillingId => ColumnType::Integer.def(), Self::Price => ColumnType::Decimal(None).def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::CakeFilling => Entity::belongs_to(super::cake_filling::Entity) .from((Column::CakeId, Column::FillingId)) .to(( super::cake_filling::Column::CakeId, super::cake_filling::Column::FillingId )) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::CakeFilling.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/cake_with_double.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "cake_with_double" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, Price, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), Self::Price => ColumnType::Double.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithDouble.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/cake_with_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "cake_with_float" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, Price, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), Self::Price => ColumnType::Float.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::CakeWithFloat.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/child.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "child" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub parent_id1: i32, pub parent_id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, ParentId1, ParentId2, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Parent, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::ParentId1 => ColumnType::Integer.def(), Self::ParentId2 => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Parent => Entity::belongs_to(super::parent::Entity) .from((Column::ParentId1, Column::ParentId2)) .to((super::parent::Column::Id1, super::parent::Column::Id2)) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Parent.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/collection.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "collection" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub integers: Vec , pub integers_opt: Option > , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Integers, IntegersOpt, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Integers => ColumnType::Array(RcOrArc::new(ColumnType::Integer)).def(), Self::IntegersOpt => ColumnType::Array(RcOrArc::new(ColumnType::Integer)).def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/collection_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "collection_float" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] pub struct Model { pub id: i32, pub floats: Vec , pub doubles: Vec , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Floats, Doubles, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Floats => ColumnType::Array(RcOrArc::new(ColumnType::Float)).def(), Self::Doubles => ColumnType::Array(RcOrArc::new(ColumnType::Double)).def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "filling" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(StringLen::N(255u32)).def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Cake.def() } fn via() -> Option { Some(super::cake_filling::Relation::Filling.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "fruit" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: String, pub cake_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, CakeId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, Vendor, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(StringLen::N(255u32)).def(), Self::CakeId => ColumnType::Integer.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), Self::Vendor => Entity::has_many(super::vendor::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Vendor.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub mod prelude; pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod vendor; ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/parent.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "parent" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id1: i32, pub id2: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id1, Id2, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id1, Id2, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = (i32, i32); fn auto_increment() -> bool { false } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Child, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id1 => ColumnType::Integer.def(), Self::Id2 => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Child => Entity::has_many(super::child::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Child.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; pub use super::vendor::Entity as Vendor; ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/rust_keyword.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "rust_keyword" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub testing: i8, pub rust: u8, pub keywords: i16, pub r#type: u16, pub r#typeof: i32, pub crate_: u32, pub self_: i64, pub self_id1: u64, pub self_id2: i32, pub fruit_id1: i32, pub fruit_id2: i32, pub cake_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Testing, Rust, Keywords, Type, Typeof, Crate, Self_, SelfId1, SelfId2, FruitId1, FruitId2, CakeId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { SelfRef1, SelfRef2, Fruit1, Fruit2, Cake, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Testing => ColumnType::TinyInteger.def(), Self::Rust => ColumnType::TinyUnsigned.def(), Self::Keywords => ColumnType::SmallInteger.def(), Self::Type => ColumnType::SmallUnsigned.def(), Self::Typeof => ColumnType::Integer.def(), Self::Crate => ColumnType::Unsigned.def(), Self::Self_ => ColumnType::BigInteger.def(), Self::SelfId1 => ColumnType::BigUnsigned.def(), Self::SelfId2 => ColumnType::Integer.def(), Self::FruitId1 => ColumnType::Integer.def(), Self::FruitId2 => ColumnType::Integer.def(), Self::CakeId => ColumnType::Integer.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::SelfRef1 => Entity::belongs_to(Entity) .from(Column::SelfId1) .to(Column::Id) .into(), Self::SelfRef2 => Entity::belongs_to(Entity) .from(Column::SelfId2) .to(Column::Id) .into(), Self::Fruit1 => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId1) .to(super::fruit::Column::Id) .into(), Self::Fruit2 => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId2) .to(super::fruit::Column::Id) .into(), Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Cake.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_schema_name/vendor.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "vendor" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: String, pub fruit_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, #[sea_orm(column_name = "_name_")] Name, #[sea_orm(column_name = "fruitId")] FruitId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(StringLen::N(255u32)).def(), Self::FruitId => ColumnType::Integer.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId) .to(super::fruit::Column::Id) .into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_serde/cake_both.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::{Deserialize,Serialize}; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Serialize, Deserialize)] pub struct Model { #[serde(skip_deserializing)] pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_serde/cake_deserialize.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::Deserialize; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Deserialize)] pub struct Model { #[serde(skip_deserializing)] pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_serde/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_serde/cake_serialize.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::Serialize; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Serialize)] pub struct Model { pub id: i32, pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/expanded_with_serde/cake_serialize_with_hidden_column.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; use serde::Serialize; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Serialize)] pub struct Model { pub id: i32, #[serde(skip)] pub name: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, #[sea_orm(column_name = "_name")] Name, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/frontend/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend/cake_filling_price.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, pub price: Decimal, } ================================================ FILE: sea-orm-codegen/tests/frontend/cake_with_double.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend/cake_with_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend/child.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub parent_id1: i32, pub parent_id2: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend/collection.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub integers: Vec , pub integers_opt: Option > , } ================================================ FILE: sea-orm-codegen/tests/frontend/collection_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 #[derive(Clone, Debug, PartialEq)] pub struct Model { pub id: i32, pub floats: Vec , pub doubles: Vec , } ================================================ FILE: sea-orm-codegen/tests/frontend/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: String, } ================================================ FILE: sea-orm-codegen/tests/frontend/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: String, pub cake_id: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub mod prelude; pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod vendor; ================================================ FILE: sea-orm-codegen/tests/frontend/parent.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id1: i32, pub id2: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub use super::cake::Model as Cake; pub use super::cake_filling::Model as CakeFilling; pub use super::filling::Model as Filling; pub use super::fruit::Model as Fruit; pub use super::vendor::Model as Vendor; ================================================ FILE: sea-orm-codegen/tests/frontend/rust_keyword.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub testing: i8, pub rust: u8, pub keywords: i16, pub r#type: u16, pub r#typeof: i32, pub crate_: u32, pub self_: i64, pub self_id1: u64, pub self_id2: i32, pub fruit_id1: i32, pub fruit_id2: i32, pub cake_id: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend/vendor.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: String, pub fruit_id: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_attributes/cake_multiple.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_attributes/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_attributes/cake_one.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_derives/cake_multiple.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq, ts_rs::TS, utoipa::ToSchema)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_derives/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_derives/cake_one.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq, ts_rs::TS)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/cake_filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/cake_filling_price.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub cake_id: i32, pub filling_id: i32, pub price: Decimal, } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/cake_with_double.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/cake_with_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq)] pub struct Model { pub id: i32, pub name: Option , pub price: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/child.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub parent_id1: i32, pub parent_id2: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/collection.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub integers: Vec , pub integers_opt: Option > , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/collection_float.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 #[derive(Clone, Debug, PartialEq)] pub struct Model { pub id: i32, pub floats: Vec , pub doubles: Vec , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/filling.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: String, } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/fruit.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: String, pub cake_id: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub mod prelude; pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; pub mod vendor; ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/parent.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id1: i32, pub id2: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/prelude.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 pub use super::cake::Model as Cake; pub use super::cake_filling::Model as CakeFilling; pub use super::filling::Model as Filling; pub use super::fruit::Model as Fruit; pub use super::vendor::Model as Vendor; ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/rust_keyword.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub testing: i8, pub rust: u8, pub keywords: i16, pub r#type: u16, pub r#typeof: i32, pub crate_: u32, pub self_: i64, pub self_id1: u64, pub self_id2: i32, pub fruit_id1: i32, pub fruit_id2: i32, pub cake_id: i32, } ================================================ FILE: sea-orm-codegen/tests/frontend_with_schema_name/vendor.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: String, pub fruit_id: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_serde/cake_both.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Model { #[serde(skip_deserializing)] pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_serde/cake_deserialize.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use serde::Deserialize; #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] pub struct Model { #[serde(skip_deserializing)] pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_serde/cake_none.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_serde/cake_serialize.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use serde::Serialize; #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct Model { pub id: i32, pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/frontend_with_serde/cake_serialize_with_hidden_column.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use serde::Serialize; #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct Model { pub id: i32, #[serde(skip)] pub name: Option , } ================================================ FILE: sea-orm-codegen/tests/postgres/binary_json.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 //! //! This file tests that the JsonBinary column type is annotated correctly is //! compact entity form. More information can be found in this issue: //! //! https://github.com/SeaQL/sea-orm/issues/1344 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "task")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub payload: Json, #[sea_orm(column_type = "JsonBinary")] pub payload_binary: Json, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/postgres/binary_json_expanded.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 //! //! This file tests that the JsonBinary column type is annotated correctly is //! expanded entity form. More information can be found in this issue: //! //! https://github.com/SeaQL/sea-orm/issues/1344 use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn schema_name(&self) -> Option< &str > { Some("schema_name") } fn table_name(&self) -> & 'static str { "task" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub payload: Json, pub payload_binary: Json, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Payload, PayloadBinary, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation {} impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), // This is the part that is being tested. Self::Payload => ColumnType::Json.def(), Self::PayloadBinary => ColumnType::JsonBinary.def(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { panic!("No RelationDef") } } impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-codegen/tests/with_seaography/cake.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(column_type = "Text", nullable)] pub name: Option , pub base_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::fruit::Entity")] Fruit, #[sea_orm(has_one = "Entity")] SelfRef , } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity { #[sea_orm(entity = "super::fruit::Entity")] Fruit, #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] SelfRef, #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] SelfRefReverse, #[sea_orm(entity = "super::filling::Entity")] Filling } ================================================ FILE: sea-orm-codegen/tests/with_seaography/cake_expanded.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 use sea_orm::entity::prelude:: * ; #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> & 'static str { "cake" } } #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] pub struct Model { pub id: i32, pub name: Option , pub base_id: Option , } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, BaseId, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { Id, } impl PrimaryKeyTrait for PrimaryKey { type ValueType = i32; fn auto_increment() -> bool { true } } #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, SelfRef , } impl ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> ColumnDef { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::Text.def().null(), Self::BaseId => ColumnType::Integer.def().null(), } } } impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { Self::Fruit => Entity::has_many(super::fruit::Entity).into(), Self::SelfRef => Entity::has_one(Entity).into(), } } } impl Related for Entity { fn to() -> RelationDef { Relation::Fruit.def() } } impl Related for Entity { fn to() -> RelationDef { super::cake_filling::Relation::Filling.def() } fn via() -> Option { Some(super::cake_filling::Relation::Cake.def().rev()) } } impl ActiveModelBehavior for ActiveModel {} #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity { #[sea_orm(entity = "super::fruit::Entity")] Fruit, #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] SelfRef, #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] SelfRefReverse, #[sea_orm(entity = "super::filling::Entity")] Filling } ================================================ FILE: sea-orm-codegen/tests/with_seaography/cake_frontend.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 #[derive(Clone, Debug, PartialEq, Eq)] pub struct Model { pub id: i32, pub name: Option , pub base_id: Option , } ================================================ FILE: sea-orm-codegen/tests/with_seaography/mod.rs ================================================ //! SeaORM Entity. Generated by sea-orm-codegen 0.1.0 seaography::register_entity_modules!([ cake, cake_filling, cake_filling_price, filling, fruit, vendor, rust_keyword, cake_with_float, cake_with_double, collection, collection_float, parent, child, ]); seaography::register_active_enums!([ sea_orm_active_enums::CoinflipResultType, sea_orm_active_enums::MediaType, ]); ================================================ FILE: sea-orm-macros/Cargo.toml ================================================ [package] authors = ["Billy Chan "] categories = ["database"] description = "Derive macros for SeaORM" documentation = "https://docs.rs/sea-orm" edition = "2024" homepage = "https://www.sea-ql.org/SeaORM" keywords = ["async", "orm", "mysql", "postgres", "sqlite"] license = "MIT OR Apache-2.0" name = "sea-orm-macros" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "2.0.0-rc.37" [lib] name = "sea_orm_macros" path = "src/lib.rs" proc-macro = true [dependencies] bae = { version = "0.2", package = "sea-bae", default-features = false, optional = true } heck = { version = "0.5", default-features = false } itertools = "0.14" pluralizer = { version = "0.5" } proc-macro-crate = { version = "3.2.0", optional = true } proc-macro2 = { version = "1", default-features = false } quote = { version = "1", default-features = false } syn = { version = "2", default-features = false, features = [ "parsing", "proc-macro", "derive", "printing", ] } unicode-ident = { version = "1" } [dev-dependencies] sea-orm = { path = "../", default-features = false, features = [ "macros", "tests-cfg", ] } serde = { version = "1.0", features = ["derive"] } [features] async = [] default = ["derive"] derive = ["bae"] entity-registry = [] postgres-array = [] seaography = ["proc-macro-crate"] strum = [] with-arrow = [] with-json = [] ================================================ FILE: sea-orm-macros/src/derives/active_enum.rs ================================================ use super::case_style::{CaseStyle, CaseStyleHelpers}; use super::util::camel_case_with_escaped_non_uax31; use heck::ToUpperCamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use syn::{Expr, Lit, LitInt, LitStr, UnOp, parse}; struct ActiveEnum { ident: syn::Ident, enum_name: String, rs_type: TokenStream, db_type: TokenStream, is_string: bool, variants: Vec, rename_all: Option, } struct ActiveEnumVariant { ident: syn::Ident, string_value: Option, num_value: Option, rename: Option, } enum Error { InputNotEnum, Syn(syn::Error), TT(TokenStream), } impl ActiveEnum { fn new(input: syn::DeriveInput) -> Result { let ident_span = input.ident.span(); let ident = input.ident; let mut enum_name = ident.to_string().to_upper_camel_case(); let mut rs_type = Err(Error::TT(quote_spanned! { ident_span => compile_error!("Missing macro attribute `rs_type`"); })); let mut db_type = Err(Error::TT(quote_spanned! { ident_span => compile_error!("Missing macro attribute `db_type`"); })); let mut rename_all = None; input .attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("rs_type") { let litstr: LitStr = meta.value()?.parse()?; rs_type = syn::parse_str::(&litstr.value()).map_err(Error::Syn); } else if meta.path.is_ident("db_type") { let litstr: LitStr = meta.value()?.parse()?; let s = litstr.value(); match s.as_ref() { "Enum" => { db_type = Ok(quote! { Enum { name: ::name(), variants: Self::iden_values(), } }) } _ => { db_type = syn::parse_str::(&s).map_err(Error::Syn); } } } else if meta.path.is_ident("enum_name") { let litstr: LitStr = meta.value()?.parse()?; enum_name = litstr.value(); } else if meta.path.is_ident("rename_all") { rename_all = Some((&meta).try_into()?); } else { return Err(meta.error(format!( "Unknown attribute parameter found: {:?}", meta.path.get_ident() ))); } Ok(()) }) .map_err(Error::Syn) })?; let variant_vec = match input.data { syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, _ => return Err(Error::InputNotEnum), }; let mut is_string = rename_all.is_some(); let mut is_int = false; let mut variants = Vec::new(); for variant in variant_vec { let variant_span = variant.ident.span(); let mut string_value = None; let mut num_value = None; let mut rename_rule = None; for attr in variant.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } attr.parse_nested_meta(|meta| { if meta.path.is_ident("string_value") { is_string = true; string_value = Some(meta.value()?.parse::()?); } else if meta.path.is_ident("num_value") { is_int = true; num_value = Some(meta.value()?.parse::()?); } else if meta.path.is_ident("display_value") { // This is a placeholder to prevent the `display_value` proc_macro attribute of `DeriveDisplay` // to be considered unknown attribute parameter meta.value()?.parse::()?; } else if meta.path.is_ident("rename") { is_string = true; rename_rule = Some((&meta).try_into()?); } else { return Err(meta.error(format!( "Unknown attribute parameter found: {:?}", meta.path.get_ident() ))); } Ok(()) }) .map_err(Error::Syn)?; } if is_string && is_int { return Err(Error::TT(quote_spanned! { ident_span => compile_error!("All enum variants should specify the same `*_value` macro attribute, either `string_value` or `num_value` but not both"); })); } if string_value.is_none() && num_value.is_none() && rename_rule.or(rename_all).is_none() { match variant.discriminant { Some((_, Expr::Lit(exprlit))) => { if let Lit::Int(litint) = exprlit.lit { is_int = true; num_value = Some(litint); } else { return Err(Error::TT(quote_spanned! { variant_span => compile_error!("Enum variant discriminant is not an integer"); })); } } //rust doesn't provide negative variants in enums as a single LitInt, this workarounds that Some((_, Expr::Unary(exprnlit))) => { if let UnOp::Neg(_) = exprnlit.op { if let Expr::Lit(exprlit) = *exprnlit.expr { if let Lit::Int(litint) = exprlit.lit { let negative_token = quote! { -#litint }; let litint = parse(negative_token.into()).unwrap(); is_int = true; num_value = Some(litint); } } } else { return Err(Error::TT(quote_spanned! { variant_span => compile_error!("Only - token is supported in enum variants, not ! and *"); })); } } _ => { return Err(Error::TT(quote_spanned! { variant_span => compile_error!("Missing macro attribute, either `string_value`, `num_value` or `rename` should be specified or specify repr[X] and have a value for every entry"); })); } } } variants.push(ActiveEnumVariant { ident: variant.ident, string_value, num_value, rename: rename_rule, }); } Ok(ActiveEnum { ident, enum_name, rs_type: rs_type?, db_type: db_type?, is_string, variants, rename_all, }) } fn expand(&self) -> syn::Result { let expanded_impl_active_enum = self.impl_active_enum(); Ok(expanded_impl_active_enum) } fn impl_active_enum(&self) -> TokenStream { let Self { ident, enum_name, rs_type, db_type, is_string, variants, rename_all, } = self; let variant_idents: Vec = variants .iter() .map(|variant| variant.ident.clone()) .collect(); let variant_values: Vec = variants .iter() .map(|variant| { let variant_span = variant.ident.span(); if let Some(string_value) = &variant.string_value { let string = string_value.value(); quote! { #string } } else if let Some(num_value) = &variant.num_value { quote! { #num_value } } else if let Some(rename_rule) = variant.rename.or(*rename_all) { let variant_ident = variant.ident.convert_case(Some(rename_rule)); quote! { #variant_ident } } else { quote_spanned! { variant_span => compile_error!("Missing macro attribute, either `string_value`, `num_value` or `rename_all` should be specified"); } } }) .collect(); let val = if *is_string { quote! { v.as_ref() } } else { quote! { v } }; let enum_name_iden = format_ident!("{}Enum", ident); let str_variants: Vec = variants .iter() .filter_map(|variant| { variant .string_value .as_ref() .map(|string_value| string_value.value()) .or(variant .rename .map(|rename| variant.ident.convert_case(Some(rename)))) .or_else(|| rename_all.map(|rule| variant.ident.convert_case(Some(rule)))) }) .collect(); let impl_enum_variant_iden = if !str_variants.is_empty() { let enum_variant_iden = format_ident!("{}Variant", ident); let enum_variants: Vec = str_variants .iter() .map(|v| { let v_cleaned = camel_case_with_escaped_non_uax31(v); format_ident!("{}", v_cleaned) }) .collect(); quote!( #[doc = " Generated by sea-orm-macros"] #[derive(Debug, Clone, PartialEq, Eq, sea_orm::EnumIter)] pub enum #enum_variant_iden { #( #[doc = " Generated by sea-orm-macros"] #enum_variants, )* } #[automatically_derived] impl sea_orm::Iden for #enum_variant_iden { fn unquoted(&self) -> &str { match self { #( Self::#enum_variants => #str_variants, )* } } } #[automatically_derived] impl #ident { #[doc = " Generated by sea-orm-macros"] pub fn iden_values() -> Vec { <#enum_variant_iden as sea_orm::strum::IntoEnumIterator>::iter() .map(|v| sea_orm::sea_query::SeaRc::new(v) as sea_orm::sea_query::DynIden) .collect() } } ) } else { quote!() }; let impl_not_u8 = if cfg!(feature = "postgres-array") { quote!( #[automatically_derived] impl sea_orm::sea_query::postgres_array::NotU8 for #ident {} ) } else { quote!() }; let impl_try_getable_array = if cfg!(feature = "postgres-array") { quote!( #[automatically_derived] impl sea_orm::TryGetableArray for #ident { fn try_get_by(res: &sea_orm::QueryResult, index: I) -> std::result::Result, sea_orm::TryGetError> { <::Value as sea_orm::ActiveEnumValue>::try_get_vec_by(res, index)? .into_iter() .map(|value| ::try_from_value(&value).map_err(Into::into)) .collect() } } ) } else { quote!() }; quote!( #[doc = " Generated by sea-orm-macros"] #[derive(Debug, Clone, PartialEq, Eq)] pub struct #enum_name_iden; #[automatically_derived] impl sea_orm::Iden for #enum_name_iden { fn unquoted(&self) -> &str { #enum_name } } #impl_enum_variant_iden #[automatically_derived] impl sea_orm::ActiveEnum for #ident { type Value = #rs_type; type ValueVec = Vec<#rs_type>; fn name() -> sea_orm::sea_query::DynIden { sea_orm::sea_query::SeaRc::new(#enum_name_iden) as sea_orm::sea_query::DynIden } fn to_value(&self) -> ::Value { match self { #( Self::#variant_idents => #variant_values, )* } .to_owned() } fn try_from_value(v: &::Value) -> std::result::Result { match #val { #( #variant_values => Ok(Self::#variant_idents), )* _ => Err(sea_orm::DbErr::Type(format!( "unexpected value for {} enum: {}", stringify!(#ident), v ))), } } fn db_type() -> sea_orm::ColumnDef { sea_orm::prelude::ColumnTypeTrait::def(sea_orm::ColumnType::#db_type) } } #impl_try_getable_array #[automatically_derived] #[allow(clippy::from_over_into)] impl Into for #ident { fn into(self) -> sea_orm::sea_query::Value { ::to_value(&self).into() } } #[automatically_derived] impl sea_orm::TryGetable for #ident { fn try_get_by(res: &sea_orm::QueryResult, idx: I) -> std::result::Result { let value = <::Value as sea_orm::TryGetable>::try_get_by(res, idx)?; ::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr) } } #[automatically_derived] impl sea_orm::sea_query::ValueType for #ident { fn try_from(v: sea_orm::sea_query::Value) -> std::result::Result { let value = <::Value as sea_orm::sea_query::ValueType>::try_from(v)?; ::try_from_value(&value).map_err(|_| sea_orm::sea_query::ValueTypeErr) } fn type_name() -> String { <::Value as sea_orm::sea_query::ValueType>::type_name() } fn array_type() -> sea_orm::sea_query::ArrayType { <::Value as sea_orm::sea_query::ValueType>::array_type() } fn column_type() -> sea_orm::sea_query::ColumnType { ::db_type() .get_column_type() .to_owned() .into() } fn enum_type_name() -> Option<&'static str> { Some(stringify!(#ident)) } } #[automatically_derived] impl sea_orm::sea_query::Nullable for #ident { fn null() -> sea_orm::sea_query::Value { <::Value as sea_orm::sea_query::Nullable>::null() } } #[automatically_derived] impl sea_orm::IntoActiveValue<#ident> for #ident { fn into_active_value(self) -> sea_orm::ActiveValue<#ident> { sea_orm::ActiveValue::set(self) } } #impl_not_u8 ) } } pub fn expand_derive_active_enum(input: syn::DeriveInput) -> syn::Result { let ident_span = input.ident.span(); match ActiveEnum::new(input) { Ok(model) => model.expand(), Err(Error::InputNotEnum) => Ok(quote_spanned! { ident_span => compile_error!("you can only derive ActiveEnum on enums"); }), Err(Error::TT(token_stream)) => Ok(token_stream), Err(Error::Syn(e)) => Err(e), } } ================================================ FILE: sea-orm-macros/src/derives/active_enum_display.rs ================================================ use super::case_style::CaseStyle; use proc_macro2::TokenStream; use quote::{ToTokens, quote, quote_spanned}; use syn::{LitInt, LitStr}; enum Error { InputNotEnum, Syn(syn::Error), } struct Display { ident: syn::Ident, variants: Vec, } struct DisplayVariant { ident: syn::Ident, display_value: TokenStream, } impl Display { fn new(input: syn::DeriveInput) -> Result { let ident = input.ident; let variant_vec = match input.data { syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, _ => return Err(Error::InputNotEnum), }; let mut variants = Vec::new(); for variant in variant_vec { let mut display_value = variant.ident.to_string().to_token_stream(); for attr in variant.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } attr.parse_nested_meta(|meta| { if meta.path.is_ident("string_value") { meta.value()?.parse::()?; } else if meta.path.is_ident("num_value") { meta.value()?.parse::()?; } else if meta.path.is_ident("display_value") { display_value = meta.value()?.parse::()?.to_token_stream(); } else if meta.path.is_ident("rename") { CaseStyle::try_from(&meta)?; } else { return Err(meta.error(format!( "Unknown attribute parameter found: {:?}", meta.path.get_ident() ))); } Ok(()) }) .map_err(Error::Syn)?; } variants.push(DisplayVariant { ident: variant.ident, display_value, }); } Ok(Display { ident, variants }) } fn expand(&self) -> syn::Result { let expanded_impl_active_enum_display = self.impl_active_enum_display(); Ok(expanded_impl_active_enum_display) } fn impl_active_enum_display(&self) -> TokenStream { let Self { ident, variants } = self; let variant_idents: Vec<_> = variants .iter() .map(|variant| variant.ident.clone()) .collect(); let variant_display: Vec<_> = variants .iter() .map(|variant| variant.display_value.to_owned()) .collect(); quote!( #[automatically_derived] impl std::fmt::Display for #ident { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", match self { #( Self::#variant_idents => #variant_display, )* }) } } ) } } pub fn expand_derive_active_enum_display(input: syn::DeriveInput) -> syn::Result { let ident_span = input.ident.span(); match Display::new(input) { Ok(model) => model.expand(), Err(Error::InputNotEnum) => Ok(quote_spanned! { ident_span => compile_error!("you can only derive EnumDisplay on enums"); }), Err(Error::Syn(e)) => Err(e), } } ================================================ FILE: sea-orm-macros/src/derives/active_model.rs ================================================ use super::util::{ escape_rust_keyword, field_not_ignored, format_field_ident, trim_starting_raw_identifier, }; use heck::ToUpperCamelCase; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{Data, DataStruct, Expr, Fields, LitStr, Type}; pub(crate) struct DeriveActiveModel { model: Ident, fields: Vec, names: Vec, types: Vec, } impl DeriveActiveModel { pub fn new(ident: &Ident, data: &Data) -> syn::Result { let all_fields = match data { Data::Struct(DataStruct { fields: Fields::Named(named), .. }) => &named.named, _ => { return Err(syn::Error::new_spanned( ident, "You can only derive DeriveActiveModel on structs", )); } }; let mut fields = Vec::new(); let mut names = Vec::new(); let mut types = Vec::new(); for field in all_fields.iter().filter(|f| field_not_ignored(f)) { fields.push(format_field_ident(field)); let ident = field.ident.as_ref().unwrap().to_string(); let ident = trim_starting_raw_identifier(ident).to_upper_camel_case(); let ident = escape_rust_keyword(ident); let mut ident = format_ident!("{}", &ident); field .attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("enum_name") { let litstr: LitStr = meta.value()?.parse()?; ident = syn::parse_str(&litstr.value()).unwrap(); } else { // Reads the value expression to advance the parse stream. // Some parameters, such as `primary_key`, do not have any value, // so ignoring an error occurred here. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) }) })?; names.push(ident); types.push(field.ty.clone()); } Ok(DeriveActiveModel { model: ident.clone(), fields, names, types, }) } } impl DeriveActiveModel { fn define_active_model(&self) -> TokenStream { let fields = &self.fields; let types = &self.types; quote!( #[doc = " Generated by sea-orm-macros"] #[derive(Clone, Debug, PartialEq)] pub struct ActiveModel { #( #[doc = " Generated by sea-orm-macros"] pub #fields: sea_orm::ActiveValue<#types> ),* } ) } fn impl_active_model(&self) -> TokenStream { let mut ts = self.impl_active_model_convert(); ts.extend(self.impl_active_model_trait()); ts } fn impl_active_model_convert(&self) -> TokenStream { let model = &self.model; let fields = &self.fields; quote!( #[automatically_derived] impl std::default::Default for ActiveModel { fn default() -> Self { ::new() } } #[automatically_derived] impl std::convert::From<#model> for ActiveModel { fn from(m: #model) -> Self { Self { #(#fields: sea_orm::ActiveValue::Unchanged(m.#fields)),* } } } #[automatically_derived] impl sea_orm::IntoActiveModel for #model { fn into_active_model(self) -> ActiveModel { self.into() } } ) } fn impl_active_model_trait(&self) -> TokenStream { let fields = &self.fields; let methods = self.impl_active_model_trait_methods(); quote! { #[automatically_derived] impl sea_orm::ActiveModelTrait for ActiveModel { type Entity = Entity; #methods fn default() -> Self { Self { #(#fields: sea_orm::ActiveValue::NotSet),* } } } } } pub fn impl_active_model_trait_methods(&self) -> TokenStream { let fields = &self.fields; let names = &self.names; quote!( fn take(&mut self, c: ::Column) -> sea_orm::ActiveValue { match c { #(::Column::#names => { let mut value = sea_orm::ActiveValue::NotSet; std::mem::swap(&mut value, &mut self.#fields); value.into_wrapped_value() },)* _ => sea_orm::ActiveValue::NotSet, } } fn get(&self, c: ::Column) -> sea_orm::ActiveValue { match c { #(::Column::#names => self.#fields.clone().into_wrapped_value(),)* _ => sea_orm::ActiveValue::NotSet, } } fn set_if_not_equals(&mut self, c: ::Column, v: sea_orm::Value) { match c { #(::Column::#names => self.#fields.set_if_not_equals(v.unwrap()),)* _ => (), } } fn try_set(&mut self, c: ::Column, v: sea_orm::Value) -> Result<(), sea_orm::DbErr> { match c { #(::Column::#names => self.#fields = sea_orm::ActiveValue::Set(sea_orm::sea_query::ValueType::try_from(v).map_err(|e| sea_orm::DbErr::Type(e.to_string()))?),)* _ => return Err(sea_orm::DbErr::Type(format!("ActiveModel does not have this field: {:?}", sea_orm::ColumnTrait::as_column_ref(&c)))), } Ok(()) } fn not_set(&mut self, c: ::Column) { match c { #(::Column::#names => self.#fields = sea_orm::ActiveValue::NotSet,)* _ => (), } } fn is_not_set(&self, c: ::Column) -> bool { match c { #(::Column::#names => self.#fields.is_not_set(),)* _ => panic!("This ActiveModel does not have this field"), } } fn reset(&mut self, c: ::Column) { match c { #(::Column::#names => self.#fields.reset(),)* _ => panic!("This ActiveModel does not have this field"), } } fn default_values() -> Self { use sea_orm::value::{DefaultActiveValue, DefaultActiveValueNone, DefaultActiveValueNotSet}; let mut default = ::default(); #(default.#fields = (&default.#fields).default_value();)* default } ) } } fn derive_into_model(ident: &Ident, data: &Data) -> syn::Result { let model_fields = match data { Data::Struct(DataStruct { fields: Fields::Named(named), .. }) => &named.named, _ => { return Err(syn::Error::new_spanned( ident, "You can only derive DeriveActiveModel on structs", )); } }; let active_model_field: Vec = model_fields .iter() .filter(|f| field_not_ignored(f)) .map(format_field_ident) .collect(); let model_field: Vec = model_fields.iter().map(format_field_ident).collect(); let ignore_attr: Vec = model_fields.iter().map(|f| !field_not_ignored(f)).collect(); let model_field_value: Vec = model_field .iter() .zip(ignore_attr) .map(|(field, ignore)| { if ignore { quote! { Default::default() } } else { quote! { a.#field.unwrap() } } }) .collect(); Ok(quote!( #[automatically_derived] impl std::convert::TryFrom for #ident { type Error = sea_orm::DbErr; fn try_from(a: ActiveModel) -> Result { #(if a.#active_model_field.is_not_set() { return Err(sea_orm::DbErr::AttrNotSet(stringify!(#active_model_field).to_owned())); })* Ok( Self { #(#model_field: #model_field_value),* } ) } } #[automatically_derived] impl sea_orm::TryIntoModel<#ident> for ActiveModel { fn try_into_model(self) -> Result<#ident, sea_orm::DbErr> { self.try_into() } } )) } pub fn expand_derive_active_model(ident: &Ident, data: &Data) -> syn::Result { let derive_active_model = DeriveActiveModel::new(ident, data)?; let define_active_model = derive_active_model.define_active_model(); let impl_active_model = derive_active_model.impl_active_model(); let derive_into_model = derive_into_model(ident, data)?; Ok(quote!( #define_active_model #impl_active_model #derive_into_model )) } ================================================ FILE: sea-orm-macros/src/derives/active_model_behavior.rs ================================================ use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::Data; /// Method to derive an implementation of [ActiveModelBehavior](sea_orm::ActiveModelBehavior) pub fn expand_derive_active_model_behavior(_ident: Ident, _data: Data) -> syn::Result { Ok(quote!( #[automatically_derived] impl sea_orm::ActiveModelBehavior for ActiveModel {} )) } ================================================ FILE: sea-orm-macros/src/derives/active_model_ex.rs ================================================ use super::active_model::DeriveActiveModel; use super::attributes::compound_attr; use super::util::{extract_compound_entity, field_not_ignored_compound, is_compound_field}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use std::collections::HashMap; use syn::{Attribute, Data, Expr, Fields, LitStr, Type}; pub fn expand_derive_active_model_ex( ident: &Ident, data: &Data, attrs: &[Attribute], ) -> syn::Result { let mut compact = false; let mut model_fields = Vec::new(); let mut ignored_model_fields = Vec::new(); let mut field_types: Vec = Vec::new(); let mut scalar_fields = Vec::new(); let mut compound_fields = Vec::new(); let mut belongs_to_fields = Vec::new(); let mut belongs_to_self_fields = Vec::new(); let mut has_one_fields = Vec::new(); let mut has_many_fields = Vec::new(); let mut has_many_self_fields = Vec::new(); let mut has_many_via_fields = Vec::new(); let mut has_many_via_self_fields = Vec::new(); let (async_, await_) = async_await(); attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("compact_model") { compact = true; } else { // Reads the value expression to advance the parse stream. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) }) })?; let mut entity_count = HashMap::new(); if let Data::Struct(item_struct) = &data { if let Fields::Named(fields) = &item_struct.fields { for field in fields.named.iter() { if field.ident.is_some() && field_not_ignored_compound(field) { let field_type = &field.ty; let field_type = quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces if is_compound_field(&field_type) { let entity_path = extract_compound_entity(&field_type); *entity_count.entry(entity_path.to_owned()).or_insert(0) += 1; } } } } } if let Data::Struct(item_struct) = &data { if let Fields::Named(fields) = &item_struct.fields { for field in fields.named.iter() { if let Some(ident) = &field.ident { if field_not_ignored_compound(field) { let field_type = &field.ty; let field_type = quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces let ty = if is_compound_field(&field_type) { compound_fields.push(ident); let entity_path = extract_compound_entity(&field_type); if !compact { let compound_attrs = compound_attr::SeaOrm::from_attributes(&field.attrs)?; if let Some(relation_enum) = compound_attrs.relation_enum { if compound_attrs.self_ref.is_some() && compound_attrs.via.is_none() && compound_attrs.from.is_some() && compound_attrs.to.is_some() { belongs_to_self_fields .push((ident.clone(), relation_enum.clone())); } else if compound_attrs.self_ref.is_some() && compound_attrs.relation_reverse.is_some() && compound_attrs.via.is_none() && compound_attrs.from.is_none() && compound_attrs.to.is_none() { has_many_self_fields .push((ident.clone(), relation_enum.clone())); } } else if *entity_count.get(entity_path).unwrap() == 1 { // can only Related to another Entity once if compound_attrs.belongs_to.is_some() { belongs_to_fields.push(ident.clone()); } else if compound_attrs.has_one.is_some() { has_one_fields.push(ident.clone()); } else if compound_attrs.has_many.is_some() && compound_attrs.via.is_none() { has_many_fields.push(ident.clone()); } else if compound_attrs.has_many.is_some() && compound_attrs.via.is_some() { has_many_via_fields.push(( ident.clone(), compound_attrs.via.as_ref().unwrap().value(), )); } } if compound_attrs.self_ref.is_some() && compound_attrs.via.is_some() && compound_attrs.reverse.is_none() { #[allow(clippy::unnecessary_unwrap)] has_many_via_self_fields.push(( ident.clone(), compound_attrs.via.as_ref().unwrap().value(), false, )); } else if compound_attrs.self_ref.is_some() && compound_attrs.via.is_some() && compound_attrs.reverse.is_some() { #[allow(clippy::unnecessary_unwrap)] has_many_via_self_fields.push(( ident.clone(), compound_attrs.via.as_ref().unwrap().value(), true, )); } } if field_type.starts_with("HasOne<") { syn::parse_str(&format!("HasOneModel < {entity_path} >"))? } else { syn::parse_str(&format!("HasManyModel < {entity_path} >"))? } } else { scalar_fields.push(ident); syn::parse_str(&format!("sea_orm::ActiveValue < {field_type} >"))? }; model_fields.push(ident); field_types.push(ty); } else { ignored_model_fields.push(ident); } } } } } let active_model_trait_methods = DeriveActiveModel::new(ident, data)?.impl_active_model_trait_methods(); let active_model_action = expand_active_model_action( &belongs_to_fields, &belongs_to_self_fields, &has_one_fields, &has_many_fields, &has_many_self_fields, &has_many_via_fields, &has_many_via_self_fields, ); let active_model_setters = expand_active_model_setters(data)?; let mut is_changed_expr = quote!(false); for field in scalar_fields.iter() { is_changed_expr.extend(quote!(|| self.#field.is_set())); } for field in compound_fields.iter() { is_changed_expr.extend(quote!(|| self.#field.is_changed())); } Ok(quote! { #[doc = " Generated by sea-orm-macros"] #[derive(Clone, Debug, PartialEq)] pub struct ActiveModelEx { #( #[doc = " Generated by sea-orm-macros"] pub #model_fields: #field_types ),* } impl ActiveModel { #[doc = " Generated by sea-orm-macros"] pub fn into_ex(self) -> ActiveModelEx { self.into() } } #[automatically_derived] impl sea_orm::ActiveModelTrait for ActiveModelEx { type Entity = Entity; #active_model_trait_methods /// Returns true if any field is set or changed. This is recursive. fn is_changed(&self) -> bool { #is_changed_expr } fn default() -> Self { ::new().into() } } impl ActiveModelEx { #[doc = " Generated by sea-orm-macros"] pub fn new() -> Self { ::default() } #active_model_action #active_model_setters } #[automatically_derived] impl std::default::Default for ActiveModelEx { fn default() -> Self { ::default() } } #[automatically_derived] impl std::convert::From for ActiveModelEx { fn from(m: ActiveModel) -> Self { Self { #(#scalar_fields: m.#scalar_fields,)* #(#compound_fields: Default::default(),)* } } } #[automatically_derived] impl std::convert::From for ActiveModel { fn from(m: ActiveModelEx) -> Self { Self { #(#scalar_fields: m.#scalar_fields,)* } } } #[automatically_derived] impl std::convert::From for ActiveModelEx { fn from(m: ModelEx) -> Self { Self { #(#scalar_fields: sea_orm::ActiveValue::Unchanged(m.#scalar_fields),)* #(#compound_fields: m.#compound_fields.into_active_model(),)* } } } #[automatically_derived] impl std::convert::TryFrom for ModelEx { type Error = sea_orm::DbErr; fn try_from(a: ActiveModelEx) -> Result { #(if a.#scalar_fields.is_not_set() { return Err(sea_orm::DbErr::AttrNotSet(stringify!(#scalar_fields).to_owned())); })* Ok( Self { #(#scalar_fields: a.#scalar_fields.unwrap(),)* #(#compound_fields: a.#compound_fields.try_into_model()?,)* #(#ignored_model_fields: Default::default(),)* } ) } } #[automatically_derived] impl sea_orm::IntoActiveModel for ModelEx { fn into_active_model(self) -> ActiveModelEx { self.into() } } #[automatically_derived] impl sea_orm::TryIntoModel for ActiveModelEx { fn try_into_model(self) -> Result { self.try_into() } } impl Model { #[doc = " Generated by sea-orm-macros"] pub #async_ fn cascade_delete<'a, C>(self, db: &'a C) -> Result where C: sea_orm::TransactionTrait, { self.into_ex().delete(db)#await_ } } impl ModelEx { #[doc = " Generated by sea-orm-macros"] pub #async_ fn delete<'a, C>(self, db: &'a C) -> Result where C: sea_orm::TransactionTrait, { let active_model: ActiveModelEx = self.into(); active_model.delete(db)#await_ } } impl ActiveModel { #[doc = " Generated by sea-orm-macros"] pub fn builder() -> ActiveModelEx { ActiveModelEx::new() } } }) } fn expand_active_model_action( belongs_to: &[Ident], belongs_to_self: &[(Ident, LitStr)], has_one: &[Ident], has_many: &[Ident], has_many_self: &[(Ident, LitStr)], has_many_via: &[(Ident, String)], has_many_via_self: &[(Ident, String, bool)], ) -> TokenStream { let mut belongs_to_action = TokenStream::new(); let mut belongs_to_after_action = TokenStream::new(); let mut has_one_before_action = TokenStream::new(); let mut has_one_action = TokenStream::new(); let mut has_one_delete = TokenStream::new(); let mut has_many_before_action = TokenStream::new(); let mut has_many_action = TokenStream::new(); let mut has_many_delete = TokenStream::new(); let mut has_many_via_before_action = TokenStream::new(); let mut has_many_via_action = TokenStream::new(); let mut has_many_via_delete = TokenStream::new(); let (async_, await_) = async_await(); let box_pin = if cfg!(feature = "async") { quote!(Box::pin) } else { quote!() }; for field in belongs_to { belongs_to_action.extend(quote! { let #field = if let Some(model) = self.#field.take() { if model.is_update() { // has primary key self.set_parent_key(&model)?; if model.is_changed() { let model = #box_pin(model.action(action, db))#await_?; Some(model) } else { Some(model) } } else { // new model let model = #box_pin(model.action(action, db))#await_?; self.set_parent_key(&model)?; Some(model) } } else { None }; }); belongs_to_after_action.extend(quote! { if let Some(#field) = #field { model.#field = HasOneModel::set(#field); } }); } for (field, relation_enum) in belongs_to_self { let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); let relation_enum = quote!(Relation::#relation_enum); // belongs to is the exception where action is performed before self belongs_to_action.extend(quote! { let #field = if let Some(model) = self.#field.take() { if model.is_update() { // has primary key self.set_parent_key_for(&model, #relation_enum)?; if model.is_changed() { let model = #box_pin(model.action(action, db))#await_?; Some(model) } else { Some(model) } } else { // new model let model = #box_pin(model.action(action, db))#await_?; self.set_parent_key_for(&model, #relation_enum)?; Some(model) } } else { None }; }); belongs_to_after_action.extend(quote! { if let Some(#field) = #field { model.#field = HasOneModel::set(#field); } }); } let delete_associated_model = quote! { let mut item = item.into_active_model(); if item.clear_parent_key::()? { item.update(db)#await_?; } else { deleted.merge(item.into_ex().delete(db)#await_?); // deep delete } }; for field in has_one { has_one_before_action.extend(quote! { let #field = self.#field.take(); }); has_one_action.extend(quote! { if let Some(mut #field) = #field { #field.set_parent_key(&model)?; if #field.is_changed() { model.#field = HasOneModel::set(#box_pin(#field.action(action, db))#await_?); } else { model.#field = HasOneModel::set(#field); } } }); has_one_delete.extend(quote! { if let Some(item) = self.find_related_of(self.#field.empty_slice()).one(db)#await_? { #delete_associated_model } }); } for field in has_many { has_many_before_action.extend(quote! { let #field = self.#field.take(); }); has_many_action.extend(quote! { if #field.is_replace() { for item in model.find_related_of(#field.as_slice()).all(db)#await_? { if !#field.find(&item) { #delete_associated_model } } } model.#field = #field.empty_holder(); for mut #field in #field.into_vec() { #field.set_parent_key(&model)?; if #field.is_changed() { model.#field.push(#box_pin(#field.action(action, db))#await_?); } else { model.#field.push(#field); } } }); has_many_delete.extend(quote! { for item in self.find_related_of(self.#field.as_slice()).all(db)#await_? { #delete_associated_model } }); } for (field, relation_enum) in has_many_self { let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); let relation_enum = quote!(Relation::#relation_enum); let delete_associated_model = quote! { let mut item = item.into_active_model(); if item.clear_parent_key_for_self_rev(#relation_enum)? { item.update(db)#await_?; } else { // attempt to cascade delete may lead to infinite recursion return Err(sea_orm::DbErr::RecordNotUpdated); } }; has_many_before_action.extend(quote! { let #field = self.#field.take(); }); has_many_action.extend(quote! { if #field.is_replace() { for item in model.find_belongs_to_self(#relation_enum, db.get_database_backend())?.all(db)#await_? { if !#field.find(&item) { #delete_associated_model } } } model.#field = #field.empty_holder(); for mut #field in #field.into_vec() { #field.set_parent_key_for_self_rev(&model, #relation_enum)?; if #field.is_changed() { model.#field.push(#box_pin(#field.action(action, db))#await_?); } else { model.#field.push(#field); } } }); has_many_delete.extend(quote! { for item in self.find_belongs_to_self(#relation_enum, db.get_database_backend())?.all(db)#await_? { #delete_associated_model } }); } for (field, via_entity) in has_many_via { let mut via_entity = via_entity.as_str(); if let Some((prefix, _)) = via_entity.split_once("::") { via_entity = prefix; } let related_entity: TokenStream = format!("super::{via_entity}::Entity").parse().unwrap(); has_many_via_before_action.extend(quote! { let #field = self.#field.take(); }); has_many_via_action.extend(quote! { model.#field = #field.empty_holder(); for item in #field.into_vec() { if item.is_update() { // has primary key if item.is_changed() { model.#field.push(#box_pin(item.action(action, db))#await_?); } else { model.#field.push(item); } } else { // new model model.#field.push(#box_pin(item.action(action, db))#await_?); } } model.establish_links( #related_entity, model.#field.as_slice(), model.#field.is_replace(), db )#await_?; }); has_many_via_delete.extend(quote! { deleted.merge(self.delete_links(#related_entity, db)#await_?); }); } for (field, via_entity, reverse) in has_many_via_self { let related_entity: TokenStream = format!("super::{via_entity}::Entity").parse().unwrap(); let establish_links = Ident::new( if *reverse { "establish_links_self_rev" } else { "establish_links_self" }, field.span(), ); has_many_via_before_action.extend(quote! { let #field = self.#field.take(); }); has_many_via_action.extend(quote! { model.#field = #field.empty_holder(); for item in #field.into_vec() { if item.is_update() { // has primary key if item.is_changed() { model.#field.push(#box_pin(item.action(action, db))#await_?); } else { model.#field.push(item); } } else { // new model model.#field.push(#box_pin(item.action(action, db))#await_?); } } model.#establish_links( #related_entity, model.#field.as_slice(), model.#field.is_replace(), db )#await_?; }); has_many_via_delete.extend(quote! { deleted.merge(self.delete_links_self(#related_entity, db)#await_?); }); } quote! { #[doc = " Generated by sea-orm-macros"] pub #async_ fn insert<'a, C>(self, db: &'a C) -> Result where C: sea_orm::TransactionTrait, { let active_model = self.action(sea_orm::ActiveModelAction::Insert, db)#await_?; active_model.try_into() } #[doc = " Generated by sea-orm-macros"] pub #async_ fn update<'a, C>(self, db: &'a C) -> Result where C: sea_orm::TransactionTrait, { let active_model = self.action(sea_orm::ActiveModelAction::Update, db)#await_?; active_model.try_into() } #[doc = " Generated by sea-orm-macros"] pub #async_ fn save<'a, C>(self, db: &'a C) -> Result where C: sea_orm::TransactionTrait, { self.action(sea_orm::ActiveModelAction::Save, db)#await_ } #[doc = " Generated by sea-orm-macros"] pub #async_ fn delete<'a, C>(self, db: &'a C) -> Result where C: sea_orm::TransactionTrait, { use sea_orm::{IntoActiveModel, TransactionSession}; let txn = db.begin()#await_?; let db = &txn; let mut deleted = sea_orm::DeleteResult::empty(); #has_one_delete #has_many_delete #has_many_via_delete let model: ActiveModel = self.into(); deleted.merge(model.delete(db)#await_?); txn.commit()#await_?; Ok(deleted) } #[doc = " Generated by sea-orm-macros"] pub #async_ fn action<'a, C>(mut self, action: sea_orm::ActiveModelAction, db: &'a C) -> Result where C: sea_orm::TransactionTrait, { use sea_orm::{HasOneModel, HasManyModel, IntoActiveModel, TransactionSession}; let txn = db.begin()#await_?; let db = &txn; let mut deleted = sea_orm::DeleteResult::empty(); #belongs_to_action #has_one_before_action #has_many_before_action #has_many_via_before_action let model: ActiveModel = self.into(); let mut model: Self = if model.is_changed() { match action { sea_orm::ActiveModelAction::Insert => model.insert(db)#await_, sea_orm::ActiveModelAction::Update => model.update(db)#await_, sea_orm::ActiveModelAction::Save => if !model.is_update() { model.insert(db)#await_ } else { model.update(db)#await_ }, }?.into_ex().into() } else { model.into() }; #belongs_to_after_action #has_one_action #has_many_action #has_many_via_action txn.commit()#await_?; Ok(model) } } } fn expand_active_model_setters(data: &Data) -> syn::Result { let mut setters = TokenStream::new(); if let Data::Struct(item_struct) = &data { if let Fields::Named(fields) = &item_struct.fields { for field in &fields.named { if let Some(ident) = &field.ident { let field_type = &field.ty; let field_type_str = quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces let mut ignore = false; for attr in field.attrs.iter() { if attr.path().is_ident("sea_orm") { attr.parse_nested_meta(|meta| { if meta.path.is_ident("ignore") { ignore = true; } else { // Reads the value expression to advance the parse stream. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) })?; } } if ignore { continue; } if is_compound_field(&field_type_str) { let entity_path = extract_compound_entity(&field_type_str); let active_model_type: Type = syn::parse_str(&format!( "{}ActiveModelEx", entity_path.trim_end_matches("Entity") ))?; if field_type_str.starts_with("HasOne<") { let setter = format_ident!("set_{}", ident); setters.extend(quote! { #[doc = " Generated by sea-orm-macros"] pub fn #setter(mut self, v: impl Into<#active_model_type>) -> Self { self.#ident.replace(v.into()); self } }); } else { let setter = format_ident!( "add_{}", pluralizer::pluralize(&ident.to_string(), 1, false) ); setters.extend(quote! { #[doc = " Generated by sea-orm-macros"] pub fn #setter(mut self, v: impl Into<#active_model_type>) -> Self { self.#ident.push(v.into()); self } }); } } else { let setter = format_ident!("set_{}", ident); setters.extend(quote! { #[doc = " Generated by sea-orm-macros"] pub fn #setter(mut self, v: impl Into<#field_type>) -> Self { self.#ident = sea_orm::Set(v.into()); self } }); } } } } } Ok(setters) } fn async_await() -> (TokenStream, TokenStream) { if cfg!(feature = "async") { (quote!(async), quote!(.await)) } else { (quote!(), quote!()) } } ================================================ FILE: sea-orm-macros/src/derives/arrow_schema.rs ================================================ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{Attribute, Data, Fields, LitInt, LitStr, Type}; /// Expand the DeriveArrowSchema derive macro pub fn expand_derive_arrow_schema( _ident: Ident, data: Data, _attrs: Vec, ) -> syn::Result { if !cfg!(feature = "with-arrow") { return Ok(quote!()); } let mut fields_info = Vec::new(); // Parse fields if let Data::Struct(item_struct) = data { if let Fields::Named(fields) = &item_struct.fields { for field in &fields.named { if let Some(field_ident) = &field.ident { let field_name = field_ident.to_string(); let field_type = &field.ty; // Detect if field is Option for nullability let type_string = quote! { #field_type }.to_string().replace(' ', ""); let is_nullable = type_string.starts_with("Option<"); // Parse field attributes let mut arrow_attrs = ArrowFieldAttrs::default(); let mut column_type_str: Option = None; let mut column_name_override: Option = None; let mut arrow_field_override: Option = None; let mut skip = false; for attr in field.attrs.iter() { if attr.path().is_ident("sea_orm") { attr.parse_nested_meta(|meta| { if meta.path.is_ident("arrow_skip") { skip = true; } else if meta.path.is_ident("arrow_field") { let lit: LitStr = meta.value()?.parse()?; arrow_field_override = Some(lit.value()); } else if meta.path.is_ident("column_name") { let lit: LitStr = meta.value()?.parse()?; column_name_override = Some(lit.value()); } else if meta.path.is_ident("arrow_precision") { let lit: LitInt = meta.value()?.parse()?; arrow_attrs.precision = Some(lit.base10_parse()?); } else if meta.path.is_ident("arrow_scale") { let lit: LitInt = meta.value()?.parse()?; arrow_attrs.scale = Some(lit.base10_parse()?); } else if meta.path.is_ident("arrow_timestamp_unit") { let lit: LitStr = meta.value()?.parse()?; arrow_attrs.timestamp_unit = Some(lit.value()); } else if meta.path.is_ident("arrow_timezone") { let lit: LitStr = meta.value()?.parse()?; arrow_attrs.timezone = Some(lit.value()); } else if meta.path.is_ident("arrow_comment") { let lit: LitStr = meta.value()?.parse()?; arrow_attrs.comment = Some(lit.value()); } else if meta.path.is_ident("arrow_byte_width") { let lit: LitInt = meta.value()?.parse()?; arrow_attrs.byte_width = Some(lit.base10_parse()?); } else if meta.path.is_ident("column_type") { let lit: LitStr = meta.value()?.parse()?; column_type_str = Some(lit.value()); } else if meta.path.is_ident("nullable") { arrow_attrs.nullable_attr = true; } else { let _ = meta.value().and_then(|v| v.parse::()); } Ok(()) })?; } } if skip { continue; // Skip this field } // Determine final nullability let nullable = is_nullable || arrow_attrs.nullable_attr; // Priority: arrow_field > column_name > Rust field name let resolved_name = arrow_field_override .or(column_name_override) .unwrap_or(field_name); fields_info.push(ArrowFieldInfo { name: resolved_name, field_type: field_type.clone(), column_type_str, nullable, arrow_attrs, }); } } } } // Generate arrow_schema() method let field_definitions = fields_info.iter().map(generate_field_definition); let entity_name = format_ident!("Entity"); Ok(quote! { #[automatically_derived] impl sea_orm::ArrowSchema for #entity_name { fn arrow_schema() -> sea_orm::arrow::datatypes::Schema { use sea_orm::arrow::datatypes::{DataType, Field, Schema, TimeUnit}; Schema::new(vec![ #(#field_definitions),* ]) } } }) } #[derive(Default)] struct ArrowFieldAttrs { precision: Option, scale: Option, timestamp_unit: Option, timezone: Option, comment: Option, nullable_attr: bool, byte_width: Option, } struct ArrowFieldInfo { name: String, field_type: Type, column_type_str: Option, #[allow(dead_code)] nullable: bool, arrow_attrs: ArrowFieldAttrs, } fn generate_field_definition(info: &ArrowFieldInfo) -> TokenStream { let field_name = &info.name; let nullable = true; // we use ActiveModel, where fields can be NotSet. // Generate DataType based on column_type or field type let data_type = if let Some(col_type_str) = &info.column_type_str { column_type_to_arrow_datatype(col_type_str, &info.arrow_attrs) } else { rust_type_to_arrow_datatype(&info.field_type, &info.arrow_attrs) }; // Add metadata if comment is present if let Some(comment) = &info.arrow_attrs.comment { quote! { Field::new(#field_name, #data_type, #nullable) .with_metadata([( "comment".into(), #comment.into() )].into()) } } else { quote! { Field::new(#field_name, #data_type, #nullable) } } } /// Map SeaORM ColumnType string to Arrow DataType fn column_type_to_arrow_datatype(col_type: &str, arrow_attrs: &ArrowFieldAttrs) -> TokenStream { // Parse ColumnType variants if col_type.starts_with("Decimal(") { // Extract precision and scale from Decimal(Some((p, s))) let (precision, scale) = if col_type.contains("Some((") { // Parse "Decimal(Some((20, 4)))" if let Some(inner) = col_type .strip_prefix("Decimal(Some((") .and_then(|s| s.strip_suffix(")))")) { let parts: Vec<&str> = inner.split(',').map(|s| s.trim()).collect(); if parts.len() == 2 { let p = parts[0].parse().unwrap_or(38); let s = parts[1].parse().unwrap_or(10); (p, s) } else { (38, 10) } } else { (38, 10) } } else { (38, 10) // Default for Decimal(None) }; // Allow arrow_precision/arrow_scale to override let final_precision = arrow_attrs.precision.unwrap_or(precision); let final_scale = arrow_attrs.scale.unwrap_or(scale); if final_precision <= 18 { quote! { DataType::Decimal64(#final_precision, #final_scale) } } else if final_precision <= 38 { quote! { DataType::Decimal128(#final_precision, #final_scale) } } else { quote! { DataType::Decimal256(#final_precision, #final_scale) } } } else if col_type.starts_with("Money(") { let precision = arrow_attrs.precision.unwrap_or(19); let scale = arrow_attrs.scale.unwrap_or(4); if precision <= 18 { quote! { DataType::Decimal64(#precision, #scale) } } else { quote! { DataType::Decimal128(#precision, #scale) } } } else if col_type == "TinyInteger" { quote! { DataType::Int8 } } else if col_type == "SmallInteger" { quote! { DataType::Int16 } } else if col_type == "Integer" { quote! { DataType::Int32 } } else if col_type == "BigInteger" { quote! { DataType::Int64 } } else if col_type == "TinyUnsigned" { quote! { DataType::UInt8 } } else if col_type == "SmallUnsigned" { quote! { DataType::UInt16 } } else if col_type == "Unsigned" { quote! { DataType::UInt32 } } else if col_type == "BigUnsigned" { quote! { DataType::UInt64 } } else if col_type == "Float" { quote! { DataType::Float32 } } else if col_type == "Double" { quote! { DataType::Float64 } } else if col_type == "Boolean" { quote! { DataType::Boolean } } else if col_type == "Text" { quote! { DataType::LargeUtf8 } } else if col_type.starts_with("String(") { // Parse String(StringLen::N(255)) or String(StringLen::None) if col_type.contains("None") || col_type.contains("Max") { quote! { DataType::LargeUtf8 } } else { // Try to extract length if let Some(inner) = col_type .strip_prefix("String(StringLen::N(") .and_then(|s| s.strip_suffix("))")) { if let Ok(n) = inner.parse::() { if n <= 32767 { return quote! { DataType::Utf8 }; } } } quote! { DataType::LargeUtf8 } } } else if col_type.starts_with("Char(") { quote! { DataType::Utf8 } } else if col_type == "Date" { quote! { DataType::Date32 } } else if col_type == "Time" { quote! { DataType::Time64(TimeUnit::Microsecond) } } else if col_type == "DateTime" || col_type == "Timestamp" { generate_timestamp_datatype(arrow_attrs, false) } else if col_type == "TimestampWithTimeZone" { generate_timestamp_datatype(arrow_attrs, true) } else if col_type.starts_with("Binary(") || col_type.starts_with("VarBinary(") { if let Some(bw) = arrow_attrs.byte_width { quote! { DataType::FixedSizeBinary(#bw) } } else { quote! { DataType::Binary } } } else if col_type == "Json" || col_type == "JsonBinary" { quote! { DataType::Utf8 } } else if col_type == "Uuid" { quote! { DataType::Binary } } else if col_type.starts_with("Enum {") { quote! { DataType::Utf8 } } else { // Default fallback quote! { DataType::Binary } } } /// Map Rust type to Arrow DataType (when no column_type specified) fn rust_type_to_arrow_datatype(field_type: &Type, arrow_attrs: &ArrowFieldAttrs) -> TokenStream { let type_string = quote! { #field_type }.to_string().replace(' ', ""); // Strip Option<> wrapper if present let inner_type = if type_string.starts_with("Option<") { type_string .strip_prefix("Option<") .and_then(|s| s.strip_suffix('>')) .unwrap_or(&type_string) } else { &type_string }; match inner_type { "i8" => quote! { DataType::Int8 }, "i16" => quote! { DataType::Int16 }, "i32" => quote! { DataType::Int32 }, "i64" => quote! { DataType::Int64 }, "u8" => quote! { DataType::UInt8 }, "u16" => quote! { DataType::UInt16 }, "u32" => quote! { DataType::UInt32 }, "u64" => quote! { DataType::UInt64 }, "f32" => quote! { DataType::Float32 }, "f64" => quote! { DataType::Float64 }, "bool" => quote! { DataType::Boolean }, "String" => quote! { DataType::Utf8 }, s if s.contains("Decimal") => { let precision = arrow_attrs.precision.unwrap_or(38); let scale = arrow_attrs.scale.unwrap_or(10); if precision <= 18 { quote! { DataType::Decimal64(#precision, #scale) } } else if precision <= 38 { quote! { DataType::Decimal128(#precision, #scale) } } else { quote! { DataType::Decimal256(#precision, #scale) } } } s if (s.contains("DateTime") && s.contains("Offset")) || (s.contains("DateTime") && s.contains("Utc")) || (s.contains("DateTime") && s.contains("TimeZone")) || s.contains("Timestamp") => { generate_timestamp_datatype(arrow_attrs, true) } s if s.contains("DateTime") => { generate_timestamp_datatype(arrow_attrs, arrow_attrs.timezone.is_some()) } s if s.contains("Date") => quote! { DataType::Date32 }, s if s.contains("Time") => quote! { DataType::Time64(TimeUnit::Microsecond) }, "Vec" => { if let Some(bw) = arrow_attrs.byte_width { quote! { DataType::FixedSizeBinary(#bw) } } else { quote! { DataType::Binary } } } _ => quote! { DataType::Binary }, // Safe fallback } } /// Generate timestamp DataType with optional timezone fn generate_timestamp_datatype(arrow_attrs: &ArrowFieldAttrs, has_timezone: bool) -> TokenStream { let unit = match arrow_attrs.timestamp_unit.as_deref() { Some("Second") => quote! { TimeUnit::Second }, Some("Millisecond") => quote! { TimeUnit::Millisecond }, Some("Microsecond") => quote! { TimeUnit::Microsecond }, Some("Nanosecond") => quote! { TimeUnit::Nanosecond }, _ => quote! { TimeUnit::Microsecond }, // Default }; if has_timezone { let tz = arrow_attrs.timezone.as_deref().unwrap_or("UTC"); quote! { DataType::Timestamp(#unit, Some(#tz.into())) } } else if let Some(tz) = &arrow_attrs.timezone { quote! { DataType::Timestamp(#unit, Some(#tz.into())) } } else { quote! { DataType::Timestamp(#unit, None) } } } ================================================ FILE: sea-orm-macros/src/derives/attributes.rs ================================================ pub mod derive_attr { use bae::FromAttributes; /// Attributes for Models and ActiveModels #[derive(Default, FromAttributes)] #[allow(dead_code)] pub struct SeaOrm { pub column: Option, pub entity: Option, pub model: Option, pub model_ex: Option, pub active_model: Option, pub active_model_ex: Option, pub primary_key: Option, pub relation: Option, pub schema_name: Option, pub table_name: Option, pub comment: Option, pub table_iden: Option<()>, pub rename_all: Option, } } pub mod relation_attr { use bae::FromAttributes; /// Attributes for Relation enum #[derive(Default, FromAttributes)] pub struct SeaOrm { pub belongs_to: Option, pub has_one: Option, pub has_many: Option, pub via_rel: Option, pub on_update: Option, pub on_delete: Option, pub on_condition: Option, pub from: Option, pub to: Option, pub fk_name: Option, pub skip_fk: Option<()>, pub condition_type: Option, } } pub mod compound_attr { use bae::FromAttributes; /// Attributes for compound model fields #[derive(Default, FromAttributes)] pub struct SeaOrm { pub has_one: Option<()>, pub has_many: Option<()>, pub belongs_to: Option<()>, pub self_ref: Option<()>, pub skip_fk: Option<()>, pub via: Option, pub via_rel: Option, pub from: Option, pub to: Option, pub relation_enum: Option, pub relation_reverse: Option, pub reverse: Option<()>, pub on_update: Option, pub on_delete: Option, } } pub mod value_type_attr { use bae::FromAttributes; /// Attributes for compound model fields #[derive(Default, FromAttributes)] pub struct SeaOrm { pub column_type: Option, pub array_type: Option, pub value_type: Option, pub from_str: Option, pub to_str: Option, pub try_from_u64: Option<()>, } } #[cfg(feature = "seaography")] pub mod related_attr { use bae::FromAttributes; /// Attributes for RelatedEntity enum #[derive(Default, FromAttributes)] pub struct SeaOrm { /// /// Allows to modify target entity /// /// Required on enumeration variants /// /// If used on enumeration attributes /// it allows to specify different /// Entity ident pub entity: Option, /// /// Allows to specify RelationDef /// /// Optional /// /// If not supplied the generated code /// will utilize `impl Related` trait pub def: Option, } } ================================================ FILE: sea-orm-macros/src/derives/case_style.rs ================================================ //! Copied from https://github.com/Peternator7/strum/blob/master/strum_macros/src/helpers/case_style.rs use heck::{ ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, }; use std::str::FromStr; use syn::{ Ident, LitStr, meta::ParseNestedMeta, parse::{Parse, ParseStream}, }; #[allow(clippy::enum_variant_names)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum CaseStyle { CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase, UpperCase, LowerCase, ScreamingKebabCase, PascalCase, } const VALID_CASE_STYLES: &[&str] = &[ "camelCase", "PascalCase", "kebab-case", "snake_case", "SCREAMING_SNAKE_CASE", "SCREAMING-KEBAB-CASE", "lowercase", "UPPERCASE", "title_case", "mixed_case", ]; impl Parse for CaseStyle { fn parse(input: ParseStream) -> syn::Result { let text = input.parse::()?; let val = text.value(); val.as_str().parse().map_err(|_| { syn::Error::new_spanned( &text, format!( "Unexpected case style for serialize_all: `{val}`. Valid values are: `{VALID_CASE_STYLES:?}`", ), ) }) } } impl FromStr for CaseStyle { type Err = (); fn from_str(text: &str) -> Result { Ok(match text { "camel_case" | "PascalCase" => CaseStyle::PascalCase, "camelCase" => CaseStyle::CamelCase, "snake_case" | "snek_case" => CaseStyle::SnakeCase, "kebab_case" | "kebab-case" => CaseStyle::KebabCase, "SCREAMING-KEBAB-CASE" => CaseStyle::ScreamingKebabCase, "shouty_snake_case" | "shouty_snek_case" | "SCREAMING_SNAKE_CASE" => { CaseStyle::ShoutySnakeCase } "title_case" => CaseStyle::TitleCase, "mixed_case" => CaseStyle::MixedCase, "lowercase" => CaseStyle::LowerCase, "UPPERCASE" => CaseStyle::UpperCase, _ => return Err(()), }) } } pub trait CaseStyleHelpers { fn convert_case(&self, case_style: Option) -> String; } impl CaseStyleHelpers for Ident { fn convert_case(&self, case_style: Option) -> String { let ident_string = self.to_string(); if let Some(case_style) = case_style { match case_style { CaseStyle::PascalCase => ident_string.to_upper_camel_case(), CaseStyle::KebabCase => ident_string.to_kebab_case(), CaseStyle::MixedCase => ident_string.to_lower_camel_case(), CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(), CaseStyle::SnakeCase => ident_string.to_snake_case(), CaseStyle::TitleCase => ident_string.to_title_case(), CaseStyle::UpperCase => ident_string.to_uppercase(), CaseStyle::LowerCase => ident_string.to_lowercase(), CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(), CaseStyle::CamelCase => { let camel_case = ident_string.to_upper_camel_case(); let mut pascal = String::with_capacity(camel_case.len()); let mut it = camel_case.chars(); if let Some(ch) = it.next() { pascal.extend(ch.to_lowercase()); } pascal.extend(it); pascal } } } else { ident_string } } } impl TryFrom<&ParseNestedMeta<'_>> for CaseStyle { type Error = syn::Error; fn try_from(value: &ParseNestedMeta) -> Result { let meta_string_literal: LitStr = value.value()?.parse()?; let value_string = meta_string_literal.value(); match CaseStyle::from_str(value_string.as_str()) { Ok(rule) => Ok(rule), Err(()) => Err(value.error(format!( "Unknown value for attribute parameter: `{value_string}`. Valid values are: `{VALID_CASE_STYLES:?}`" ))), } } } #[test] fn test_convert_case() { let id = Ident::new("test_me", proc_macro2::Span::call_site()); assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase))); assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase))); } ================================================ FILE: sea-orm-macros/src/derives/column.rs ================================================ use super::is_static_iden; use heck::{ToLowerCamelCase, ToSnakeCase}; use proc_macro2::{Ident, TokenStream}; use quote::{quote, quote_spanned}; use syn::{Data, DataEnum, Expr, Fields, LitStr, Variant}; /// Derive a Column name for an enum type pub fn impl_iden(ident: &Ident, data: &Data) -> syn::Result { let variants = match data { syn::Data::Enum(DataEnum { variants, .. }) => variants, _ => { return Ok(quote_spanned! { ident.span() => compile_error!("you can only derive DeriveColumn on enums"); }); } }; let variant: Vec = variants .iter() .map(|Variant { ident, fields, .. }| match fields { Fields::Named(_) => quote! { #ident{..} }, Fields::Unnamed(_) => quote! { #ident(..) }, Fields::Unit => quote! { #ident }, }) .collect(); let mut all_static = true; let name: Vec = variants .iter() .map(|v| { let mut column_name = v.ident.to_string().to_snake_case(); for attr in v.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } attr.parse_nested_meta(|meta| { if meta.path.is_ident("column_name") { column_name = meta.value()?.parse::()?.value(); } else { // Reads the value expression to advance the parse stream. // Some parameters, such as `primary_key`, do not have any value, // so ignoring an error occurred here. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) })?; } all_static &= is_static_iden(&column_name); Ok::(quote! { #column_name }) }) .collect::>()?; let quoted = if all_static { quote! { fn quoted(&self) -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(sea_orm::IdenStatic::as_str(self)) } } } else { quote! {} }; Ok(quote!( #[automatically_derived] impl sea_orm::IdenStatic for #ident { fn as_str(&self) -> &'static str { match self { #(Self::#variant => #name),* } } } #[automatically_derived] impl sea_orm::Iden for #ident { #quoted fn unquoted(&self) -> &str { sea_orm::IdenStatic::as_str(self) } } )) } /// Implement a column for an enum using [DeriveColumn](sea_orm::DeriveColumn) pub fn impl_col_from_str(ident: &Ident, data: &Data) -> syn::Result { let data_enum = match data { Data::Enum(data_enum) => data_enum, _ => { return Ok(quote_spanned! { ident.span() => compile_error!("you can only derive DeriveColumn on enums"); }); } }; let columns = data_enum .variants .iter() .map(|column| { let column_iden = column.ident.clone(); let column_str_snake = column_iden.to_string().to_snake_case(); let column_str_mixed = column_iden.to_string().to_lower_camel_case(); let mut column_name = column_str_snake.clone(); for attr in column.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } attr.parse_nested_meta(|meta| { if meta.path.is_ident("column_name") { column_name = meta.value()?.parse::()?.value(); } else { // Reads the value expression to advance the parse stream. // Some parameters, such as `primary_key`, do not have any value, // so ignoring an error occurred here. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) })?; } Ok::(quote!( #column_str_snake | #column_str_mixed | #column_name => Ok(#ident::#column_iden) )) }) .collect::, _>>()?; Ok(quote!( #[automatically_derived] impl std::str::FromStr for #ident { type Err = sea_orm::ColumnFromStrErr; fn from_str(s: &str) -> std::result::Result { match s { #(#columns),*, _ => Err(sea_orm::ColumnFromStrErr(s.to_owned())), } } } )) } pub fn expand_derive_column(ident: &Ident, data: &Data) -> syn::Result { let impl_col_from_str = impl_col_from_str(ident, data)?; let impl_iden = impl_iden(ident, data)?; Ok(quote!( #impl_col_from_str #impl_iden )) } ================================================ FILE: sea-orm-macros/src/derives/derive_iden.rs ================================================ use heck::ToSnakeCase; use proc_macro2::{self, TokenStream}; use quote::{quote, quote_spanned}; use syn::{ DataEnum, DataStruct, DeriveInput, Expr, Fields, LitStr, Variant, punctuated::Punctuated, }; pub(super) fn is_static_iden(name: &str) -> bool { // can only begin with [a-z_] name.chars() .take(1) .all(|c| c == '_' || c.is_ascii_alphabetic()) && name.chars().all(|c| c == '_' || c.is_ascii_alphanumeric()) } pub(super) fn impl_iden_for_unit_struct( ident: &syn::Ident, iden_str: &str, ) -> proc_macro2::TokenStream { let quoted = if is_static_iden(iden_str) { quote! { fn quoted(&self) -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(#iden_str) } } } else { quote! {} }; quote! { #[automatically_derived] impl sea_orm::Iden for #ident { #quoted fn unquoted(&self) -> &str { #iden_str } } } } fn impl_iden_for_enum( ident: &syn::Ident, variants: Punctuated, ) -> proc_macro2::TokenStream { let variants = variants.iter(); let mut all_static = true; let match_pair: Vec = variants .map(|v| { let var_ident = &v.ident; let var_name = if var_ident == "Table" { ident } else { var_ident }; let mut var_name = var_name.to_string().to_snake_case(); v.attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("iden") { let litstr: LitStr = meta.value()?.parse()?; var_name = litstr.value(); } else { // Reads the value expression to advance the parse stream. // Some parameters do not have any value, // so ignoring an error occurred here. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) }) }) .expect("something something"); all_static &= is_static_iden(&var_name); quote! { Self::#var_ident => #var_name } }) .collect(); let match_arms: TokenStream = quote! { #(#match_pair),* }; let quoted = if all_static { quote! { fn quoted(&self) -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(match self { #match_arms }) } } } else { quote! {} }; quote! { #[automatically_derived] impl sea_orm::Iden for #ident { #quoted fn unquoted(&self) -> &str { match self { #match_arms } } } } } pub fn expand_derive_iden(input: DeriveInput) -> syn::Result { let DeriveInput { ident, data, .. } = input; let mut new_iden: String = ident.to_string().to_snake_case(); input .attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("iden") { let litstr: LitStr = meta.value()?.parse()?; new_iden = litstr.value(); } else { // Reads the value expression to advance the parse stream. // Some parameters do not have any value, // so ignoring an error occurred here. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) }) })?; // Currently we only support enums and unit structs match data { syn::Data::Enum(DataEnum { variants, .. }) => { if variants.is_empty() { Ok(TokenStream::new()) } else { Ok(impl_iden_for_enum(&ident, variants)) } } syn::Data::Struct(DataStruct { fields: Fields::Unit, .. }) => Ok(impl_iden_for_unit_struct(&ident, &new_iden)), _ => Ok(quote_spanned! { ident.span() => compile_error!("you can only derive DeriveIden on unit struct or enum"); }), } } ================================================ FILE: sea-orm-macros/src/derives/entity.rs ================================================ use std::iter::FromIterator; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use super::{attributes::derive_attr, impl_iden_for_unit_struct}; struct DeriveEntity { column_ident: syn::Ident, ident: syn::Ident, model_ident: syn::Ident, model_ex_ident: syn::Ident, active_model_ident: syn::Ident, active_model_ex_ident: syn::Ident, primary_key_ident: syn::Ident, relation_ident: syn::Ident, schema_name: Option, table_name: Option, } impl DeriveEntity { fn new(input: syn::DeriveInput) -> Result { let sea_attr = derive_attr::SeaOrm::try_from_attributes(&input.attrs)?.unwrap_or_default(); let ident = input.ident; let column_ident = sea_attr.column.unwrap_or_else(|| format_ident!("Column")); let model_ident = sea_attr.model.unwrap_or_else(|| format_ident!("Model")); let model_ex_ident = sea_attr.model_ex.unwrap_or_else(|| format_ident!("Model")); let active_model_ident = sea_attr .active_model .unwrap_or_else(|| format_ident!("ActiveModel")); let active_model_ex_ident = sea_attr .active_model_ex .unwrap_or_else(|| format_ident!("ActiveModel")); let primary_key_ident = sea_attr .primary_key .unwrap_or_else(|| format_ident!("PrimaryKey")); let relation_ident = sea_attr .relation .unwrap_or_else(|| format_ident!("Relation")); let table_name = sea_attr.table_name; let schema_name = sea_attr.schema_name; Ok(DeriveEntity { column_ident, ident, model_ident, model_ex_ident, active_model_ident, active_model_ex_ident, primary_key_ident, relation_ident, schema_name, table_name, }) } fn expand(&self) -> TokenStream { let expanded_impl_entity_name = self.impl_entity_name(); let expanded_impl_entity_trait = self.impl_entity_trait(); let expanded_impl_iden = self.impl_iden(); let expanded_impl_iden_static = self.impl_iden_static(); let expanded_impl_entity_registry = self.impl_entity_registry(); TokenStream::from_iter([ expanded_impl_entity_name, expanded_impl_entity_trait, expanded_impl_iden, expanded_impl_iden_static, expanded_impl_entity_registry, ]) } fn impl_entity_name(&self) -> TokenStream { let ident = &self.ident; let table_name = match &self.table_name { Some(table_name) => table_name, None => return TokenStream::new(), // No table name, do not derive EntityName }; let expanded_schema_name = self .schema_name .as_ref() .map(|schema| quote!(Some(#schema))) .unwrap_or_else(|| quote!(None)); quote!( #[automatically_derived] impl sea_orm::entity::EntityName for #ident { fn schema_name(&self) -> Option<&str> { #expanded_schema_name } fn table_name(&self) -> &'static str { #table_name } } ) } fn impl_entity_trait(&self) -> TokenStream { let Self { ident, model_ident, model_ex_ident, active_model_ident, active_model_ex_ident, column_ident, primary_key_ident, relation_ident, .. } = self; quote!( #[automatically_derived] impl sea_orm::entity::EntityTrait for #ident { type Model = #model_ident; type ModelEx = #model_ex_ident; type ActiveModel = #active_model_ident; type ActiveModelEx = #active_model_ex_ident; type Column = #column_ident; type PrimaryKey = #primary_key_ident; type Relation = #relation_ident; } ) } fn impl_iden(&self) -> TokenStream { let ident = &self.ident; match &self.table_name { Some(table_name) => impl_iden_for_unit_struct(ident, &table_name.value()), None => quote!( #[automatically_derived] impl sea_orm::Iden for #ident { fn unquoted(&self) -> &str { ::as_str(self) } } ), } } fn impl_iden_static(&self) -> TokenStream { let ident = &self.ident; quote!( #[automatically_derived] impl sea_orm::IdenStatic for #ident { fn as_str(&self) -> &'static str { ::table_name(self) } } ) } fn impl_entity_registry(&self) -> TokenStream { if cfg!(feature = "entity-registry") { quote! { sea_orm::register_entity! { sea_orm::EntityRegistry { module_path: module_path!(), schema_info: |schema| sea_orm::EntitySchemaInfo::new(Entity, schema), } } } } else { quote!() } } } pub fn expand_derive_entity(input: syn::DeriveInput) -> syn::Result { Ok(DeriveEntity::new(input)?.expand()) } ================================================ FILE: sea-orm-macros/src/derives/entity_loader.rs ================================================ use proc_macro2::TokenStream; use quote::quote; use std::collections::HashMap; use syn::{Ident, punctuated::Punctuated, token::Comma}; #[derive(Default)] pub struct EntityLoaderSchema { pub fields: Vec, } pub struct EntityLoaderField { pub is_one: bool, pub is_self: bool, pub is_reverse: bool, pub field: Ident, /// super::bakery::Entity pub entity: String, pub relation_enum: Option, pub via: Option, } pub fn expand_entity_loader(schema: EntityLoaderSchema) -> TokenStream { let mut field_bools: Punctuated<_, Comma> = Punctuated::new(); let mut field_nests: Punctuated<_, Comma> = Punctuated::new(); let mut one_fields: Punctuated<_, Comma> = Punctuated::new(); let mut with_impl = TokenStream::new(); let mut with_nest_impl = TokenStream::new(); let mut select_impl = TokenStream::new(); let mut assemble_one = TokenStream::new(); let mut load_one = TokenStream::new(); let mut load_many = TokenStream::new(); let mut load_one_nest = TokenStream::new(); let mut load_many_nest = TokenStream::new(); let mut load_one_nest_nest = TokenStream::new(); let mut load_many_nest_nest = TokenStream::new(); let mut into_with_param_impl = TokenStream::new(); let mut arity = 1; let (async_, await_) = if cfg!(feature = "async") { (quote!(async), quote!(.await)) } else { (quote!(), quote!()) }; let async_trait = if cfg!(feature = "async") { quote!(#[async_trait::async_trait]) } else { quote!() }; one_fields.push(quote!(model)); let mut total_count = HashMap::new(); for entity_field in schema.fields.iter() { *total_count.entry(&entity_field.entity).or_insert(0) += 1; } for entity_field in schema.fields.iter() { let field = &entity_field.field; let is_one = entity_field.is_one; let is_self = entity_field.is_self; let is_reverse = entity_field.is_reverse; let entity: TokenStream = entity_field.entity.parse().unwrap(); let entity_module: TokenStream = entity_field .entity .trim_end_matches("::Entity") .parse() .unwrap(); if !is_self && *total_count.get(&entity_field.entity).unwrap() != 1 { // prevent impl trait for same entity twice // self_ref is allowed continue; } if !is_self { field_bools.push(quote! { #[doc = " Generated by sea-orm-macros"] pub #field: bool }); field_nests.push(quote! { #[doc = " Generated by sea-orm-macros"] pub #field: #entity_module::EntityLoaderWith }); with_impl.extend(quote! { if target == sea_orm::compound::LoadTarget::TableRef(#entity.table_ref()) { self.#field = true; } }); with_nest_impl.extend(quote! { if left == sea_orm::compound::LoadTarget::TableRef(#entity.table_ref()) { self.with.#field = true; self.nest.#field.set(right); return self; } }); } else { field_bools.push(quote! { #[doc = " Generated by sea-orm-macros"] pub #field: bool }); field_nests.push(quote! { #[doc = " Generated by sea-orm-macros"] pub #field: EntityLoaderWith }); if let Some(relation_enum) = &entity_field.relation_enum { with_impl.extend(quote! { if let sea_orm::compound::LoadTarget::Relation(relation_enum) = &target { if relation_enum == #relation_enum { self.#field = true; } } }); } if let Some(via_lit) = &entity_field.via { let via = Ident::new(&via_lit.value(), via_lit.span()); let target_type = if !is_reverse { Ident::new("TableRef", via_lit.span()) } else { Ident::new("TableRefRev", via_lit.span()) }; let target_entity = if !is_reverse { quote!(super::#via::Entity) } else { quote!(super::#via::EntityReverse) }; with_impl.extend(quote! { if target == sea_orm::compound::LoadTarget::#target_type(super::#via::Entity.table_ref()) { self.#field = true; } }); with_nest_impl.extend(quote! { if left == sea_orm::compound::LoadTarget::#target_type(super::#via::Entity.table_ref()) { self.with.#field = true; self.nest.#field.set(right); return self; } }); into_with_param_impl.extend(quote! { impl EntityLoaderWithParam for #target_entity { fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option) { (sea_orm::compound::LoadTarget::#target_type(super::#via::Entity.table_ref()), None) } } impl EntityLoaderWithParam for (#target_entity, S) where S: EntityTrait, Entity: Related, { fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option) { ( sea_orm::compound::LoadTarget::#target_type(super::#via::Entity.table_ref()), Some(sea_orm::compound::LoadTarget::TableRef(self.1.table_ref())), ) } } }); } } if is_one && !is_self { arity += 1; if arity <= 3 { // do not go beyond SelectThree one_fields.push(quote!(#field)); select_impl.extend(quote! { let select = if self.with.#field && self.nest.#field.is_empty() { self.with.#field = false; loaded.#field = true; select.find_also(Entity, #entity) } else { select.select_also_fake(#entity) }; }); assemble_one.extend(quote! { if loaded.#field { model.#field = #field.map(Into::into).map(Box::new).into(); } }); } load_one.extend(quote! { if with.#field { let #field = models.as_slice().load_one_ex(#entity, db)#await_?; let #field = #entity_module::EntityLoader::load_nest(#field, &nest.#field, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.map(Into::into).map(Box::new).into(); } } }); load_one_nest.extend(quote! { if with.#field { let #field = models.as_slice().load_one_ex(#entity, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { if let Some(model) = model.as_mut() { model.#field = #field.map(Into::into).map(Box::new).into(); } } } }); load_one_nest_nest.extend(quote! { if with.#field { let #field = models.as_slice().load_one_ex(#entity, db)#await_?; for (models, #field) in models.iter_mut().zip(#field) { for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.map(Into::into).map(Box::new).into(); } } } }); } else if !is_one && !is_self { load_many.extend(quote! { if with.#field { let #field = models.as_slice().load_many_ex(#entity, db)#await_?; let #field = #entity_module::EntityLoader::load_nest_nest(#field, &nest.#field, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.into(); } } }); load_many_nest.extend(quote! { if with.#field { let #field = models.as_slice().load_many_ex(#entity, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { if let Some(model) = model.as_mut() { model.#field = #field.into(); } } } }); load_many_nest_nest.extend(quote! { if with.#field { let #field = models.as_slice().load_many_ex(#entity, db)#await_?; for (models, #field) in models.iter_mut().zip(#field) { for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.into(); } } } }); } else if is_one && is_self { if let Some(relation_enum) = &entity_field.relation_enum { let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); load_one.extend(quote! { if with.#field { let #field = models.as_slice().load_self_ex(#entity, Relation::#relation_enum, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.map(Into::into).map(Box::new).into(); } } }); } } else if !is_one && is_self { if let Some(relation_enum) = &entity_field.relation_enum { let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); load_many.extend(quote! { if with.#field { let #field = models.as_slice().load_self_many_ex(#entity, Relation::#relation_enum, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.into(); } } }); } if let Some(via) = &entity_field.via { let via = Ident::new(&via.value(), via.span()); load_many.extend(quote! { if with.#field { let #field = models.as_slice().load_self_via_ex(super::#via::Entity, #is_reverse, db)#await_?; let #field = EntityLoader::load_nest_nest(#field, &nest.#field, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.into(); } } }); load_many_nest.extend(quote! { if with.#field { let #field = models.as_slice().load_self_via_ex(super::#via::Entity, #is_reverse, db)#await_?; for (model, #field) in models.iter_mut().zip(#field) { if let Some(model) = model.as_mut() { model.#field = #field.into(); } } } }); load_many_nest_nest.extend(quote! { if with.#field { let #field = models.as_slice().load_self_via_ex(super::#via::Entity, #is_reverse, db)#await_?; for (models, #field) in models.iter_mut().zip(#field) { for (model, #field) in models.iter_mut().zip(#field) { model.#field = #field.into(); } } } }); } } } quote! { #[doc = " Generated by sea-orm-macros"] #[derive(Clone)] pub struct EntityLoader { select: sea_orm::Select, with: EntityLoaderWith, nest: EntityLoaderNest, } #[doc = " Generated by sea-orm-macros"] #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct EntityReverse; impl sea_orm::compound::EntityReverse for EntityReverse { type Entity = Entity; } #[doc = " Generated by sea-orm-macros"] #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct EntityLoaderWith { #field_bools } #[doc = " Generated by sea-orm-macros"] #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct EntityLoaderNest { #field_nests } impl Entity { #[doc = " Generated by sea-orm-macros"] pub const REVERSE: EntityReverse = EntityReverse; } impl EntityLoaderWith { #[doc = " Generated by sea-orm-macros"] pub fn is_empty(&self) -> bool { self == &Self::default() } #[doc = " Generated by sea-orm-macros"] pub fn set(&mut self, target: sea_orm::compound::LoadTarget) { #with_impl } } #[doc = " Parameters for EntityLoader"] pub trait EntityLoaderWithParam { #[doc = " Generated by sea-orm-macros"] fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option); } #[automatically_derived] impl EntityLoaderWithParam for R where R: EntityTrait, Entity: Related, { fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option) { (sea_orm::compound::LoadTarget::TableRef(self.table_ref()), None) } } #[automatically_derived] impl EntityLoaderWithParam for (R, S) where R: EntityTrait, Entity: Related, S: EntityTrait, R: Related, { fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option) { ( sea_orm::compound::LoadTarget::TableRef(self.0.table_ref()), Some(sea_orm::compound::LoadTarget::TableRef(self.1.table_ref())), ) } } #[automatically_derived] impl EntityLoaderWithParam for sea_orm::compound::EntityLoaderWithSelf where R: EntityTrait, Entity: Related, S: EntityTrait, R: RelatedSelfVia, { fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option) { ( sea_orm::compound::LoadTarget::TableRef(self.0.table_ref()), Some(sea_orm::compound::LoadTarget::TableRef(self.1.table_ref())), ) } } #[automatically_derived] impl EntityLoaderWithParam for sea_orm::compound::EntityLoaderWithSelfRev where R: EntityTrait, Entity: Related, S: EntityTrait, R: RelatedSelfVia, SR: sea_orm::compound::EntityReverse, { fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option) { ( sea_orm::compound::LoadTarget::TableRef(self.0.table_ref()), Some(sea_orm::compound::LoadTarget::TableRefRev(S::default().table_ref())), ) } } #[automatically_derived] impl EntityLoaderWithParam for Relation { fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option) { (sea_orm::compound::LoadTarget::Relation(self.name()), None) } } #into_with_param_impl #[automatically_derived] impl std::fmt::Debug for EntityLoader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("EntityLoader") .field("select", &match (Entity::default().schema_name(), Entity::default().table_name()) { (Some(s), t) => format!("{s}.{t}"), (None, t) => t.to_owned(), }) .field("with", &self.with) .field("nest", &self.nest) .finish() } } #[automatically_derived] impl sea_orm::QueryFilter for EntityLoader { type QueryStatement = as sea_orm::QueryFilter>::QueryStatement; fn query(&mut self) -> &mut Self::QueryStatement { sea_orm::QueryFilter::query(&mut self.select) } } #[automatically_derived] impl sea_orm::QueryOrder for EntityLoader { type QueryStatement = as sea_orm::QueryOrder>::QueryStatement; fn query(&mut self) -> &mut Self::QueryStatement { sea_orm::QueryOrder::query(&mut self.select) } } #[automatically_derived] #async_trait impl sea_orm::compound::EntityLoaderTrait for EntityLoader { type ModelEx = ModelEx; #async_ fn fetch(self, db: &C, page: u64, page_size: u64) -> Result, sea_orm::DbErr> { self.fetch(db, page, page_size)#await_ } #async_ fn num_items(self, db: &C, page_size: u64) -> Result { self.select.paginate(db, page_size).num_items()#await_ } } impl Entity { #[doc = " Generated by sea-orm-macros"] pub fn load() -> EntityLoader { EntityLoader { select: Entity::find(), with: Default::default(), nest: Default::default(), } } } impl EntityLoader { #[doc = " Generated by sea-orm-macros"] pub #async_ fn one(mut self, db: &C) -> Result, sea_orm::DbErr> { use sea_orm::QuerySelect; self.select = self.select.limit(1); Ok(self.all(db)#await_?.into_iter().next()) } #[doc = " Generated by sea-orm-macros"] pub #async_ fn all(self, db: &C) -> Result, sea_orm::DbErr> { self.fetch(db, 0, 0)#await_ } #[doc = " Generated by sea-orm-macros"] pub fn with(mut self, param: T) -> Self { match param.into_with_param() { (left, None) => self.with_1(left), (left, Some(right)) => self.with_2(left, right), } } fn with_1(mut self, load_target: sea_orm::compound::LoadTarget) -> Self { self.with.set(load_target); self } fn with_2(mut self, left: sea_orm::compound::LoadTarget, right: sea_orm::compound::LoadTarget) -> Self { #with_nest_impl self } #[doc = " Generated by sea-orm-macros"] #async_ fn fetch(mut self, db: &C, page: u64, page_size: u64) -> Result, sea_orm::DbErr> { let select = self.select; let mut loaded = EntityLoaderWith::default(); #select_impl let models = if page_size != 0 { select.paginate(db, page_size).fetch_page(page)#await_? } else { select.all(db)#await_? }; let models = models.into_iter().map(|(#one_fields)| { let mut model = model.into_ex(); #assemble_one model }).collect::>(); let models = Self::load(models, &self.with, &self.nest, db)#await_?; Ok(models) } #[doc = " Generated by sea-orm-macros"] pub #async_ fn load(mut models: Vec, with: &EntityLoaderWith, nest: &EntityLoaderNest, db: &C) -> Result, DbErr> { use sea_orm::LoaderTraitEx; #load_one #load_many Ok(models) } #[doc = " Generated by sea-orm-macros"] pub #async_ fn load_nest(mut models: Vec>, with: &EntityLoaderWith, db: &C) -> Result>, DbErr> { use sea_orm::LoaderTraitEx; #load_one_nest #load_many_nest Ok(models) } #[doc = " Generated by sea-orm-macros"] pub #async_ fn load_nest_nest(mut models: Vec>, with: &EntityLoaderWith, db: &C) -> Result>, DbErr> { use sea_orm::NestedLoaderTrait; #load_one_nest_nest #load_many_nest_nest Ok(models) } } } } ================================================ FILE: sea-orm-macros/src/derives/entity_model.rs ================================================ use super::case_style::{CaseStyle, CaseStyleHelpers}; use super::util::{escape_rust_keyword, trim_starting_raw_identifier}; use heck::{ ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, }; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use std::str::FromStr; use syn::meta::ParseNestedMeta; use syn::{ Attribute, Data, Fields, Lit, LitStr, punctuated::Punctuated, spanned::Spanned, token::Comma, }; const NOT_AUTO_INCRE_TYPE_SUFFIX: [&str; 2] = ["String", "Uuid"]; #[allow(dead_code)] fn convert_case(s: &str, case_style: CaseStyle) -> String { match case_style { CaseStyle::PascalCase => s.to_upper_camel_case(), CaseStyle::KebabCase => s.to_kebab_case(), CaseStyle::MixedCase => s.to_lower_camel_case(), CaseStyle::ShoutySnakeCase => s.to_shouty_snake_case(), CaseStyle::SnakeCase => s.to_snake_case(), CaseStyle::TitleCase => s.to_title_case(), CaseStyle::UpperCase => s.to_uppercase(), CaseStyle::LowerCase => s.to_lowercase(), CaseStyle::ScreamingKebabCase => s.to_kebab_case().to_uppercase(), CaseStyle::CamelCase => { let camel_case = s.to_upper_camel_case(); let mut result = String::with_capacity(camel_case.len()); let mut it = camel_case.chars(); if let Some(ch) = it.next() { result.extend(ch.to_lowercase()); } result.extend(it); result } } } #[cfg(feature = "with-json")] fn serde_deserialize_name( orig: &str, serde_rename: Option<&str>, serde_rename_all: Option, ) -> String { if let Some(rename) = serde_rename.as_ref() { return rename.to_string(); } if let Some(case_style) = serde_rename_all { convert_case(orig, case_style) } else { orig.to_string() } } fn consume_meta(meta: ParseNestedMeta<'_>) { let _ = meta.value().and_then(|v| v.parse::()); } /// Method to derive an Model pub fn expand_derive_entity_model(data: &Data, attrs: &[Attribute]) -> syn::Result { // if #[sea_orm(table_name = "foo", schema_name = "bar")] specified, create Entity struct let mut table_name = None; let mut comment = quote! {None}; let mut schema_name = quote! { None }; let mut table_iden = false; let mut model_ex = false; let mut rename_all: Option = None; let mut serde_rename_all: Option = None; // Parse #[serde(rename_all = "...")] at struct level attrs .iter() .filter(|attr| attr.path().is_ident("serde")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("rename_all") { if let Ok(lit) = meta.value().and_then(|v| v.parse::()) { // #[serde(rename_all = "camelCase")] serde_rename_all = CaseStyle::from_str(&lit.value()).ok(); } else { // #[serde(rename_all(serialize = "...", deserialize = "..."))] meta.parse_nested_meta(|nested| { if nested.path.is_ident("deserialize") { let lit: LitStr = nested.value()?.parse()?; serde_rename_all = CaseStyle::from_str(&lit.value()).ok(); } else { consume_meta(nested); } Ok(()) })?; } } else { consume_meta(meta); } Ok(()) }) })?; attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("comment") { let name: Lit = meta.value()?.parse()?; comment = quote! { Some(#name) }; } else if meta.path.is_ident("table_name") { table_name = Some(meta.value()?.parse::()?); } else if meta.path.is_ident("schema_name") { let name: Lit = meta.value()?.parse()?; schema_name = quote! { Some(#name) }; } else if meta.path.is_ident("table_iden") { table_iden = true; } else if meta.path.is_ident("model_ex") { model_ex = true; } else if meta.path.is_ident("rename_all") { rename_all = Some((&meta).try_into()?); } else { consume_meta(meta); } Ok(()) }) })?; let entity_def = table_name .as_ref() .map(|table_name| { let entity_extra_attr = if model_ex { quote!(#[sea_orm(model_ex = ModelEx, active_model_ex = ActiveModelEx)]) } else { quote!() }; quote! { #[doc = " Generated by sea-orm-macros"] #[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)] #entity_extra_attr pub struct Entity; #[automatically_derived] impl sea_orm::prelude::EntityName for Entity { fn schema_name(&self) -> Option<&str> { #schema_name } fn table_name(&self) -> &'static str { #table_name } fn comment(&self) -> Option<&str> { #comment } } } }) .unwrap_or_default(); // generate Column enum and it's ColumnTrait impl let mut columns_enum: Punctuated<_, Comma> = Punctuated::new(); let mut columns_trait: Punctuated<_, Comma> = Punctuated::new(); let mut columns_enum_type_name: Punctuated<_, Comma> = Punctuated::new(); let mut columns_select_as: Punctuated<_, Comma> = Punctuated::new(); let mut columns_save_as: Punctuated<_, Comma> = Punctuated::new(); let mut primary_keys: Punctuated<_, Comma> = Punctuated::new(); let mut primary_key_types: Punctuated<_, Comma> = Punctuated::new(); let mut auto_increment: Option = None; #[cfg(feature = "with-json")] let mut columns_json_keys: Punctuated<_, Comma> = Punctuated::new(); if table_iden { if let Some(table_name) = &table_name { let table_field_name = Ident::new("Table", Span::call_site()); columns_enum.push(quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(table_name=#table_name)] #[strum(disabled)] #table_field_name }); columns_trait.push( quote! { Self::#table_field_name => panic!("Table cannot be used as a column") }, ); } } if let Data::Struct(item_struct) = data { if let Fields::Named(fields) = &item_struct.fields { for field in &fields.named { if let Some(ident) = &field.ident { let original_field_name = trim_starting_raw_identifier(ident); let mut field_name = Ident::new(&original_field_name.to_upper_camel_case(), ident.span()); let mut nullable = false; let mut default_value = None; let mut comment = None; let mut default_expr = None; let mut select_as = None; let mut save_as = None; let mut unique_key = None; let mut renamed_from = None; let mut indexed = false; let mut ignore = false; let mut unique = false; let mut sql_type = None; let mut enum_name = None; let mut is_primary_key = false; let mut is_auto_increment = false; let mut extra = None; let mut seaography_ignore = false; #[cfg(feature = "with-json")] let mut serde_rename: Option = None; let mut column_name = if let Some(case_style) = rename_all { Some(field_name.convert_case(Some(case_style))) } else if original_field_name != original_field_name.to_upper_camel_case().to_snake_case() { // `to_snake_case` was used to trim prefix and tailing underscore Some(original_field_name.to_snake_case()) } else { None }; // search for #[sea_orm(primary_key, auto_increment = false, column_type = "String(StringLen::N(255))", default_value = "new user", default_expr = "gen_random_uuid()", column_name = "name", enum_name = "Name", nullable, indexed, unique)] for attr in field.attrs.iter() { if attr.path().is_ident("sea_orm") { // single param attr.parse_nested_meta(|meta| { if meta.path.is_ident("column_type") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { let ty: TokenStream = syn::parse_str(&litstr.value())?; sql_type = Some(ty); } else { return Err( meta.error(format!("Invalid column_type {lit:?}")) ); } } else if meta.path.is_ident("auto_increment") { let lit = meta.value()?.parse()?; if let Lit::Bool(litbool) = lit { is_auto_increment = litbool.value(); auto_increment = Some(litbool.value()); } else { return Err( meta.error(format!("Invalid auto_increment = {lit:?}")) ); } } else if meta.path.is_ident("comment") { comment = Some(meta.value()?.parse::()?); } else if meta.path.is_ident("default_value") { default_value = Some(meta.value()?.parse::()?); } else if meta.path.is_ident("default_expr") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { let value_expr: TokenStream = syn::parse_str(&litstr.value())?; default_expr = Some(value_expr); } else { return Err( meta.error(format!("Invalid column_type {lit:?}")) ); } } else if meta.path.is_ident("column_name") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { column_name = Some(litstr.value()); } else { return Err( meta.error(format!("Invalid column_name {lit:?}")) ); } } else if meta.path.is_ident("enum_name") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { let ty: Ident = syn::parse_str(&litstr.value())?; enum_name = Some(ty); } else { return Err( meta.error(format!("Invalid enum_name {lit:?}")) ); } } else if meta.path.is_ident("select_as") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { select_as = Some(litstr.value()); } else { return Err( meta.error(format!("Invalid select_as {lit:?}")) ); } } else if meta.path.is_ident("save_as") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { save_as = Some(litstr.value()); } else { return Err(meta.error(format!("Invalid save_as {lit:?}"))); } } else if meta.path.is_ident("ignore") { ignore = true; } else if meta.path.is_ident("primary_key") { is_primary_key = true; primary_key_types.push(field.ty.clone()); } else if meta.path.is_ident("nullable") { nullable = true; } else if meta.path.is_ident("indexed") { indexed = true; } else if meta.path.is_ident("unique") { unique = true; } else if meta.path.is_ident("unique_key") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { unique_key = Some(litstr.value()); } else { return Err( meta.error(format!("Invalid unique_key {lit:?}")) ); } } else if meta.path.is_ident("renamed_from") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { renamed_from = Some(litstr.value()); } else { return Err( meta.error(format!("Invalid renamed_from {lit:?}")) ); } } else if meta.path.is_ident("extra") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { extra = Some(litstr.value()); } else { return Err(meta.error(format!("Invalid extra {lit:?}"))); } } else { consume_meta(meta); } Ok(()) })?; } else if attr.path().is_ident("seaography") { attr.parse_nested_meta(|meta| { if meta.path.is_ident("ignore") { seaography_ignore = true; } Ok(()) })?; } else if cfg!(feature = "with-json") && attr.path().is_ident("serde") { #[cfg(feature = "with-json")] attr.parse_nested_meta(|meta| { if meta.path.is_ident("rename") { if let Ok(lit) = meta.value().and_then(|v| v.parse::()) { // #[serde(rename = "xxx")] serde_rename = Some(lit.value()); } else { // #[serde(rename(serialize = "...", deserialize = "..."))] meta.parse_nested_meta(|nested| { if nested.path.is_ident("deserialize") { let lit: LitStr = nested.value()?.parse()?; serde_rename = Some(lit.value()); } else { consume_meta(nested); } Ok(()) })?; } } else { consume_meta(meta); } Ok(()) })?; } } #[cfg(feature = "with-json")] let json_key_name = serde_deserialize_name( &original_field_name, serde_rename.as_deref(), serde_rename_all, ); if let Some(enum_name) = enum_name { field_name = enum_name; } field_name = Ident::new(&escape_rust_keyword(field_name), ident.span()); let variant_attrs = match &column_name { Some(column_name) => quote! { #[sea_orm(column_name = #column_name)] #[doc = " Generated by sea-orm-macros"] }, None => quote! { #[doc = " Generated by sea-orm-macros"] }, }; let field_type = &field.ty; let field_type = quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces if ignore { continue; } else { columns_enum.push(quote! { #variant_attrs #field_name }); #[cfg(feature = "with-json")] columns_json_keys.push(quote! { Self::#field_name => #json_key_name }); } if is_primary_key { primary_keys.push(quote! { #variant_attrs #field_name }); } if !is_primary_key && is_auto_increment { return Err(syn::Error::new_spanned( ident, "auto_increment can only be used on primary_key", )); } if primary_key_types.len() > 1 && is_auto_increment { return Err(syn::Error::new_spanned( ident, "auto_increment cannot be used on composite primary_key", )); } if let Some(select_as) = select_as { columns_select_as.push(quote! { Self::#field_name => sea_orm::sea_query::ExprTrait::cast_as(expr, #select_as) }); } if let Some(save_as) = save_as { columns_save_as.push(quote! { Self::#field_name => sea_orm::sea_query::ExprTrait::cast_as(val, #save_as) }); } let field_type = if field_type.starts_with("Option<") { nullable = true; &field_type["Option<".len()..(field_type.len() - 1)] // Extract `T` out of `Option` } else { field_type.as_str() }; let field_span = field.span(); if is_primary_key && auto_increment.is_none() { for suffix in NOT_AUTO_INCRE_TYPE_SUFFIX { if field_type.ends_with(suffix) { auto_increment = Some(false); break; } } } let sea_query_col_type = super::value_type_match::column_type_expr(sql_type, field_type, field_span); let col_def = quote! { sea_orm::prelude::ColumnTypeTrait::def(#sea_query_col_type) }; let mut match_row = quote! { Self::#field_name => #col_def }; if nullable { match_row = quote! { #match_row.nullable() }; } if indexed { match_row = quote! { #match_row.indexed() }; } if unique { match_row = quote! { #match_row.unique() }; } if seaography_ignore { match_row = quote! { #match_row.seaography_ignore() }; } if unique_key.is_some() { match_row = quote! { #match_row.unique_key(#unique_key) }; } if renamed_from.is_some() { match_row = quote! { #match_row.renamed_from(#renamed_from) }; } if let Some(default_value) = default_value { match_row = quote! { #match_row.default_value(#default_value) }; } if let Some(comment) = comment { match_row = quote! { #match_row.comment(#comment) }; } if let Some(default_expr) = default_expr { match_row = quote! { #match_row.default(#default_expr) }; } if let Some(extra) = extra { match_row = quote! { #match_row.extra(#extra) }; } // match_row = quote! { #match_row.comment() }; columns_trait.push(match_row); let ty: syn::Type = syn::LitStr::new(field_type, field_span) .parse() .expect("field type error"); let enum_type_name = quote::quote_spanned! { field_span => <#ty as sea_orm::sea_query::ValueType>::enum_type_name() }; columns_enum_type_name.push(quote! { Self::#field_name => #enum_type_name }); } } } } // Add tailing comma if !columns_select_as.is_empty() { columns_select_as.push_punct(Comma::default()); } if !columns_save_as.is_empty() { columns_save_as.push_punct(Comma::default()); } let primary_key = { let auto_increment = match auto_increment { Some(value) => value && primary_keys.len() == 1, None => primary_keys.len() == 1, }; let primary_key_types = if primary_key_types.len() == 1 { let first = primary_key_types.first(); quote! { #first } } else { quote! { (#primary_key_types) } }; quote! { #[doc = " Generated by sea-orm-macros"] #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { #primary_keys } #[automatically_derived] impl PrimaryKeyTrait for PrimaryKey { type ValueType = #primary_key_types; fn auto_increment() -> bool { #auto_increment } } } }; let impl_model_ex = if model_ex { quote!() } else { quote! { impl Model { #[doc = " Generated by sea-orm-macros"] pub fn into_ex(self) -> Self { self } } } }; let with_json_impls = { #[cfg(feature = "with-json")] quote! { fn json_key(&self) -> &'static str { match self { #columns_json_keys } } } #[cfg(not(feature = "with-json"))] quote! {} }; Ok(quote! { #impl_model_ex #[doc = " Generated by sea-orm-macros"] #[derive(Copy, Clone, Debug, sea_orm::prelude::EnumIter, sea_orm::prelude::DeriveColumn)] pub enum Column { #columns_enum } #[automatically_derived] impl sea_orm::prelude::ColumnTrait for Column { type EntityName = Entity; fn def(&self) -> sea_orm::prelude::ColumnDef { match self { #columns_trait } } fn enum_type_name(&self) -> Option<&'static str> { match self { #columns_enum_type_name } } fn select_as(&self, expr: sea_orm::sea_query::Expr) -> sea_orm::sea_query::SimpleExpr { match self { #columns_select_as _ => sea_orm::prelude::ColumnTrait::select_enum_as(self, expr), } } fn save_as(&self, val: sea_orm::sea_query::Expr) -> sea_orm::sea_query::SimpleExpr { match self { #columns_save_as _ => sea_orm::prelude::ColumnTrait::save_enum_as(self, val), } } #with_json_impls } #entity_def #primary_key }) } ================================================ FILE: sea-orm-macros/src/derives/from_query_result.rs ================================================ use super::util::GetMeta; use proc_macro2::{Ident, TokenStream}; use quote::{ToTokens, format_ident, quote, quote_spanned}; use syn::{ Data, DataStruct, DeriveInput, Fields, Generics, Meta, ext::IdentExt, punctuated::Punctuated, token::Comma, }; #[derive(Debug)] enum Error { InputNotStruct, } pub(super) enum ItemType { Flat, Skip, Nested, } pub(super) struct DeriveFromQueryResult { pub ident: syn::Ident, pub generics: Generics, pub fields: Vec, } pub(super) struct FromQueryResultItem { pub typ: ItemType, pub ident: Ident, pub alias: Option, } /// Initially, we try to obtain the value for each field and check if it is an ordinary DB error /// (which we return immediatly), or a null error. /// /// ### Background /// /// Null errors do not necessarily mean that the deserialization as a whole fails, /// since structs embedding the current one might have wrapped the current one in an `Option`. /// In this case, we do not want to swallow other errors, which are very likely to actually be /// programming errors that should be noticed (and fixed). struct TryFromQueryResultCheck<'a>(bool, &'a FromQueryResultItem); impl ToTokens for TryFromQueryResultCheck<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let FromQueryResultItem { ident, typ, alias } = self.1; match typ { ItemType::Flat => { let name = alias .to_owned() .unwrap_or_else(|| ident.unraw().to_string()); tokens.extend(quote! { let #ident = match row.try_get_nullable(pre, #name) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } v => v, }; }); } ItemType::Skip => { tokens.extend(quote! { let #ident = std::default::Default::default(); }); } ItemType::Nested => { let prefix = if self.0 { let name = ident.unraw().to_string(); quote! { &format!("{pre}{}_", #name) } } else { quote! { pre } }; tokens.extend(quote! { let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(row, #prefix) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } v => v, }; }); } } } } struct TryFromQueryResultAssignment<'a>(&'a FromQueryResultItem); impl ToTokens for TryFromQueryResultAssignment<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let FromQueryResultItem { ident, typ, .. } = self.0; match typ { ItemType::Flat | ItemType::Nested => { tokens.extend(quote! { #ident: #ident?, }); } ItemType::Skip => { tokens.extend(quote! { #ident, }); } } } } impl DeriveFromQueryResult { fn new( DeriveInput { ident, data, generics, .. }: DeriveInput, ) -> Result { let parsed_fields = match data { Data::Struct(DataStruct { fields: Fields::Named(named), .. }) => named.named, _ => return Err(Error::InputNotStruct), }; let mut fields = Vec::with_capacity(parsed_fields.len()); for parsed_field in parsed_fields { let mut typ = ItemType::Flat; let mut alias = None; for attr in parsed_field.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { for meta in list.iter() { if meta.exists("skip") { typ = ItemType::Skip; } else if meta.exists("nested") { typ = ItemType::Nested; } else if let Some(alias_) = meta.get_as_kv("from_alias") { alias = Some(alias_); } else { alias = meta.get_as_kv("alias"); } } } } let ident = format_ident!("{}", parsed_field.ident.unwrap().to_string()); fields.push(FromQueryResultItem { typ, ident, alias }); } Ok(Self { ident, generics, fields, }) } fn expand(&self) -> syn::Result { Ok(self.impl_from_query_result(false)) } pub(super) fn impl_from_query_result(&self, prefix: bool) -> TokenStream { let Self { ident, generics, fields, } = self; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let ident_try_init: Vec<_> = fields .iter() .map(|s| TryFromQueryResultCheck(prefix, s)) .collect(); let ident_try_assign: Vec<_> = fields.iter().map(TryFromQueryResultAssignment).collect(); quote!( #[automatically_derived] impl #impl_generics sea_orm::FromQueryResult for #ident #ty_generics #where_clause { fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { Ok(Self::from_query_result_nullable(row, pre)?) } fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { #(#ident_try_init)* Ok(Self { #(#ident_try_assign)* }) } } ) } } pub fn expand_derive_from_query_result(input: DeriveInput) -> syn::Result { let ident_span = input.ident.span(); match DeriveFromQueryResult::new(input) { Ok(partial_model) => partial_model.expand(), Err(Error::InputNotStruct) => Ok(quote_spanned! { ident_span => compile_error!("you can only derive `FromQueryResult` on named struct"); }), } } ================================================ FILE: sea-orm-macros/src/derives/into_active_model.rs ================================================ use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use syn::{Meta, PathArguments, PathSegment, punctuated::Punctuated, token::Comma}; use super::util::GetMeta; enum Error { InputNotStruct, Syn(syn::Error), } /// Matches all potential ways to convert struct fields into ActiveModel ones pub(super) enum IntoActiveModelField { /// `IntoActiveValue::into_active_value(self.field).into()` Normal(syn::Ident), /// Option with fallback: `Some(v) => Set(v).into(), None => Set(expr).into()` WithDefault { ident: syn::Ident, expr: syn::Expr }, } impl IntoActiveModelField { pub(super) fn ident(&self) -> &syn::Ident { match self { IntoActiveModelField::Normal(ident) => ident, IntoActiveModelField::WithDefault { ident, .. } => ident, } } } /// Contains all the information extracted from the input struct and its attributes /// needed to generate the `IntoActiveModel` trait implementation. pub(super) struct DeriveIntoActiveModel { /// The identifier of the input struct pub ident: syn::Ident, /// Optional explicit ActiveModel type specified via `#[sea_orm(active_model = "Type")]` pub active_model: Option, /// handles provided struct fields pub fields: Vec, /// handles fields set by #[sea_orm(set(field = expr))] pub set_fields: Vec<(syn::Ident, syn::Expr)>, /// require all fields specified, no `..default::Default()` pub exhaustive: bool, } impl DeriveIntoActiveModel { /// This function finds attributes relevant for this macros: /// Container attributes (#[sea_orm(...)]) on the struct for: /// - active_model: explicit ActiveModel type /// - exhaustive: require all fields to be set /// - set(...): provided values for ommited fields /// /// Field attributes (#[sea_orm(...)]) with: /// - ignore/skip: exclude from conversion /// - default: fallback value for Option fields fn new(input: syn::DeriveInput) -> Result { let fields = match input.data { syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(syn::FieldsNamed { named, .. }), .. }) => named, _ => return Err(Error::InputNotStruct), }; let mut active_model = None; let mut set_fields = Vec::new(); let mut exhaustive = false; for attr in input.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } // Parse container attributes: #[sea_orm(...)] // Supports: // - active_model = "Type": explicitly specify the ActiveModel type // - exhaustive: require all ActiveModel fields to be explicitly set // - set(field = expr, ...): provide default values for fields not in the input struct if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { for meta in list { // Parse active_model attribute: #[sea_orm(active_model = "MyActiveModel")] if let Some(s) = meta.get_as_kv("active_model") { active_model = Some(syn::parse_str::(&s).map_err(Error::Syn)?); } // Parse exhaustive flag: #[sea_orm(exhaustive)] // When set, prevents using Default::default() for unspecified fields if meta.exists("exhaustive") { exhaustive = true; } // Parse set attribute: #[sea_orm(set(field1 = expr1, field2 = expr2, ...))] // Collects field assignments to be included in the generated ActiveModel if let Meta::List(meta_list) = &meta { if meta_list.path.is_ident("set") { let nested = meta_list .parse_args_with(Punctuated::::parse_terminated) .map_err(Error::Syn)?; for nested_meta in nested { if let Some(val) = nested_meta.get_as_kv_with_ident() { let (ident, expr_str) = val; let expr = syn::parse_str::(&expr_str) .map_err(Error::Syn)?; set_fields.push((ident, expr)); } } } } } } } // Field attributes let mut field_idents: Vec = Vec::new(); for field in fields.iter() { if let Some(f) = parse_field(field)? { field_idents.push(f); } } Ok(Self { ident: input.ident, active_model, fields: field_idents, set_fields, exhaustive, }) } fn expand(&self) -> syn::Result { let expanded_impl_into_active_model = self.impl_into_active_model(); Ok(expanded_impl_into_active_model) } /// Generates the implementation of `IntoActiveModel` trait for the input struct pub(super) fn impl_into_active_model(&self) -> TokenStream { let Self { ident, active_model, fields, set_fields, exhaustive, } = self; let mut active_model_ident = active_model .clone() .unwrap_or_else(|| syn::parse_str::("ActiveModel").unwrap()); // Create a type alias for qualified types let type_alias_definition = if is_qualified_type(&active_model_ident) { let type_alias = format_ident!("ActiveModelFor{ident}"); let type_def = quote!( type #type_alias = #active_model_ident; ); active_model_ident = syn::Type::Path(syn::TypePath { qself: None, path: syn::Path { leading_colon: None, segments: [PathSegment { ident: type_alias, arguments: PathArguments::None, }] .into_iter() .collect(), }, }); type_def } else { quote!() }; let field_idents: Vec<_> = fields.iter().map(|f| f.ident()).collect(); // Generate field conversion code based on field type let expanded_fields = fields.iter().map(|field| match field { IntoActiveModelField::Normal(ident) => quote!( sea_orm::IntoActiveValue::<_>::into_active_value(self.#ident).into() ), IntoActiveModelField::WithDefault { ident, expr } => quote!({ match self.#ident.into() { Some(v) => sea_orm::ActiveValue::Set(v).into(), None => sea_orm::ActiveValue::Set(#expr).into(), } }), }); // Add custom field assignments from #[sea_orm(set(field = expr))] let (set_idents, set_exprs): (Vec<_>, Vec<_>) = set_fields.iter().cloned().unzip(); let expanded_sets = set_exprs.iter().map(|expr| { quote!( sea_orm::ActiveValue::Set(#expr) ) }); // Add defaults(Unset) unless exhaustive mode is enabled let rest = if *exhaustive { quote!() } else { quote!(..::std::default::Default::default()) }; quote!( #type_alias_definition #[automatically_derived] impl sea_orm::IntoActiveModel<#active_model_ident> for #ident { fn into_active_model(self) -> #active_model_ident { #active_model_ident { #( #field_idents: #expanded_fields, )* #( #set_idents: #expanded_sets, )* #rest } } } ) } } /// Parse field-level attributes on each struct field /// Supports: /// - ignore or skip: exclude the field from conversion /// - default = "expr": provide a fallback value for Option fields (Some(v) => Set(v), None => Set(expr)) fn parse_field(field: &syn::Field) -> Result, Error> { let ident = field.ident.as_ref().unwrap().clone(); // Default expression for this field let mut default_expr: Option = None; for attr in field.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } // Parse the attribute arguments: #[sea_orm(...)] if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { for meta in list.iter() { // Check for ignore/skip: #[sea_orm(ignore)] or #[sea_orm(skip)] if meta.exists("ignore") || meta.exists("skip") { return Ok(None); } // Check for bare default: #[sea_orm(default)] if meta.exists("default") { if default_expr.is_some() { return Err(Error::Syn(syn::Error::new_spanned( meta, "duplicate `default` attribute", ))); } let expr: syn::Expr = syn::parse_quote!(::core::default::Default::default()); default_expr = Some(expr); continue; // Skip next default check } // Check for default value: #[sea_orm(default = "expr")] if let Some(expr_str) = meta.get_as_kv("default") { // Error on duplicate `default` if default_expr.is_some() { return Err(Error::Syn(syn::Error::new_spanned( meta, "duplicate `default` attribute", ))); } // Parse the expression string into a syn::Expr let expr = syn::parse_str::(&expr_str).map_err(Error::Syn)?; default_expr = Some(expr); } } } } // Finnaly match and return appropriate field type if let Some(expr) = default_expr { Ok(Some(IntoActiveModelField::WithDefault { ident, expr })) } else { Ok(Some(IntoActiveModelField::Normal(ident))) } } /// Method to derive the ActiveModel from the [ActiveModelTrait](sea_orm::ActiveModelTrait) pub fn expand_into_active_model(input: syn::DeriveInput) -> syn::Result { let ident_span = input.ident.span(); match DeriveIntoActiveModel::new(input) { Ok(model) => model.expand(), Err(Error::InputNotStruct) => Ok(quote_spanned! { ident_span => compile_error!("you can only derive IntoActiveModel on structs"); }), Err(Error::Syn(err)) => Err(err), } } fn is_qualified_type(ty: &syn::Type) -> bool { matches!(ty, syn::Type::Path(syn::TypePath { qself: Some(_), .. })) } ================================================ FILE: sea-orm-macros/src/derives/migration.rs ================================================ use proc_macro2::TokenStream; use quote::quote; struct DeriveMigrationName { ident: syn::Ident, } impl DeriveMigrationName { fn new(input: syn::DeriveInput) -> Self { let ident = input.ident; DeriveMigrationName { ident } } fn expand(&self) -> TokenStream { let ident = &self.ident; quote!( #[automatically_derived] impl sea_orm_migration::MigrationName for #ident { fn name(&self) -> &str { sea_orm_migration::util::get_file_stem(file!()) } } ) } } /// Method to derive a MigrationName pub fn expand_derive_migration_name(input: syn::DeriveInput) -> syn::Result { Ok(DeriveMigrationName::new(input).expand()) } ================================================ FILE: sea-orm-macros/src/derives/mod.rs ================================================ mod active_enum; mod active_enum_display; mod active_model; mod active_model_behavior; mod active_model_ex; mod arrow_schema; mod attributes; mod case_style; mod column; mod derive_iden; mod entity; mod entity_loader; mod entity_model; mod from_query_result; mod into_active_model; mod migration; mod model; mod model_ex; mod partial_model; mod primary_key; mod related_entity; mod relation; mod try_getable_from_json; mod typed_column; mod util; mod value_type; mod value_type_match; pub use active_enum::*; pub use active_enum_display::*; pub use active_model::*; pub use active_model_behavior::*; pub use active_model_ex::*; pub use arrow_schema::*; pub use column::*; pub use derive_iden::*; pub use entity::*; pub use entity_model::*; pub use from_query_result::*; pub use into_active_model::*; pub use migration::*; pub use model::*; pub use model_ex::*; pub use partial_model::*; pub use primary_key::*; pub use related_entity::*; pub use relation::*; pub use try_getable_from_json::*; pub use typed_column::*; pub use value_type::*; ================================================ FILE: sea-orm-macros/src/derives/model.rs ================================================ use super::{ attributes::derive_attr, util::{escape_rust_keyword, field_not_ignored, trim_starting_raw_identifier}, }; use heck::ToUpperCamelCase; use itertools::izip; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::iter::FromIterator; use syn::{Attribute, Data, Expr, Ident, LitStr}; pub(crate) struct DeriveModel { column_idents: Vec, entity_ident: Ident, field_idents: Vec, field_types: Vec, ident: Ident, ignore_attrs: Vec, } impl DeriveModel { pub fn new(ident: &Ident, data: &Data, attrs: &[Attribute]) -> syn::Result { let fields = match data { syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(syn::FieldsNamed { named, .. }), .. }) => named, _ => { return Err(syn::Error::new_spanned( ident, "You can only derive DeriveModel on structs", )); } }; let sea_attr = derive_attr::SeaOrm::try_from_attributes(attrs)?.unwrap_or_default(); let entity_ident = sea_attr.entity.unwrap_or_else(|| format_ident!("Entity")); let field_idents = fields .iter() .map(|field| field.ident.as_ref().unwrap().clone()) .collect(); let field_types = fields.iter().map(|field| field.ty.clone()).collect(); let column_idents = fields .iter() .map(|field| { let ident = field.ident.as_ref().unwrap().to_string(); let ident = trim_starting_raw_identifier(ident).to_upper_camel_case(); let ident = escape_rust_keyword(ident); let mut ident = format_ident!("{}", &ident); field .attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("enum_name") { ident = syn::parse_str(&meta.value()?.parse::()?.value()) .unwrap(); } else { // Reads the value expression to advance the parse stream. // Some parameters, such as `primary_key`, do not have any value, // so ignoring an error occurred here. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) }) })?; Ok(ident) }) .collect::>()?; let ignore_attrs = fields .iter() .map(|field| !field_not_ignored(field)) .collect(); Ok(DeriveModel { column_idents, entity_ident, field_idents, field_types, ident: ident.clone(), ignore_attrs, }) } fn expand(&self) -> syn::Result { let expanded_impl_from_query_result = self.impl_from_query_result(); let expanded_impl_model_trait = self.impl_model_trait(); Ok(TokenStream::from_iter([ expanded_impl_from_query_result, expanded_impl_model_trait, ])) } fn impl_from_query_result(&self) -> TokenStream { let ident = &self.ident; let field_idents = &self.field_idents; let column_idents = &self.column_idents; let field_types = &self.field_types; let ignore_attrs = &self.ignore_attrs; let (field_readers, field_values): (Vec, Vec) = izip!( field_idents.iter(), column_idents, field_types, ignore_attrs, ) .map(|(field_ident, column_ident, field_type, &ignore)| { if ignore { let reader = quote! { let #field_ident: Option<()> = None; }; let unwrapper = quote! { #field_ident: Default::default() }; (reader, unwrapper) } else { let reader = quote! { let #field_ident = row.try_get_nullable::>( pre, sea_orm::IdenStatic::as_str( &<::Entity as sea_orm::entity::EntityTrait>::Column::#column_ident ).into() )?; }; let unwrapper = quote! { #field_ident: #field_ident.ok_or_else(|| sea_orm::DbErr::Type( format!( "Missing value for column '{}'", sea_orm::IdenStatic::as_str( &<::Entity as sea_orm::entity::EntityTrait>::Column::#column_ident ) ) ))? }; (reader, unwrapper) } }) .unzip(); // When a nested model is loaded via LEFT JOIN, all its fields may be NULL. // In that case we interpret it as "no nested row" (i.e., Option::None). // This check detects that condition by testing if all non-ignored fields are NULL. let all_null_check = { let checks: Vec<_> = izip!(field_idents, ignore_attrs) .filter_map(|(field_ident, &ignore)| { if ignore { None } else { Some(quote! { #field_ident.is_none() }) } }) .collect(); quote! { true #( && #checks )* } }; quote!( #[automatically_derived] impl sea_orm::FromQueryResult for #ident { fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { Self::from_query_result_nullable(row, pre).map_err(Into::into) } fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { #(#field_readers)* if #all_null_check { return Err(sea_orm::TryGetError::Null("All fields of nested model are null".into())); } Ok(Self { #(#field_values),* }) } } ) } pub fn impl_model_trait<'a>(&'a self) -> TokenStream { let ident = &self.ident; let entity_ident = &self.entity_ident; let ignore_attrs = &self.ignore_attrs; let ignore = |(ident, ignore): (&'a Ident, &bool)| -> Option<&'a Ident> { if *ignore { None } else { Some(ident) } }; let field_idents: Vec<&Ident> = self .field_idents .iter() .zip(ignore_attrs) .filter_map(ignore) .collect(); let column_idents: Vec<&Ident> = self .column_idents .iter() .zip(ignore_attrs) .filter_map(ignore) .collect(); let get_field_type: Vec = self .field_types .iter() .zip(ignore_attrs) .filter_map(|(ty, ignore)| { if *ignore { None } else { Some(quote!(<#ty as sea_orm::sea_query::ValueType>::array_type())) } }) .collect(); let missing_field_msg = format!("field does not exist on {ident}"); quote!( #[automatically_derived] impl sea_orm::ModelTrait for #ident { type Entity = #entity_ident; fn get(&self, c: ::Column) -> sea_orm::Value { match c { #(::Column::#column_idents => self.#field_idents.clone().into(),)* } } fn get_value_type(c: ::Column) -> sea_orm::sea_query::ArrayType { match c { #(::Column::#column_idents => #get_field_type,)* } } fn try_set(&mut self, c: ::Column, v: sea_orm::Value) -> Result<(), sea_orm::DbErr> { match c { #(::Column::#column_idents => self.#field_idents = sea_orm::sea_query::ValueType::try_from(v).map_err(|e| sea_orm::DbErr::Type(e.to_string()))?,)* _ => return Err(sea_orm::DbErr::Type(#missing_field_msg.to_owned())), } Ok(()) } } ) } } pub fn expand_derive_model( ident: &Ident, data: &Data, attrs: &[Attribute], ) -> syn::Result { DeriveModel::new(ident, data, attrs)?.expand() } ================================================ FILE: sea-orm-macros/src/derives/model_ex.rs ================================================ use super::attributes::compound_attr; use super::entity_loader::{EntityLoaderField, EntityLoaderSchema, expand_entity_loader}; use super::util::{extract_compound_entity, format_field_ident, is_compound_field}; use super::{expand_typed_column, model::DeriveModel}; use heck::ToUpperCamelCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; use std::collections::{BTreeMap, HashMap}; use syn::{ Attribute, Data, Expr, Fields, ItemStruct, Lit, Meta, Type, parse_quote, punctuated::Punctuated, token::Comma, }; pub fn expand_sea_orm_model(input: ItemStruct, compact: bool) -> syn::Result { let model = input.ident; let vis = input.vis; let mut all_fields = input.fields; let mut model_attrs: Vec = Vec::new(); let mut model_ex_attrs: Vec = Vec::new(); let mut has_arrow_schema = false; for attr in input.attrs { if !attr.path().is_ident("sea_orm") { model_attrs.push(attr.clone()); model_ex_attrs.push(attr); continue; } let mut other_attrs = Punctuated::::new(); attr.parse_nested_meta(|meta| { let is_model = meta.path.is_ident("model_attrs"); let is_model_ex = meta.path.is_ident("model_ex_attrs"); if is_model || is_model_ex { let content; syn::parenthesized!(content in meta.input); use syn::parse::Parse; let nested_metas = content.parse_terminated(Meta::parse, Comma)?; for m in nested_metas { let new_attr: Attribute = parse_quote!( #[#m] ); if is_model { model_attrs.push(new_attr); } else { model_ex_attrs.push(new_attr); } } } else if meta.path.is_ident("arrow_schema") { has_arrow_schema = true; } else { let path = &meta.path; if meta.input.peek(syn::Token![=]) { let value: Expr = meta.value()?.parse()?; other_attrs.push(parse_quote!( #path = #value )); } else if meta.input.is_empty() || meta.input.peek(Comma) { other_attrs.push(parse_quote!( #path )); } else { let content; syn::parenthesized!(content in meta.input); let tokens: TokenStream = content.parse()?; other_attrs.push(parse_quote!( #path(#tokens) )); } } Ok(()) })?; if !other_attrs.is_empty() { let attr: Attribute = parse_quote!( #[sea_orm(#other_attrs)] ); model_attrs.push(attr.clone()); model_ex_attrs.push(attr); } } if has_arrow_schema { model_attrs.push(parse_quote!(#[derive(DeriveArrowSchema)])); } let model_ex = Ident::new(&format!("{model}Ex"), model.span()); for attr in &mut model_ex_attrs { if attr.path().is_ident("derive") { if let Meta::List(list) = &mut attr.meta { let mut new_list: Punctuated<_, Comma> = Punctuated::new(); list.parse_nested_meta(|meta| { if meta.path.is_ident("Eq") { // skip } else if meta.path.is_ident("DeriveEntityModel") { // replace macro new_list.push(parse_quote!(DeriveModelEx)); new_list.push(parse_quote!(DeriveActiveModelEx)); } else { new_list.push(meta.path); } Ok(()) })?; *attr = parse_quote!(#[derive( #new_list )]); } } } let compact_model = if compact { quote!(#[sea_orm(compact_model)]) } else { quote!() }; let mut model_fields = Vec::new(); for field in all_fields.iter_mut() { let field_type = &field.ty; let field_type = quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces if is_compound_field(&field_type) { let entity_path = extract_compound_entity(&field_type); if field_type.starts_with("Option<") || field_type.starts_with("HasOne<") { field.ty = syn::parse_str(&format!("HasOne < {entity_path} >"))?; } else { field.ty = syn::parse_str(&format!("HasMany < {entity_path} >"))?; } } else { model_fields.push(field); } } Ok(quote! { #(#model_attrs)* #[sea_orm(model_ex)] #vis struct #model { #(#model_fields),* } #(#model_ex_attrs)* #compact_model #vis struct #model_ex #all_fields }) } pub fn expand_derive_model_ex( ident: Ident, data: Data, attrs: Vec, ) -> syn::Result { let mut compact = false; let mut model_fields: Vec = Vec::new(); let mut compound_fields: Vec = Vec::new(); let mut impl_related = Vec::new(); let mut entity_loader_schema = EntityLoaderSchema::default(); let mut unique_keys = BTreeMap::new(); attrs .iter() .filter(|attr| attr.path().is_ident("sea_orm")) .try_for_each(|attr| { attr.parse_nested_meta(|meta| { if meta.path.is_ident("compact_model") { compact = true; } else { // Reads the value expression to advance the parse stream. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) }) })?; if let Data::Struct(item_struct) = &data { if let Fields::Named(fields) = &item_struct.fields { for field in &fields.named { if let Some(ident) = &field.ident { let field_type = &field.ty; let field_type = quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces if is_compound_field(&field_type) { let compound_attrs = compound_attr::SeaOrm::from_attributes(&field.attrs).ok(); let is_reverse = compound_attrs .as_ref() .map(|r| r.reverse.is_some()) .unwrap_or_default(); let relation_enum = compound_attrs .as_ref() .and_then(|r| r.relation_enum.clone()); if field_type.starts_with("HasOne<") { entity_loader_schema.fields.push(EntityLoaderField { is_one: true, is_self: field_type == "HasOne", is_reverse, field: ident.clone(), entity: extract_compound_entity(&field_type).to_owned(), relation_enum, via: None, }); } else if field_type.starts_with("HasMany<") { entity_loader_schema.fields.push(EntityLoaderField { is_one: false, is_self: field_type == "HasMany", is_reverse, field: ident.clone(), entity: extract_compound_entity(&field_type).to_owned(), relation_enum, via: compound_attrs.as_ref().and_then(|r| r.via.clone()), }); } if let Some(attrs) = compound_attrs { if compact && (attrs.has_one.is_some() || attrs.has_many.is_some() || attrs.belongs_to.is_some()) { return Err(syn::Error::new_spanned( ident, "You cannot use #[has_one / has_many / belongs_to] on #[sea_orm::compact_model], please use #[sea_orm::model] instead.", )); } else if attrs.belongs_to.is_some() && !field_type.starts_with("HasOne<") { return Err(syn::Error::new_spanned( ident, "belongs_to must be paired with HasOne", )); } else if attrs.has_one.is_some() && !field_type.starts_with("HasOne<") { return Err(syn::Error::new_spanned( ident, "has_one must be paired with HasOne", )); } else if attrs.has_many.is_some() && !field_type.starts_with("HasMany<") { return Err(syn::Error::new_spanned( ident, "has_many must be paired with HasMany", )); } impl_related.push((attrs, field_type)); } compound_fields.push(format_field_ident(field)); } else { // scalar field for attr in field.attrs.iter() { // still have to parse column attributes to extract unique keys if attr.path().is_ident("sea_orm") { attr.parse_nested_meta(|meta| { if meta.path.is_ident("unique") { unique_keys.insert( ident.clone(), vec![(ident.clone(), field.ty.clone())], ); } else if meta.path.is_ident("unique_key") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { unique_keys .entry(litstr.parse()?) .or_default() .push((ident.clone(), field.ty.clone())); } else { return Err( meta.error(format!("Invalid unique_key {lit:?}")) ); } } else { // Reads the value expression to advance the parse stream. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) })?; } } model_fields.push(format_field_ident(field)); } } } } } let impl_model_trait = DeriveModel::new(&ident, &data, &attrs)?.impl_model_trait(); let impl_from_model = quote! { impl Model { #[doc = " Generated by sea-orm-macros"] pub fn into_ex(self) -> ModelEx { self.into() } } #[automatically_derived] impl std::convert::From for ModelEx { fn from(m: Model) -> Self { Self { #(#model_fields: m.#model_fields,)* #(#compound_fields: Default::default(),)* } } } #[automatically_derived] impl std::convert::From for Model { fn from(m: ModelEx) -> Self { Self { #(#model_fields: m.#model_fields,)* } } } #[automatically_derived] impl PartialEq for Model { fn eq(&self, other: &ModelEx) -> bool { true #(&& self.#model_fields == other.#model_fields)* } } #[automatically_derived] impl PartialEq for ModelEx { fn eq(&self, other: &Model) -> bool { true #(&& self.#model_fields == other.#model_fields)* } } }; let mut relation_enum_variants: Punctuated<_, Comma> = Punctuated::new(); let mut related_entity_enum_variants: Punctuated<_, Comma> = Punctuated::new(); let impl_related_trait = { let mut ts = TokenStream::new(); let mut seen_entity = HashMap::new(); for (_, field_type) in impl_related.iter() { let entity_path = extract_compound_entity(field_type); *seen_entity.entry(entity_path).or_insert(0) += 1; } for (attrs, field_type) in impl_related.iter() { if attrs.self_ref.is_some() && attrs.via.is_some() { ts.extend(expand_impl_related_self_via(attrs, field_type)?); } else { if attrs.self_ref.is_some() && attrs.relation_enum.is_none() { return Err(syn::Error::new_spanned( ident, "Please specify `relation_enum` for `self_ref`", )); } if let Some(var) = relation_enum_variant(attrs, field_type) { relation_enum_variants.push(var); } if attrs.self_ref.is_some() && field_type.starts_with("HasMany<") { // related entity is already provided by the HasOne item // so self_ref HasMany has to be skipped continue; } let (first, second) = related_entity_enum_variant(attrs, field_type); related_entity_enum_variants.push(first); if let Some(second) = second { related_entity_enum_variants.push(second); } let entity_path = extract_compound_entity(field_type); if *seen_entity.get(entity_path).unwrap() == 1 { // prevent impl trait for same entity twice ts.extend(expand_impl_related_trait(attrs, field_type)?); } } } ts }; let relation_enum = if !compact { quote! { #[doc = " Generated by sea-orm-macros"] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #relation_enum_variants } } } else { // for backwards compatibility with compact models quote!() }; let related_entity_enum = if !compact { quote! { #[doc = " Generated by sea-orm-macros"] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] pub enum RelatedEntity { #related_entity_enum_variants } } } else { // for backwards compatibility with compact models quote!() }; let (typed_column, typed_column_const) = expand_typed_column(&data)?; let (entity_find_by_key, loader_filter_by_key) = expand_find_by_unique_key(unique_keys); let entity_loader = expand_entity_loader(entity_loader_schema); Ok(quote! { #typed_column #typed_column_const #impl_from_model #impl_model_trait #relation_enum #impl_related_trait #related_entity_enum #entity_loader impl Entity { #[doc = " Generated by sea-orm-macros"] pub const COLUMN: TypedColumn = COLUMN; #entity_find_by_key } impl EntityLoader { #loader_filter_by_key } }) } fn relation_enum_variant(attr: &compound_attr::SeaOrm, ty: &str) -> Option { let (related_entity, relation_enum) = get_related(attr, ty); if attr.belongs_to.is_some() { let belongs_to = Ident::new("belongs_to", Span::call_site()); let from = format_tuple( "", "Column", &attr .from .as_ref() .expect("Must specify `from` and `to` on belongs_to relation") .value(), ); let to = format_tuple( related_entity.trim_end_matches("::Entity"), "Column", &attr .to .as_ref() .expect("Must specify `from` and `to` on belongs_to relation") .value(), ); let mut extra: Punctuated<_, Comma> = Punctuated::new(); if let Some(on_update) = &attr.on_update { let tag = Ident::new("on_update", on_update.span()); extra.push(quote!(#tag = #on_update)) } if let Some(on_delete) = &attr.on_delete { let tag = Ident::new("on_delete", on_delete.span()); extra.push(quote!(#tag = #on_delete)) } if let Some(()) = &attr.skip_fk { extra.push(quote!(skip_fk)) } Some(quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(#belongs_to = #related_entity, from = #from, to = #to, #extra)] #relation_enum }) } else if attr.self_ref.is_some() && attr.via.is_none() && attr.from.is_some() && attr.to.is_some() { let belongs_to = Ident::new("belongs_to", Span::call_site()); let from = format_tuple("", "Column", &attr.from.as_ref().unwrap().value()); let to = format_tuple("", "Column", &attr.to.as_ref().unwrap().value()); let mut extra: Punctuated<_, Comma> = Punctuated::new(); if let Some(on_update) = &attr.on_update { let tag = Ident::new("on_update", on_update.span()); extra.push(quote!(#tag = #on_update)) } if let Some(on_delete) = &attr.on_delete { let tag = Ident::new("on_delete", on_delete.span()); extra.push(quote!(#tag = #on_delete)) } if let Some(()) = &attr.skip_fk { extra.push(quote!(skip_fk)) } Some(quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(#belongs_to = "Entity", from = #from, to = #to, #extra)] #relation_enum }) } else if attr.self_ref.is_some() && attr.via.is_none() && attr.relation_reverse.is_some() && ty.starts_with("HasMany<") { let has_many = Ident::new("has_many", Span::call_site()); #[allow(clippy::unnecessary_unwrap)] let via_rel = format!( "Relation::{}", attr.relation_reverse.as_ref().unwrap().value() ); Some(quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(#has_many = "Entity", via_rel = #via_rel)] #relation_enum }) } else if attr.has_many.is_some() && attr.via.is_none() { // skip junction relation let has_many = Ident::new("has_many", Span::call_site()); let mut extra: Punctuated<_, Comma> = Punctuated::new(); if let Some(via_rel) = &attr.via_rel { let tag = Ident::new("via_rel", via_rel.span()); let via_rel = format!("Relation::{}", via_rel.value()); extra.push(quote!(#tag = #via_rel)) } Some(quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(#has_many = #related_entity, #extra)] #relation_enum }) } else if attr.has_one.is_some() { let has_one = Ident::new("has_one", Span::call_site()); let mut extra: Punctuated<_, Comma> = Punctuated::new(); if let Some(via_rel) = &attr.via_rel { let tag = Ident::new("via_rel", via_rel.span()); let via_rel = format!("Relation::{}", via_rel.value()); extra.push(quote!(#tag = #via_rel)) } Some(quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(#has_one = #related_entity, #extra)] #relation_enum }) } else { None } } fn related_entity_enum_variant( attr: &compound_attr::SeaOrm, ty: &str, ) -> (TokenStream, Option) { let (related_entity, relation_enum) = get_related(attr, ty); let extra = if attr.relation_enum.is_some() { let relation_def = format!("Relation::{relation_enum}.def()"); quote!(, def = #relation_def) } else { quote!() }; let first = quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(entity = #related_entity #extra)] #relation_enum }; let second = if attr.self_ref.is_some() { let relation_def = format!("Relation::{relation_enum}.def().rev()"); let relation_enum_ref = if let Some(relation_reverse) = &attr.relation_reverse { Ident::new(&relation_reverse.value(), relation_reverse.span()) } else { Ident::new(&format!("{relation_enum}Reverse"), relation_enum.span()) }; Some(quote! { #[doc = " Generated by sea-orm-macros"] #[sea_orm(entity = #related_entity def = #relation_def)] #relation_enum_ref }) } else { None }; (first, second) } fn expand_impl_related_trait(attr: &compound_attr::SeaOrm, ty: &str) -> syn::Result { if attr.has_one.is_some() || attr.has_many.is_some() || attr.belongs_to.is_some() { let (related_entity, relation_enum) = get_related(attr, ty); let related_entity: TokenStream = related_entity.parse().unwrap(); if let Some(via_lit) = &attr.via { let via = via_lit.value(); let mut junction = via.as_str(); let mut via_related = ""; if let Some((prefix, suffix)) = via.split_once("::") { junction = prefix; via_related = suffix; } let junction = Ident::new(junction, via_lit.span()); let relation_def = quote!(super::#junction::Relation::#relation_enum.def()); let via_relation_def: TokenStream = if !via_related.is_empty() { let via_related = Ident::new(via_related, via_lit.span()); quote!(super::#junction::Relation::#via_related.def().rev()) } else { quote!(>::to().rev()) }; Ok(quote! { #[doc = " Generated by sea-orm-macros"] impl Related<#related_entity> for Entity { fn to() -> RelationDef { #relation_def } fn via() -> Option { Some(#via_relation_def) } } }) // #[sea_orm(relation, via = "cakes_bakers::Cake")] // impl Related for Entity { // fn to() -> RelationDef { // super::cakes_bakers::Relation::Baker.def() // } // fn via() -> Option { // Some(super::cakes_bakers::Relation::Cake.def().rev()) // } // } } else { let relation_def = quote!(Relation::#relation_enum.def()); Ok(quote! { #[doc = " Generated by sea-orm-macros"] impl Related<#related_entity> for Entity { fn to() -> RelationDef { #relation_def } } }) // #[sea_orm(relation)] // impl Related for Entity { // fn to() -> RelationDef { // Relation::Bakery.def() // } // } } } else { Ok(quote!()) } } fn expand_impl_related_self_via( attr: &compound_attr::SeaOrm, ty: &str, ) -> syn::Result { let Some(via) = &attr.via else { return Err(syn::Error::new( Span::call_site(), "Please specify the junction Entity `via` for `self_ref`.", )); }; if ty != "HasMany" { return Err(syn::Error::new_spanned( via, "self_ref + via field type must be `HasMany`", )); } if attr.reverse.is_some() { return Ok(quote!()); } if let (Some(from), Some(to)) = (&attr.from, &attr.to) { let junction = Ident::new(&via.value(), via.span()); let from = Ident::new(&from.value(), from.span()); let to = Ident::new(&to.value(), to.span()); Ok(quote! { #[doc = " Generated by sea-orm-macros"] impl RelatedSelfVia for Entity { fn to() -> RelationDef { super::#junction::Relation::#to.def() } fn via() -> RelationDef { super::#junction::Relation::#from.def().rev() } } }) // #[sea_orm(self_ref, via = "user_follower", from = "User", to = "Follower")] // impl RelatedSelfVia for Entity { // fn to() -> RelationDef { // super::user_follower::Relation::Follower.def() // } // fn via() -> RelationDef { // super::user_follower::Relation::User.def().rev() // } // } } else { Ok(quote!()) } } fn get_related<'a>(attr: &compound_attr::SeaOrm, ty: &'a str) -> (&'a str, Ident) { let related_entity = extract_compound_entity(ty); let relation_enum = if let Some(relation_enum) = &attr.relation_enum { Ident::new( &relation_enum.value().to_upper_camel_case(), relation_enum.span(), ) } else { Ident::new( &infer_relation_name_from_entity(related_entity).to_upper_camel_case(), Span::call_site(), ) }; (related_entity, relation_enum) } fn infer_relation_name_from_entity(s: &str) -> &str { let s = s.trim_end_matches("::Entity"); if let Some((_, suffix)) = s.rsplit_once("::") { return suffix; } s } fn expand_find_by_unique_key( unique_keys: BTreeMap>, ) -> (TokenStream, TokenStream) { let mut entity_find_by_key = TokenStream::new(); let mut loader_filter_by_key = TokenStream::new(); for (name, columns) in unique_keys { let find_method = format_ident!("find_by_{}", name); let filter_method = format_ident!("filter_by_{}", name); let delete_method = format_ident!("delete_by_{}", name); if columns.len() > 1 { let key_type = columns.iter().map(|(_, ty)| ty).collect::>(); let filters = columns .iter() .enumerate() .map(|(i, (col, _))| { let i = syn::Index::from(i); let col = to_upper_camel_case(col); quote!(Column::#col.eq(v.#i)) }) .collect::>(); entity_find_by_key.extend(quote! { #[doc = " Generated by sea-orm-macros"] pub fn #find_method(v: (#(#key_type),*)) -> Select { Self::find() #(.filter(#filters))* } #[doc = " Generated by sea-orm-macros"] pub fn #delete_method(v: (#(#key_type),*)) -> sea_orm::ValidatedDeleteOne { sea_orm::Delete::_one_only_for_use_by_model_ex(Entity) #(.filter(#filters))* } }); loader_filter_by_key.extend(quote! { #[doc = " Generated by sea-orm-macros"] pub fn #filter_method(mut self, v: (#(#key_type),*)) -> Self { #(self.filter_mut(#filters);)* self } }); } else { let col = to_upper_camel_case(&columns[0].0); let key_type = &columns[0].1; entity_find_by_key.extend(quote! { #[doc = " Generated by sea-orm-macros"] pub fn #find_method(v: impl Into<#key_type>) -> Select { Self::find().filter(Column::#col.eq(v.into())) } }); loader_filter_by_key.extend(quote! { #[doc = " Generated by sea-orm-macros"] pub fn #filter_method(mut self, v: impl Into<#key_type>) -> Self { self.filter_mut(Column::#col.eq(v.into())); self } }); } } (entity_find_by_key, loader_filter_by_key) } fn format_tuple(prefix: &str, middle: &str, suffix: &str) -> String { use std::fmt::Write; let parts = if suffix.starts_with('(') && suffix.ends_with(')') { suffix[1..suffix.len() - 1] .split(',') .map(|s| s.trim()) .collect() } else { vec![suffix] }; let mut output = String::new(); if parts.len() > 1 { output.write_char('(').unwrap(); } for (i, suffix) in parts.iter().enumerate() { let mut part = String::new(); part.write_str(prefix).unwrap(); if !part.is_empty() { part.write_str("::").unwrap(); } part.write_str(middle).unwrap(); part.write_str("::").unwrap(); part.write_str(&suffix.to_upper_camel_case()).unwrap(); if i > 0 { output.write_str(", ").unwrap(); } output.write_str(&part).unwrap(); } if parts.len() > 1 { output.write_char(')').unwrap(); } output } fn to_upper_camel_case(i: &Ident) -> Ident { Ident::new(&i.to_string().to_upper_camel_case(), Span::call_site()) } #[cfg(test)] mod test { use super::format_tuple; #[test] fn test_format_tuple() { assert_eq!(format_tuple("", "Column", "Id"), "Column::Id"); assert_eq!(format_tuple("super", "Column", "Id"), "super::Column::Id"); assert_eq!( format_tuple("", "Column", "(A, B)"), "(Column::A, Column::B)" ); assert_eq!( format_tuple("super", "Column", "(A, B)"), "(super::Column::A, super::Column::B)" ); } } ================================================ FILE: sea-orm-macros/src/derives/partial_model.rs ================================================ use heck::ToUpperCamelCase; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::{ Expr, Meta, Type, ext::IdentExt, punctuated::Punctuated, spanned::Spanned, token::Comma, }; use super::from_query_result::{ DeriveFromQueryResult, FromQueryResultItem, ItemType as FqrItemType, }; use super::into_active_model::{DeriveIntoActiveModel, IntoActiveModelField}; use super::util::GetMeta; #[derive(Debug)] enum Error { InputNotStruct, EntityNotSpecified, NotSupportGeneric(Span), OverlappingAttributes(Span), Syn(syn::Error), } #[derive(Debug, PartialEq, Eq)] enum ColumnAs { /// alias from a column in model Col { col: Option, field: syn::Ident, }, /// from an expr Expr { expr: syn::Expr, field: syn::Ident, }, /// nesting another struct Nested { typ: Type, field: syn::Ident, alias: Option, }, Skip(syn::Ident), } struct DerivePartialModel { entity: Option, active_model: Option, model_alias: Option, ident: syn::Ident, fields: Vec, from_query_result: bool, into_active_model: bool, } impl DerivePartialModel { fn new(input: syn::DeriveInput) -> Result { if !input.generics.params.is_empty() { return Err(Error::NotSupportGeneric(input.generics.params.span())); } let fields = match input.data { syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(syn::FieldsNamed { named, .. }), .. }) => named, _ => return Err(Error::InputNotStruct), }; let mut entity = None; let mut entity_string = String::new(); let mut active_model = None; let mut model_alias = None; let mut from_query_result = true; let mut into_active_model = false; for attr in input.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { for meta in list { if let Some(s) = meta.get_as_kv("entity") { entity = Some(syn::parse_str::(&s).map_err(Error::Syn)?); entity_string = s; } else if let Some(s) = meta.get_as_kv("alias") { model_alias = Some(s); } else if let Some(s) = meta.get_as_kv("from_query_result") { if s == "false" { from_query_result = false; } } else if meta.exists("into_active_model") { into_active_model = true; } } } } if into_active_model { active_model = Some( syn::parse_str::(&format!( "<{entity_string} as EntityTrait>::ActiveModel" )) .map_err(Error::Syn)?, ); } let mut column_as_list = Vec::with_capacity(fields.len()); for field in fields { let field_span = field.span(); let mut from_col = None; let mut from_expr = None; let mut nested = false; let mut nested_alias = None; let mut skip = false; for attr in field.attrs.iter() { if !attr.path().is_ident("sea_orm") { continue; } if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { for meta in list.iter() { if meta.exists("skip") { skip = true; } else if meta.exists("nested") { nested = true; } else if let Some(s) = meta.get_as_kv("from_col") { from_col = Some(format_ident!("{}", s.to_upper_camel_case())); } else if let Some(s) = meta.get_as_kv("from_expr") { from_expr = Some(syn::parse_str::(&s).map_err(Error::Syn)?); } else if let Some(s) = meta.get_as_kv("alias") { nested_alias = Some(s); } } } } let field_name = field.ident.unwrap(); let col_as = match (from_col, from_expr, nested) { (Some(col), None, false) => { if entity.is_none() { return Err(Error::EntityNotSpecified); } ColumnAs::Col { col: Some(col), field: field_name, } } (None, Some(expr), false) => ColumnAs::Expr { expr, field: field_name, }, (None, None, true) => ColumnAs::Nested { typ: field.ty, field: field_name, alias: nested_alias, }, (None, None, false) => { if entity.is_none() { return Err(Error::EntityNotSpecified); } if skip { ColumnAs::Skip(field_name) } else { ColumnAs::Col { col: None, field: field_name, } } } (_, _, _) => return Err(Error::OverlappingAttributes(field_span)), }; column_as_list.push(col_as); } Ok(Self { entity, active_model, model_alias, ident: input.ident, fields: column_as_list, from_query_result, into_active_model, }) } fn expand(&self) -> syn::Result { let impl_partial_model = self.impl_partial_model(); let impl_from_query_result = if self.from_query_result { DeriveFromQueryResult { ident: self.ident.clone(), generics: Default::default(), fields: self .fields .iter() .map(|col_as| FromQueryResultItem { typ: match col_as { ColumnAs::Nested { .. } => FqrItemType::Nested, ColumnAs::Skip(_) => FqrItemType::Skip, _ => FqrItemType::Flat, }, ident: match col_as { ColumnAs::Col { field, .. } => field, ColumnAs::Expr { field, .. } => field, ColumnAs::Nested { field, .. } => field, ColumnAs::Skip(field) => field, } .to_owned(), alias: None, }) .collect(), } .impl_from_query_result(true) } else { quote!() }; let impl_into_active_model = if self.into_active_model { DeriveIntoActiveModel { ident: self.ident.clone(), active_model: self.active_model.clone(), fields: self .fields .iter() .filter_map(|col_as| { match col_as { ColumnAs::Col { field, .. } => Some(field), ColumnAs::Expr { field, .. } => Some(field), ColumnAs::Nested { .. } => None, ColumnAs::Skip(_) => None, } .map(|f| IntoActiveModelField::Normal(f.clone())) }) .collect(), set_fields: Vec::new(), exhaustive: false, } .impl_into_active_model() } else { quote!() }; Ok(quote! { #impl_partial_model #impl_from_query_result #impl_into_active_model }) } fn impl_partial_model(&self) -> TokenStream { let select_ident = format_ident!("select"); let DerivePartialModel { entity, model_alias, ident, fields, .. } = self; let select_col_code_gen = fields.iter().map(|col_as| match col_as { ColumnAs::Col { col, field } => { let field = field.unraw().to_string(); let entity = entity.as_ref().unwrap(); let variant_name = if let Some(col) = col { col } else { &format_ident!("{}", field.to_upper_camel_case()) }; // variant of the entity column let column = quote! { <#entity as sea_orm::EntityTrait>::Column::#variant_name }; // We cast enum as text in select_as if the backend is postgres let non_nested = match model_alias { Some(model_alias) => quote! { let col_expr = sea_orm::sea_query::Expr::col((#model_alias, #column)); let casted = sea_orm::ColumnTrait::select_as(&#column, col_expr); sea_orm::QuerySelect::column_as(#select_ident, casted, col_alias) }, None => quote! { sea_orm::QuerySelect::column_as(#select_ident, #column, col_alias) }, }; quote! { let #select_ident = { let col_alias = pre.map_or(#field.to_string(), |pre| format!("{pre}{}", #field)); if let Some(nested_alias) = nested_alias { let alias = sea_orm::sea_query::SeaRc::new(nested_alias); let col_expr = sea_orm::sea_query::Expr::col( (alias, #column) ); let casted = sea_orm::ColumnTrait::select_as(&#column, col_expr); sea_orm::QuerySelect::column_as(#select_ident, casted, col_alias) } else { #non_nested } }; } } ColumnAs::Expr { expr, field } => { let field = field.unraw().to_string(); quote!(let #select_ident = if let Some(prefix) = pre { let ident = format!("{prefix}{}", #field); sea_orm::QuerySelect::column_as(#select_ident, #expr, ident) } else { sea_orm::QuerySelect::column_as(#select_ident, #expr, #field) }; ) } ColumnAs::Nested { typ, field, alias } => { let field = field.unraw().to_string(); let alias_ref: Option<&str> = alias.as_deref(); let alias_arg = match alias_ref { Some(s) => quote! { Some(#s) }, None => quote! { None }, }; quote!(let #select_ident = <#typ as sea_orm::PartialModelTrait>::select_cols_nested(#select_ident, Some(&if let Some(prefix) = pre { format!("{prefix}{}_", #field) } else { format!("{}_", #field) } ), #alias_arg ); ) } ColumnAs::Skip(_) => quote!(), }); quote! { #[automatically_derived] impl sea_orm::PartialModelTrait for #ident { fn select_cols_nested(#select_ident: S, pre: Option<&str>, nested_alias: Option<&'static str>) -> S { #(#select_col_code_gen)* #select_ident } } } } } pub fn expand_derive_partial_model(input: syn::DeriveInput) -> syn::Result { let ident_span = input.ident.span(); match DerivePartialModel::new(input) { Ok(partial_model) => partial_model.expand(), Err(Error::NotSupportGeneric(span)) => Ok(quote_spanned! { span => compile_error!("you can only derive `DerivePartialModel` on concrete struct"); }), Err(Error::OverlappingAttributes(span)) => Ok(quote_spanned! { span => compile_error!("you can only use one of `from_col`, `from_expr`, `nested`"); }), Err(Error::EntityNotSpecified) => Ok(quote_spanned! { ident_span => compile_error!("you need specific which entity you are using") }), Err(Error::InputNotStruct) => Ok(quote_spanned! { ident_span => compile_error!("you can only derive `DerivePartialModel` on named struct"); }), Err(Error::Syn(err)) => Err(err), } } #[cfg(test)] mod test { use quote::format_ident; use syn::{DeriveInput, Type, parse_str}; use crate::derives::partial_model::ColumnAs; use super::DerivePartialModel; type StdResult = Result>; const CODE_SNIPPET_1: &str = r#" #[sea_orm(entity = "Entity")] struct PartialModel { default_field: i32, #[sea_orm(from_col = "bar")] alias_field: i32, #[sea_orm(from_expr = "Expr::val(1).add(1)")] expr_field : i32 } "#; #[test] fn test_load_macro_input_1() -> StdResult<()> { let input = parse_str::(CODE_SNIPPET_1)?; let middle = DerivePartialModel::new(input).unwrap(); assert_eq!(middle.entity, Some(parse_str::("Entity").unwrap())); assert_eq!(middle.ident, format_ident!("PartialModel")); assert_eq!(middle.fields.len(), 3); assert_eq!( middle.fields[0], ColumnAs::Col { col: None, field: format_ident!("default_field") } ); assert_eq!( middle.fields[1], ColumnAs::Col { col: Some(format_ident!("Bar")), field: format_ident!("alias_field"), }, ); assert_eq!( middle.fields[2], ColumnAs::Expr { expr: syn::parse_str("Expr::val(1).add(1)").unwrap(), field: format_ident!("expr_field"), } ); assert_eq!(middle.from_query_result, true); Ok(()) } const CODE_SNIPPET_2: &str = r#" #[sea_orm(entity = "MyEntity", from_query_result = "false")] struct PartialModel { default_field: i32, } "#; #[test] fn test_load_macro_input_2() -> StdResult<()> { let input = parse_str::(CODE_SNIPPET_2)?; let middle = DerivePartialModel::new(input).unwrap(); assert_eq!(middle.entity, Some(parse_str::("MyEntity").unwrap())); assert_eq!(middle.ident, format_ident!("PartialModel")); assert_eq!(middle.fields.len(), 1); assert_eq!( middle.fields[0], ColumnAs::Col { col: None, field: format_ident!("default_field") } ); assert_eq!(middle.from_query_result, false); Ok(()) } } ================================================ FILE: sea-orm-macros/src/derives/primary_key.rs ================================================ use super::impl_iden; use proc_macro2::{Ident, TokenStream}; use quote::{quote, quote_spanned}; use syn::{Data, DataEnum, Fields, Variant}; fn impl_primary_key_to_column(ident: &Ident, data: &Data) -> syn::Result { let variants = match data { syn::Data::Enum(DataEnum { variants, .. }) => variants, _ => { return Ok(quote_spanned! { ident.span() => compile_error!("you can only derive DerivePrimaryKey on enums"); }); } }; if variants.is_empty() { return Ok(quote_spanned! { ident.span() => compile_error!("Entity must have a primary key column. See for details."); }); } let variant: Vec = variants .iter() .map(|Variant { ident, fields, .. }| match fields { Fields::Named(_) => quote! { #ident{..} }, Fields::Unnamed(_) => quote! { #ident(..) }, Fields::Unit => quote! { #ident }, }) .collect(); Ok(quote!( #[automatically_derived] impl sea_orm::PrimaryKeyToColumn for #ident { type Column = Column; fn into_column(self) -> Self::Column { match self { #(Self::#variant => Self::Column::#variant,)* } } fn from_column(col: Self::Column) -> Option { match col { #(Self::Column::#variant => Some(Self::#variant),)* _ => None, } } } )) } /// Method to derive a Primary Key for a Model using the [PrimaryKeyTrait](sea_orm::PrimaryKeyTrait) pub fn expand_derive_primary_key(ident: &Ident, data: &Data) -> syn::Result { let impl_primary_key_to_column = impl_primary_key_to_column(ident, data)?; let impl_iden = impl_iden(ident, data)?; Ok(quote!( #impl_primary_key_to_column #impl_iden )) } ================================================ FILE: sea-orm-macros/src/derives/related_entity.rs ================================================ #[cfg(feature = "seaography")] mod private { use heck::ToLowerCamelCase; use proc_macro_crate::{FoundCrate, crate_name}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, quote_spanned}; use crate::derives::attributes::related_attr; enum Error { InputNotEnum, InvalidEntityPath, Syn(syn::Error), } struct DeriveRelatedEntity { entity_ident: TokenStream, ident: syn::Ident, variants: syn::punctuated::Punctuated, } impl DeriveRelatedEntity { fn new(input: syn::DeriveInput) -> Result { let sea_attr = related_attr::SeaOrm::try_from_attributes(&input.attrs) .map_err(Error::Syn)? .unwrap_or_default(); let ident = input.ident; let entity_ident = match sea_attr.entity.as_ref().map(Self::parse_lit_string) { Some(entity_ident) => entity_ident.map_err(|_| Error::InvalidEntityPath)?, None => quote! { Entity }, }; let variants = match input.data { syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, _ => return Err(Error::InputNotEnum), }; Ok(DeriveRelatedEntity { entity_ident, ident, variants, }) } fn expand(&self) -> syn::Result { let ident = &self.ident; let entity_ident = &self.entity_ident; let mut get_relation_impl = Vec::new(); let mut get_relation_name_impl = Vec::new(); let mut get_related_entity_filter_impl = Vec::new(); for variant in &self.variants { let attr = related_attr::SeaOrm::from_attributes(&variant.attrs)?; let enum_name = &variant.ident; let target_entity = attr .entity .as_ref() .map(Self::parse_lit_string) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'entity'") })??; let def = match attr.def { Some(def) => Some(Self::parse_lit_string(&def).map_err(|_| { syn::Error::new_spanned(variant, "Missing value for 'def'") })?), None => None, }; let name = enum_name.to_string().to_lower_camel_case(); get_relation_impl.push(if let Some(def) = &def { quote! { Self::#enum_name => builder.get_relation::<#entity_ident, #target_entity>(#name, #def) } } else { quote! { Self::#enum_name => via_builder.get_relation::<#entity_ident, #target_entity>(#name) } }); get_relation_name_impl.push(if let Some(def) = &def { quote! { Self::#enum_name => builder.get_relation_name::<#entity_ident, #target_entity>(#name, #def) } } else { quote! { Self::#enum_name => via_builder.get_relation_name::<#entity_ident, #target_entity>(#name) } }); get_related_entity_filter_impl.push(if let Some(def) = &def { quote! { Self::#enum_name => builder.get_relation::<#entity_ident, #target_entity>(#name, #def) } } else { quote! { Self::#enum_name => builder.get_relation_via::<#entity_ident, #target_entity>(#name) } }); } // Get the path of the `async-graphql` on the application's Cargo.toml let async_graphql_crate = match crate_name("async-graphql") { // if found, use application's `async-graphql` Ok(FoundCrate::Name(name)) => { let ident = Ident::new(&name, Span::call_site()); quote! { #ident } } Ok(FoundCrate::Itself) => quote! { async_graphql }, // if not, then use the `async-graphql` re-exported by `seaography` Err(_) => quote! { seaography::async_graphql }, }; Ok(quote! { impl seaography::RelationBuilder for #ident { fn get_relation(&self, context: & 'static seaography::BuilderContext) -> #async_graphql_crate::dynamic::Field { let builder = seaography::EntityObjectRelationBuilder { context }; let via_builder = seaography::EntityObjectViaRelationBuilder { context }; match self { #(#get_relation_impl,)* _ => panic!("No relations for this entity"), } } fn get_relation_name(&self, context: & 'static seaography::BuilderContext) -> String { let builder = seaography::EntityObjectRelationBuilder { context }; let via_builder = seaography::EntityObjectViaRelationBuilder { context }; match self { #(#get_relation_name_impl,)* _ => panic!("No relations for this entity"), } } fn get_related_entity_filter(&self, context: & 'static seaography::BuilderContext) -> seaography::RelatedEntityFilterField { let builder = seaography::RelatedEntityFilterBuilder { context }; match self { #(#get_related_entity_filter_impl,)* _ => panic!("No relations for this entity"), } } } }) } fn parse_lit_string(lit: &syn::Lit) -> syn::Result { match lit { syn::Lit::Str(lit_str) => lit_str .value() .parse() .map_err(|_| syn::Error::new_spanned(lit, "attribute not valid")), _ => Err(syn::Error::new_spanned(lit, "attribute must be a string")), } } } /// Method to derive a Related enumeration pub fn expand_derive_related_entity(input: syn::DeriveInput) -> syn::Result { let ident_span = input.ident.span(); match DeriveRelatedEntity::new(input) { Ok(model) => model.expand(), Err(Error::InputNotEnum) => Ok(quote_spanned! { ident_span => compile_error!("you can only derive DeriveRelation on enums"); }), Err(Error::InvalidEntityPath) => Ok(quote_spanned! { ident_span => compile_error!("invalid attribute value for 'entity'"); }), Err(Error::Syn(err)) => Err(err), } } } #[cfg(not(feature = "seaography"))] mod private { use proc_macro2::TokenStream; pub fn expand_derive_related_entity(_: syn::DeriveInput) -> syn::Result { Ok(TokenStream::new()) } } pub use private::*; ================================================ FILE: sea-orm-macros/src/derives/relation.rs ================================================ use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use super::attributes::{derive_attr, relation_attr}; enum Error { InputNotEnum, Syn(syn::Error), } struct DeriveRelation { entity_ident: syn::Ident, ident: syn::Ident, variants: syn::punctuated::Punctuated, } impl DeriveRelation { fn new(input: syn::DeriveInput) -> Result { let variants = match input.data { syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, _ => return Err(Error::InputNotEnum), }; let sea_attr = derive_attr::SeaOrm::try_from_attributes(&input.attrs) .map_err(Error::Syn)? .unwrap_or_default(); let ident = input.ident; let entity_ident = sea_attr.entity.unwrap_or_else(|| format_ident!("Entity")); Ok(DeriveRelation { entity_ident, ident, variants, }) } fn expand(&self) -> syn::Result { let expanded_impl_relation_trait = self.impl_relation_trait()?; Ok(expanded_impl_relation_trait) } fn impl_relation_trait(&self) -> syn::Result { let ident = &self.ident; let entity_ident = &self.entity_ident; let no_relation_def_msg = format!("No RelationDef for {ident}"); fn lit_str(lit: &syn::Lit) -> syn::Result { match lit { syn::Lit::Str(lit_str) => Ok(lit_str.value()), _ => Err(syn::Error::new_spanned(lit, "attribute must be a string")), } } fn parse_lit_str(lit: &syn::Lit) -> syn::Result { lit_str(lit)? .parse() .map_err(|_| syn::Error::new_spanned(lit, "attribute not valid")) } let variant_relation_defs: Vec = self .variants .iter() .map(|variant| { let variant_ident = &variant.ident; let attr = relation_attr::SeaOrm::from_attributes(&variant.attrs)?; let mut relation_type = quote! { error }; let related_to = if attr.belongs_to.is_some() { relation_type = quote! { belongs_to }; attr.belongs_to .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'belongs_to'") }) } else if attr.has_one.is_some() { relation_type = quote! { has_one }; attr.has_one .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'has_one'") }) } else if attr.has_many.is_some() { relation_type = quote! { has_many }; attr.has_many .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'has_many'") }) } else { Err(syn::Error::new_spanned( variant, "Missing one of 'has_one', 'has_many' or 'belongs_to'", )) }??; let mut result = if let (Some(has_many), Some(via)) = (&attr.has_many, &attr.via_rel) { let has_many = lit_str(has_many)?; let via: TokenStream = if has_many == "Entity" { lit_str(via)? } else { format!("{}::{}", has_many.trim_end_matches("::Entity"), lit_str(via)?) }.parse().unwrap(); quote!( Self::#variant_ident => #entity_ident::has_many_via(#related_to, #via) ) } else { quote!( Self::#variant_ident => #entity_ident::#relation_type(#related_to) ) }; if attr.from.is_some() { let from = attr.from .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'from'") })??; result = quote! { #result.from(#from) }; } else if attr.belongs_to.is_some() { return Err(syn::Error::new_spanned(variant, "Missing attribute 'from'")); } if attr.to.is_some() { let to = attr .to .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'to'") })??; result = quote! { #result.to(#to) }; } else if attr.belongs_to.is_some() { return Err(syn::Error::new_spanned(variant, "Missing attribute 'to'")); } if attr.on_update.is_some() { let on_update = attr .on_update .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'on_update'") })??; result = quote! { #result.on_update(sea_orm::prelude::ForeignKeyAction::#on_update) }; } if attr.on_delete.is_some() { let on_delete = attr .on_delete .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'on_delete'") })??; result = quote! { #result.on_delete(sea_orm::prelude::ForeignKeyAction::#on_delete) }; } if attr.on_condition.is_some() { let on_condition = attr .on_condition .as_ref() .map(parse_lit_str) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'on_condition'") })??; result = quote! { #result.on_condition(|_, _| sea_orm::sea_query::IntoCondition::into_condition(#on_condition)) }; } if attr.fk_name.is_some() { let fk_name = attr .fk_name .as_ref() .map(|lit| { match lit { syn::Lit::Str(lit_str) => Ok(lit_str.value()), _ => Err(syn::Error::new_spanned(lit, "attribute must be a string")), } }) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'fk_name'") })??; result = quote! { #result.fk_name(#fk_name) }; } if attr.skip_fk.is_some() { result = quote! { #result.skip_fk() }; } if attr.condition_type.is_some() { let condition_type = attr .condition_type .as_ref() .map(|lit| { match lit { syn::Lit::Str(lit_str) => { match lit_str.value().to_ascii_lowercase().as_str() { "all" => Ok(quote!( sea_orm::sea_query::ConditionType::All )), "any" => Ok(quote!( sea_orm::sea_query::ConditionType::Any )), _ => Err(syn::Error::new_spanned(lit, "Condition type must be one of `all` or `any`")), } }, _ => Err(syn::Error::new_spanned(lit, "attribute must be a string")), } }) .ok_or_else(|| { syn::Error::new_spanned(variant, "Missing value for 'condition_type'") })??; result = quote! { #result.condition_type(#condition_type) }; } result = quote! { #result.into() }; Result::<_, syn::Error>::Ok(result) }) .collect::, _>>()?; Ok(quote!( #[automatically_derived] impl sea_orm::entity::RelationTrait for #ident { fn def(&self) -> sea_orm::entity::RelationDef { match self { #( #variant_relation_defs, )* _ => panic!(#no_relation_def_msg) } } } )) } } /// Method to derive a Relation pub fn expand_derive_relation(input: syn::DeriveInput) -> syn::Result { let ident_span = input.ident.span(); match DeriveRelation::new(input) { Ok(model) => model.expand(), Err(Error::InputNotEnum) => Ok(quote_spanned! { ident_span => compile_error!("you can only derive DeriveRelation on enums"); }), Err(Error::Syn(err)) => Err(err), } } ================================================ FILE: sea-orm-macros/src/derives/try_getable_from_json.rs ================================================ use proc_macro2::{Ident, TokenStream}; use quote::quote; pub fn expand_derive_from_json_query_result(ident: Ident) -> syn::Result { let impl_not_u8 = if cfg!(feature = "postgres-array") { quote!( #[automatically_derived] impl sea_orm::sea_query::postgres_array::NotU8 for #ident {} ) } else { quote!() }; Ok(quote!( #[automatically_derived] impl sea_orm::TryGetableFromJson for #ident {} #[automatically_derived] impl std::convert::From<#ident> for sea_orm::Value { fn from(source: #ident) -> Self { sea_orm::Value::Json( Some(std::boxed::Box::new( serde_json::to_value(&source) .expect(concat!("Failed to serialize '", stringify!(#ident), "'")) )) ) } } #[automatically_derived] impl sea_orm::sea_query::ValueType for #ident { fn try_from(v: sea_orm::Value) -> Result { match v { sea_orm::Value::Json(Some(json)) => Ok( serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?, ), _ => Err(sea_orm::sea_query::ValueTypeErr), } } fn type_name() -> String { stringify!(#ident).to_owned() } fn array_type() -> sea_orm::sea_query::ArrayType { sea_orm::sea_query::ArrayType::Json } fn column_type() -> sea_orm::sea_query::ColumnType { sea_orm::sea_query::ColumnType::Json } } #[automatically_derived] impl sea_orm::sea_query::Nullable for #ident { fn null() -> sea_orm::Value { sea_orm::Value::Json(None) } } #[automatically_derived] impl sea_orm::IntoActiveValue<#ident> for #ident { fn into_active_value(self) -> sea_orm::ActiveValue<#ident> { sea_orm::ActiveValue::set(self) } } #impl_not_u8 )) } ================================================ FILE: sea-orm-macros/src/derives/typed_column.rs ================================================ use super::util::{ escape_rust_keyword, format_field_ident, is_compound_field, trim_starting_raw_identifier, }; use heck::ToUpperCamelCase; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{Data, Expr, Fields, Lit, spanned::Spanned}; /// First is `struct TypedColumn`, second is the `const COLUMN` pub fn expand_typed_column(data: &Data) -> syn::Result<(TokenStream, TokenStream)> { let mut column_fields = Vec::new(); let mut column_types = Vec::new(); let mut column_values = Vec::new(); if let Data::Struct(item_struct) = &data { if let Fields::Named(fields) = &item_struct.fields { for field in &fields.named { if let Some(ident) = &field.ident { let field_name = trim_starting_raw_identifier(ident); let mut field_name = Ident::new(&field_name.to_upper_camel_case(), ident.span()); let field_type = &field.ty; let field_type = quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces let mut ignore = false; let mut column_type = None; for attr in field.attrs.iter() { if attr.path().is_ident("sea_orm") { attr.parse_nested_meta(|meta| { if meta.path.is_ident("column_type") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { column_type = Some(litstr.value()); } else { return Err( meta.error(format!("Invalid column_type {lit:?}")) ); } } else if meta.path.is_ident("enum_name") { let lit = meta.value()?.parse()?; if let Lit::Str(litstr) = lit { let ty: Ident = syn::parse_str(&litstr.value())?; field_name = ty; } else { return Err( meta.error(format!("Invalid enum_name {lit:?}")) ); } } else if meta.path.is_ident("ignore") { ignore = true; } else { // Reads the value expression to advance the parse stream. let _: Option = meta.value().and_then(|v| v.parse()).ok(); } Ok(()) })?; } } if ignore { continue; } if is_compound_field(&field_type) { continue; } field_name = Ident::new(&escape_rust_keyword(field_name), ident.span()); column_fields.push(format_field_ident(field)); let wrapper = super::value_type_match::column_type_wrapper( &column_type, &field_type, field.span(), ); column_types.push(if let Some(wrapper) = &wrapper { quote!(sea_orm::#wrapper) } else { quote!(Column) }); column_values.push(if let Some(wrapper) = &wrapper { quote!(sea_orm::#wrapper(Column::#field_name)) } else { quote!(Column::#field_name) }); } } } } Ok(( quote! { #[doc = " Generated by sea-orm-macros"] pub struct TypedColumn { #( #[doc = " Generated by sea-orm-macros"] pub #column_fields: #column_types ),* } }, quote! { #[doc = " Generated by sea-orm-macros"] pub const COLUMN: TypedColumn = TypedColumn { #(#column_fields: #column_values),* }; }, )) } ================================================ FILE: sea-orm-macros/src/derives/util.rs ================================================ use heck::ToUpperCamelCase; use syn::{Field, Ident, Meta, MetaNameValue, punctuated::Punctuated, token::Comma}; /// Remove ignored fields and compound fields pub(crate) fn field_not_ignored(field: &Field) -> bool { let field_type = &field.ty; let field_type = quote::quote! { #field_type } .to_string() // e.g.: "Option < String >" .replace(' ', ""); // Remove spaces if is_compound_field(&field_type) { return false; } field_not_ignored_compound(field) } /// Remove ignored fields, compound fields okay pub(crate) fn field_not_ignored_compound(field: &Field) -> bool { for attr in field.attrs.iter() { if let Some(ident) = attr.path().get_ident() { if ident != "sea_orm" { continue; } } else { continue; } if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { for meta in list.iter() { if let Meta::Path(path) = meta { if let Some(name) = path.get_ident() { if name == "ignore" { return false; } } } } } } true } pub(crate) fn is_compound_field(field_type: &str) -> bool { // for #[sea_orm::model] ((field_type.starts_with("Option<") || field_type.starts_with("Vec<")) && field_type.ends_with("::Entity>")) // for DeriveModelEx || field_type.starts_with("HasOne<") || field_type.starts_with("HasMany<") } pub(crate) fn extract_compound_entity(ty: &str) -> &str { if ty.starts_with("HasMany<") { &ty["HasMany<".len()..(ty.len() - 1)] } else if ty.starts_with("HasOne<") { &ty["HasOne<".len()..(ty.len() - 1)] } else if ty.starts_with("Option<") { &ty["Option<".len()..(ty.len() - 1)] } else if ty.starts_with("Vec<") { &ty["Vec<".len()..(ty.len() - 1)] } else { panic!("Relation applied to non compound type: {ty}") } } pub(crate) fn format_field_ident(field: &Field) -> Ident { field.ident.clone().unwrap() } pub(crate) fn trim_starting_raw_identifier(string: T) -> String where T: ToString, { string .to_string() .trim_start_matches(RAW_IDENTIFIER) .to_string() } pub(crate) fn escape_rust_keyword(string: T) -> String where T: ToString, { let string = string.to_string(); if RUST_KEYWORDS.iter().any(|s| s.eq(&string)) { format!("r#{string}") } else if RUST_SPECIAL_KEYWORDS.iter().any(|s| s.eq(&string)) { format!("{string}_") } else { string } } /// Turn a string to PascalCase while escaping all special characters in ASCII words. /// /// (camel_case is used here to match naming of heck.) /// /// In ActiveEnum, string_value will be PascalCased and made /// an identifier in {Enum}Variant. /// /// However Rust only allows for XID_Start char followed by /// XID_Continue characters as identifiers; this causes a few /// problems: /// /// - `string_value = ""` will cause a panic; /// - `string_value` containing only non-alphanumerics will become `""` /// and cause the above panic; /// - `string_values`: /// - `"A B"` /// - `"A B"` /// - `"A_B"` /// - `"A_ B"` /// /// All shares the same identifier of `"AB"`; /// /// This function does the PascelCase conversion with a few special escapes: /// - Non-Unicode Standard Annex #31 compliant characters will converted to their hex notation; /// - `"_"` into `"0x5F"`; /// - `" "` into `"0x20"`; /// - Empty strings will become special keyword of `"__Empty"` /// /// Note that this does NOT address: /// /// - case-sensitivity. String value "ABC" and "abc" remains /// conflicted after .camel_case(). /// /// Example Conversions: /// /// ```ignore /// assert_eq!(camel_case_with_escaped_non_uax31(""), "__Empty"); /// assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x20"); /// assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x200x20"); /// assert_eq!(camel_case_with_escaped_non_uax31("_"), "_0x5F"); /// assert_eq!(camel_case_with_escaped_non_uax31("foobar"), "Foobar"); /// assert_eq!(camel_case_with_escaped_non_uax31("foo bar"), "Foo0x20bar"); /// ``` pub(crate) fn camel_case_with_escaped_non_uax31(string: T) -> String where T: ToString, { let additional_chars_to_replace: [char; 2] = ['_', ' ']; let mut rebuilt = string .to_string() .chars() .enumerate() .map(|(pos, char_)| { if !additional_chars_to_replace.contains(&char_) && match pos { 0 => unicode_ident::is_xid_start(char_), _ => unicode_ident::is_xid_continue(char_), } { char_.to_string() } else { format!("{:#X}", char_ as u32) } }) .reduce( // Join the "characters" (now strings) // back together |lhs, rhs| lhs + rhs.as_str(), ) .map_or( // if string_value is "" // Make sure the default does NOT go through camel_case, // as the __ will be removed! The underscores are // what guarantees this being special case avoiding // all potential conflicts. String::from("__Empty"), |s| s.to_upper_camel_case(), ); if rebuilt .chars() .next() .map(char::is_numeric) .unwrap_or(false) { rebuilt = String::from("_") + &rebuilt; } rebuilt } pub(crate) const RAW_IDENTIFIER: &str = "r#"; pub(crate) const RUST_KEYWORDS: [&str; 49] = [ "as", "async", "await", "break", "const", "continue", "dyn", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "static", "struct", "super", "trait", "true", "type", "union", "unsafe", "use", "where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "try", "typeof", "unsized", "virtual", "yield", ]; pub(crate) const RUST_SPECIAL_KEYWORDS: [&str; 3] = ["crate", "Self", "self"]; pub(crate) trait GetMeta { fn exists(&self, k: &str) -> bool; fn get_as_kv(&self, k: &str) -> Option; fn get_as_kv_with_ident(&self) -> Option<(Ident, String)>; } impl GetMeta for Meta { fn exists(&self, key: &str) -> bool { let Meta::Path(path) = self else { return false; }; path.is_ident(key) } fn get_as_kv(&self, key: &str) -> Option { let Meta::NameValue(MetaNameValue { path, value: syn::Expr::Lit(exprlit), .. }) = self else { return None; }; let syn::Lit::Str(litstr) = &exprlit.lit else { return None; }; if path.is_ident(key) { Some(litstr.value()) } else { None } } fn get_as_kv_with_ident(&self) -> Option<(Ident, String)> { let Meta::NameValue(MetaNameValue { path, value: syn::Expr::Lit(exprlit), .. }) = self else { return None; }; let syn::Lit::Str(litstr) = &exprlit.lit else { return None; }; path.get_ident() .map(|ident| (ident.clone(), litstr.value())) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_non_uax31_escape() { // Test empty string assert_eq!(camel_case_with_escaped_non_uax31(""), "__Empty"); // Test additional_chars_to_replace (to_camel_case related characters) assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x20"); // Test additional_chars_to_replace (multiples. ensure distinct from single) assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x200x20"); // Test additional_chars_to_replace (udnerscores) assert_eq!(camel_case_with_escaped_non_uax31("_"), "_0x5F"); // Test typical use case assert_eq!(camel_case_with_escaped_non_uax31("foobar"), "Foobar"); // Test spaced words distinct from non-spaced assert_eq!(camel_case_with_escaped_non_uax31("foo bar"), "Foo0x20bar"); // Test underscored words distinct from non-spaced and spaced assert_eq!(camel_case_with_escaped_non_uax31("foo_bar"), "Foo0x5Fbar"); // Test leading numeric characters assert_eq!(camel_case_with_escaped_non_uax31("1"), "_0x31"); // Test escaping also works on full string following lead numeric character // This was previously a fail condition. assert_eq!( camel_case_with_escaped_non_uax31("1 2 3"), "_0x310x2020x203" ); assert_eq!(camel_case_with_escaped_non_uax31("씨오알엠"), "씨오알엠"); assert_eq!(camel_case_with_escaped_non_uax31("A_B"), "A0x5Fb"); assert_eq!(camel_case_with_escaped_non_uax31("AB"), "Ab"); } } ================================================ FILE: sea-orm-macros/src/derives/value_type.rs ================================================ use super::attributes::value_type_attr; use super::value_type_match::{array_type_expr, can_try_from_u64, column_type_expr}; use proc_macro2::TokenStream; use quote::quote; use syn::{Field, Ident, Type, punctuated::Punctuated, spanned::Spanned, token::Comma}; #[allow(clippy::large_enum_variant)] enum DeriveValueType { TupleStruct(DeriveValueTypeStruct), StringLike(DeriveValueTypeString), } struct DeriveValueTypeStruct { name: syn::Ident, ty: Type, column_type: TokenStream, array_type: TokenStream, can_try_from_u64: bool, } #[derive(Default)] struct DeriveValueTypeStructAttrs { column_type: Option, array_type: Option, try_from_u64: bool, } impl TryFrom for DeriveValueTypeStructAttrs { type Error = syn::Error; fn try_from(attrs: value_type_attr::SeaOrm) -> syn::Result { Ok(Self { column_type: attrs.column_type.map(|s| s.parse()).transpose()?, array_type: attrs.array_type.map(|s| s.parse()).transpose()?, try_from_u64: attrs.try_from_u64.is_some(), }) } } struct DeriveValueTypeString { name: syn::Ident, from_str: Option, to_str: Option, column_type: Option, } struct DeriveValueTypeStringAttrs { from_str: Option, to_str: Option, column_type: Option, } impl TryFrom for DeriveValueTypeStringAttrs { type Error = syn::Error; fn try_from(attrs: value_type_attr::SeaOrm) -> syn::Result { let value_type = attrs.value_type.map(|s| s.value()); assert_eq!(value_type.as_deref(), Some("String")); Ok(Self { from_str: attrs.from_str.map(|s| s.parse()).transpose()?, to_str: attrs.to_str.map(|s| s.parse()).transpose()?, column_type: attrs.column_type.map(|s| s.parse()).transpose()?, }) } } impl DeriveValueType { fn new(input: syn::DeriveInput) -> syn::Result { // Produce an error if the macro attributes are malformed let value_type_attr = value_type_attr::SeaOrm::try_from_attributes(&input.attrs)?; // If some attributes were set, inspect the optional `value_type` let value_type = if let Some(ref value_type_attr) = value_type_attr { value_type_attr.value_type.as_ref().map(|s| s.value()) } else { None }; // If either `value_type` is unset, or no attributes were passed, assume // `DeriveValueTypeStruct`. If no attrs were set, use default values. if value_type.is_none() || value_type_attr.is_none() { let value_type_attr = if let Some(value_type_attr) = value_type_attr { value_type_attr.try_into()? } else { DeriveValueTypeStructAttrs::default() }; match input.data { syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }), .. }) => { return DeriveValueTypeStruct::new(input.ident, value_type_attr, unnamed) .map(Self::TupleStruct); } _ => { return Err(syn::Error::new_spanned( input, "You can only derive `DeriveValueType` on a struct with a single unnamed field, unless `value_type` is set.", )); } } } let value_type_attr = value_type_attr.unwrap(); let value_type = value_type.unwrap(); match value_type.as_str() { "String" => DeriveValueTypeString::new(input.ident, value_type_attr.try_into()?) .map(Self::StringLike), _ => Err(syn::Error::new_spanned( input.ident, r#"Please specify value_type = "String""#, )), } } fn expand(&self) -> syn::Result { Ok(match self { Self::TupleStruct(s) => s.impl_value_type(), Self::StringLike(s) => s.impl_value_type(), }) } } impl DeriveValueTypeStruct { fn new( name: Ident, attrs: DeriveValueTypeStructAttrs, fields: Punctuated, ) -> syn::Result { let Some(field) = fields.into_iter().next() else { return Err(syn::Error::new_spanned( name, "You can only derive `DeriveValueType` on tuple struct with 1 inner value", )); }; let field_span = field.span(); let ty = field.ty; let field_type = quote! { #ty } .to_string() //E.g.: "Option < String >" .replace(' ', ""); // Remove spaces let field_type = if field_type.starts_with("Option<") { &field_type[7..(field_type.len() - 1)] // Extract `T` out of `Option` } else { field_type.as_str() }; let column_type = column_type_expr(attrs.column_type, field_type, field_span); let array_type = array_type_expr(attrs.array_type, field_type, field_span); let can_try_from_u64 = attrs.try_from_u64 || can_try_from_u64(field_type); Ok(Self { name, ty, column_type, array_type, can_try_from_u64, }) } fn impl_value_type(&self) -> TokenStream { let name = &self.name; let field_type = &self.ty; let column_type = &self.column_type; let array_type = &self.array_type; let try_from_u64_impl = if self.can_try_from_u64 { quote!( #[automatically_derived] impl sea_orm::TryFromU64 for #name { fn try_from_u64(n: u64) -> Result { use std::convert::TryInto; Ok(Self(n.try_into().map_err(|e| sea_orm::DbErr::TryIntoErr { from: stringify!(u64), into: stringify!(#name), source: std::sync::Arc::new(e), })?)) } } ) } else { quote!() }; let impl_not_u8 = if cfg!(feature = "postgres-array") { quote!( #[automatically_derived] impl sea_orm::sea_query::postgres_array::NotU8 for #name {} ) } else { quote!() }; quote!( #[automatically_derived] impl std::convert::From<#name> for sea_orm::Value { fn from(source: #name) -> Self { source.0.into() } } #[automatically_derived] impl sea_orm::TryGetable for #name { fn try_get_by(res: &sea_orm::QueryResult, idx: I) -> std::result::Result { <#field_type as sea_orm::TryGetable>::try_get_by(res, idx).map(|v| #name(v)) } } #[automatically_derived] impl sea_orm::sea_query::ValueType for #name { fn try_from(v: sea_orm::Value) -> std::result::Result { <#field_type as sea_orm::sea_query::ValueType>::try_from(v).map(|v| #name(v)) } fn type_name() -> std::string::String { stringify!(#name).to_owned() } fn array_type() -> sea_orm::sea_query::ArrayType { #array_type } fn column_type() -> sea_orm::sea_query::ColumnType { #column_type } } #[automatically_derived] impl sea_orm::sea_query::Nullable for #name { fn null() -> sea_orm::Value { <#field_type as sea_orm::sea_query::Nullable>::null() } } #[automatically_derived] impl sea_orm::IntoActiveValue<#name> for #name { fn into_active_value(self) -> sea_orm::ActiveValue<#name> { sea_orm::ActiveValue::Set(self) } } #try_from_u64_impl #impl_not_u8 ) } } impl DeriveValueTypeString { fn new(name: Ident, attrs: DeriveValueTypeStringAttrs) -> syn::Result { Ok(Self { name, from_str: attrs.from_str, to_str: attrs.to_str, column_type: attrs.column_type, }) } fn impl_value_type(&self) -> TokenStream { let name = &self.name; let from_str = match &self.from_str { Some(from_str) => from_str, None => "e!(std::str::FromStr::from_str), }; let to_str = match &self.to_str { Some(to_str) => to_str, None => "e!(std::string::ToString::to_string), }; let column_type = match &self.column_type { Some(column_type) => column_type, None => "e!(String(sea_orm::sea_query::StringLen::None)), }; let impl_not_u8 = if cfg!(feature = "postgres-array") { quote!( #[automatically_derived] impl sea_orm::sea_query::postgres_array::NotU8 for #name {} ) } else { quote!() }; quote!( #[automatically_derived] impl std::convert::From<#name> for sea_orm::Value { fn from(source: #name) -> Self { #to_str(&source).into() } } #[automatically_derived] impl sea_orm::TryGetable for #name { fn try_get_by(res: &sea_orm::QueryResult, idx: I) -> std::result::Result { let string = String::try_get_by(res, idx)?; #from_str(&string).map_err(|err| { sea_orm::TryGetError::DbErr(sea_orm::DbErr::TryIntoErr { from: "String", into: stringify!(#name), source: std::sync::Arc::new(err), }) }) } } #[automatically_derived] impl sea_orm::sea_query::ValueType for #name { fn try_from(v: sea_orm::Value) -> std::result::Result { let string = ::try_from(v)?; #from_str(&string).map_err(|_| sea_orm::sea_query::ValueTypeErr) } fn type_name() -> std::string::String { stringify!(#name).to_owned() } fn array_type() -> sea_orm::sea_query::ArrayType { sea_orm::sea_query::ArrayType::String } fn column_type() -> sea_orm::sea_query::ColumnType { sea_orm::sea_query::ColumnType::#column_type } } #[automatically_derived] impl sea_orm::sea_query::Nullable for #name { fn null() -> sea_orm::Value { sea_orm::Value::String(None) } } #[automatically_derived] impl sea_orm::IntoActiveValue<#name> for #name { fn into_active_value(self) -> sea_orm::ActiveValue<#name> { sea_orm::ActiveValue::Set(self) } } #impl_not_u8 ) } } pub fn expand_derive_value_type(input: syn::DeriveInput) -> syn::Result { DeriveValueType::new(input)?.expand() } ================================================ FILE: sea-orm-macros/src/derives/value_type_match.rs ================================================ use proc_macro2::{Span, TokenStream}; use quote::quote_spanned; use syn::{Ident, LitStr, Type}; pub fn column_type_expr( column_type: Option, field_type: &str, field_span: Span, ) -> TokenStream { match column_type { Some(column_type) => { quote_spanned! { field_span => sea_orm::prelude::ColumnType::#column_type } } None => { let ty: Type = LitStr::new(field_type, field_span) .parse() .expect("field type error"); quote_spanned! { field_span => <#ty as sea_orm::sea_query::ValueType>::column_type() } } } } pub fn column_type_wrapper( column_type: &Option, field_type: &str, field_span: Span, ) -> Option { let nullable = trim_option(field_type).0; if let Some(column_type) = column_type { let column_type = if let Some((prefix, _)) = column_type.split_once('(') { prefix } else { column_type }; let value_type = match column_type { "String" | "Text" => { if nullable { Some("StringColumnNullable") } else { Some("StringColumn") } } "Blob" | "Binary" | "VarBinary" => Some("BytesColumn"), "TinyInteger" | "SmallInteger" | "Integer" | "BigInteger" | "TinyUnsigned" | "SmallUnsigned" | "Unsigned" | "BigUnsigned" | "Float" | "Double" | "Decimal" | "Money" => { if nullable { Some("NumericColumnNullable") } else { Some("NumericColumn") } } "DateTime" | "Timestamp" | "TimestampWithTimeZone" => Some("DateTimeLikeColumn"), "Time" => Some("TimeLikeColumn"), "Date" => Some("DateLikeColumn"), "Boolean" => Some("BoolColumn"), "Json" | "JsonBinary" => Some("JsonColumn"), "Uuid" => Some("UuidColumn"), "Array" => Some("GenericArrayColumn"), _ => None, } .map(|ty| Ident::new(ty, field_span)); if value_type.is_some() { return value_type; } } match trim_option(field_type).1 { "bool" => Some("BoolColumn"), "String" => { if nullable { Some("StringColumnNullable") } else { Some("StringColumn") } } "Vec" => Some("BytesColumn"), "Uuid" => Some("UuidColumn"), "IpNetwork" => Some("IpNetworkColumn"), "Json" | "serde_json::Value" => Some("JsonColumn"), "TextUuid" => Some("TextUuidColumn"), field_type => { if is_numeric_column(field_type) || field_type.contains("UnixTimestamp") { if nullable { Some("NumericColumnNullable") } else { Some("NumericColumn") } } else if field_type.starts_with("Vec<") { let field_type = &field_type["Vec<".len()..field_type.len() - 1]; if is_numeric_column(field_type) { Some("NumericArrayColumn") } else { Some("GenericArrayColumn") } } else if field_type.contains("DateTime") || field_type.contains("Timestamp") { Some("DateTimeLikeColumn") } else if field_type.contains("Date") { Some("DateLikeColumn") } else if field_type.contains("Time") { Some("TimeLikeColumn") } else { None } } } .map(|ty| Ident::new(ty, field_span)) } fn is_numeric_column(ty: &str) -> bool { matches!( ty, "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "f32" | "f64" | "Decimal" | "BigDecimal" ) } pub fn array_type_expr( array_type: Option, field_type: &str, field_span: Span, ) -> TokenStream { match array_type { Some(array_type) => { quote_spanned! { field_span => sea_orm::sea_query::ArrayType::#array_type } } None => { let ty: Type = LitStr::new(field_type, field_span) .parse() .expect("field type error"); quote_spanned! { field_span => <#ty as sea_orm::sea_query::ValueType>::array_type() } } } } pub fn can_try_from_u64(field_type: &str) -> bool { matches!( field_type, "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" ) } /// Return whether it is nullable fn trim_option(s: &str) -> (bool, &str) { if s.starts_with("Option<") { (true, &s["Option<".len()..s.len() - 1]) } else { (false, s) } } ================================================ FILE: sea-orm-macros/src/lib.rs ================================================ extern crate proc_macro; use proc_macro::TokenStream; use syn::{DeriveInput, Error, parse_macro_input}; #[cfg(feature = "derive")] mod derives; #[cfg(feature = "strum")] mod strum; mod raw_sql; /// Create an Entity /// /// ### Usage /// /// ``` /// use sea_orm::entity::prelude::*; /// /// #[derive(Copy, Clone, Default, Debug, DeriveEntity)] /// pub struct Entity; /// /// # impl EntityName for Entity { /// # fn table_name(&self) -> &'static str { /// # "cake" /// # } /// # } /// # /// # #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] /// # pub struct Model { /// # pub id: i32, /// # pub name: String, /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// # pub enum Column { /// # Id, /// # Name, /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] /// # pub enum PrimaryKey { /// # Id, /// # } /// # /// # impl PrimaryKeyTrait for PrimaryKey { /// # type ValueType = i32; /// # /// # fn auto_increment() -> bool { /// # true /// # } /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter)] /// # pub enum Relation {} /// # /// # impl ColumnTrait for Column { /// # type EntityName = Entity; /// # /// # fn def(&self) -> ColumnDef { /// # match self { /// # Self::Id => ColumnType::Integer.def(), /// # Self::Name => ColumnType::String(StringLen::None).def(), /// # } /// # } /// # } /// # /// # impl RelationTrait for Relation { /// # fn def(&self) -> RelationDef { /// # panic!("No Relation"); /// # } /// # } /// # /// # impl ActiveModelBehavior for ActiveModel {} /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveEntity, attributes(sea_orm))] pub fn derive_entity(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); derives::expand_derive_entity(input) .unwrap_or_else(Error::into_compile_error) .into() } /// This derive macro is the 'almighty' macro which automatically generates /// Entity, Column, and PrimaryKey from a given Model. /// /// ### Usage /// /// ``` /// use sea_orm::entity::prelude::*; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] /// #[sea_orm(table_name = "posts")] /// pub struct Model { /// #[sea_orm(primary_key)] /// pub id: i32, /// pub title: String, /// #[sea_orm(column_type = "Text")] /// pub text: String, /// } /// /// # #[derive(Copy, Clone, Debug, EnumIter)] /// # pub enum Relation {} /// # /// # impl RelationTrait for Relation { /// # fn def(&self) -> RelationDef { /// # panic!("No Relation"); /// # } /// # } /// # /// # impl ActiveModelBehavior for ActiveModel {} /// ``` /// /// Entity should always have a primary key. /// Or, it will result in a compile error. /// See for details. /// /// ```compile_fail /// use sea_orm::entity::prelude::*; /// /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// #[sea_orm(table_name = "posts")] /// pub struct Model { /// pub title: String, /// #[sea_orm(column_type = "Text")] /// pub text: String, /// } /// /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] /// # pub enum Relation {} /// # /// # impl ActiveModelBehavior for ActiveModel {} /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveEntityModel, attributes(sea_orm, seaography))] pub fn derive_entity_model(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, attrs, .. } = parse_macro_input!(input as DeriveInput); if ident != "Model" { panic!("Struct name must be Model"); } let mut ts: TokenStream = derives::expand_derive_entity_model(&data, &attrs) .unwrap_or_else(Error::into_compile_error) .into(); ts.extend::( derives::expand_derive_model(&ident, &data, &attrs) .unwrap_or_else(Error::into_compile_error) .into(), ); ts.extend::( derives::expand_derive_active_model(&ident, &data) .unwrap_or_else(Error::into_compile_error) .into(), ); ts } /// Derive a complex model with relational fields #[cfg(feature = "derive")] #[proc_macro_derive(DeriveModelEx, attributes(sea_orm, seaography))] pub fn derive_model_ex(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, attrs, .. } = parse_macro_input!(input as DeriveInput); if ident != "ModelEx" { panic!("Struct name must be ModelEx"); } derives::expand_derive_model_ex(ident, data, attrs) .unwrap_or_else(Error::into_compile_error) .into() } /// Derive a complex active model with relational fields #[cfg(feature = "derive")] #[proc_macro_derive(DeriveActiveModelEx, attributes(sea_orm, seaography))] pub fn derive_active_model_ex(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, attrs, .. } = parse_macro_input!(input as DeriveInput); if ident != "ModelEx" { panic!("Struct name must be ModelEx"); } derives::expand_derive_active_model_ex(&ident, &data, &attrs) .unwrap_or_else(Error::into_compile_error) .into() } /// The DerivePrimaryKey derive macro will implement [PrimaryKeyToColumn] /// for PrimaryKey which defines tedious mappings between primary keys and columns. /// The [EnumIter] is also derived, allowing iteration over all enum variants. /// /// ### Usage /// /// ``` /// use sea_orm::entity::prelude::*; /// /// #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] /// pub enum PrimaryKey { /// CakeId, /// FillingId, /// } /// /// # #[derive(Copy, Clone, Default, Debug, DeriveEntity)] /// # pub struct Entity; /// # /// # impl EntityName for Entity { /// # fn table_name(&self) -> &'static str { /// # "cake" /// # } /// # } /// # /// # #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] /// # pub struct Model { /// # pub cake_id: i32, /// # pub filling_id: i32, /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// # pub enum Column { /// # CakeId, /// # FillingId, /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter)] /// # pub enum Relation {} /// # /// # impl ColumnTrait for Column { /// # type EntityName = Entity; /// # /// # fn def(&self) -> ColumnDef { /// # match self { /// # Self::CakeId => ColumnType::Integer.def(), /// # Self::FillingId => ColumnType::Integer.def(), /// # } /// # } /// # } /// # /// # impl PrimaryKeyTrait for PrimaryKey { /// # type ValueType = (i32, i32); /// # /// # fn auto_increment() -> bool { /// # false /// # } /// # } /// # /// # impl RelationTrait for Relation { /// # fn def(&self) -> RelationDef { /// # panic!("No Relation"); /// # } /// # } /// # /// # impl ActiveModelBehavior for ActiveModel {} /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DerivePrimaryKey, attributes(sea_orm))] pub fn derive_primary_key(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); match derives::expand_derive_primary_key(&ident, &data) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } /// The DeriveColumn derive macro will implement [ColumnTrait] for Columns. /// It defines the identifier of each column by implementing Iden and IdenStatic. /// The EnumIter is also derived, allowing iteration over all enum variants. /// /// ### Usage /// /// ``` /// use sea_orm::entity::prelude::*; /// /// #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// pub enum Column { /// CakeId, /// FillingId, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveColumn, attributes(sea_orm))] pub fn derive_column(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); match derives::expand_derive_column(&ident, &data) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } /// The DeriveModel derive macro will implement ModelTrait for Model, /// which provides setters and getters for all attributes in the mod /// It also implements FromQueryResult to convert a query result into the corresponding Model. /// /// ### Usage /// /// ``` /// use sea_orm::entity::prelude::*; /// /// #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] /// pub struct Model { /// pub id: i32, /// pub name: String, /// } /// /// # #[derive(Copy, Clone, Default, Debug, DeriveEntity)] /// # pub struct Entity; /// # /// # impl EntityName for Entity { /// # fn table_name(&self) -> &'static str { /// # "cake" /// # } /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// # pub enum Column { /// # Id, /// # Name, /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] /// # pub enum PrimaryKey { /// # Id, /// # } /// # /// # impl PrimaryKeyTrait for PrimaryKey { /// # type ValueType = i32; /// # /// # fn auto_increment() -> bool { /// # true /// # } /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter)] /// # pub enum Relation {} /// # /// # impl ColumnTrait for Column { /// # type EntityName = Entity; /// # /// # fn def(&self) -> ColumnDef { /// # match self { /// # Self::Id => ColumnType::Integer.def(), /// # Self::Name => ColumnType::String(StringLen::None).def(), /// # } /// # } /// # } /// # /// # impl RelationTrait for Relation { /// # fn def(&self) -> RelationDef { /// # panic!("No Relation"); /// # } /// # } /// # /// # impl ActiveModelBehavior for ActiveModel {} /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveModel, attributes(sea_orm))] pub fn derive_model(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, attrs, .. } = parse_macro_input!(input as DeriveInput); derives::expand_derive_model(&ident, &data, &attrs) .unwrap_or_else(Error::into_compile_error) .into() } /// The DeriveActiveModel derive macro will implement ActiveModelTrait for ActiveModel /// which provides setters and getters for all active values in the active model. /// /// ### Usage /// /// ``` /// use sea_orm::entity::prelude::*; /// /// #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] /// pub struct Model { /// pub id: i32, /// pub name: String, /// } /// /// # #[derive(Copy, Clone, Default, Debug, DeriveEntity)] /// # pub struct Entity; /// # /// # impl EntityName for Entity { /// # fn table_name(&self) -> &'static str { /// # "cake" /// # } /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// # pub enum Column { /// # Id, /// # Name, /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] /// # pub enum PrimaryKey { /// # Id, /// # } /// # /// # impl PrimaryKeyTrait for PrimaryKey { /// # type ValueType = i32; /// # /// # fn auto_increment() -> bool { /// # true /// # } /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter)] /// # pub enum Relation {} /// # /// # impl ColumnTrait for Column { /// # type EntityName = Entity; /// # /// # fn def(&self) -> ColumnDef { /// # match self { /// # Self::Id => ColumnType::Integer.def(), /// # Self::Name => ColumnType::String(StringLen::None).def(), /// # } /// # } /// # } /// # /// # impl RelationTrait for Relation { /// # fn def(&self) -> RelationDef { /// # panic!("No Relation"); /// # } /// # } /// # /// # impl ActiveModelBehavior for ActiveModel {} /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveActiveModel, attributes(sea_orm))] pub fn derive_active_model(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); match derives::expand_derive_active_model(&ident, &data) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } /// Will implement [`IntoActiveModel`] for a struct, allowing conversion into an ActiveModel. /// This is useful for "form" or "input" structs (like from API requests) that turn into /// an entity's ActiveModel but may omit some fields or provide optional values with defaults. /// /// ## Usage: /// /// ```rust /// # mod fruit { /// # use sea_orm::entity::prelude::*; /// # #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// # #[sea_orm(table_name = "fruit")] /// # pub struct Model { /// # #[sea_orm(primary_key)] /// # pub id: i32, /// # pub name: String, /// # pub cake_id: Option, /// # } /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] /// # pub enum Relation {} /// # impl ActiveModelBehavior for ActiveModel {} /// # } /// use sea_orm::entity::prelude::*; /// /// #[derive(DeriveIntoActiveModel)] /// #[sea_orm(active_model = "fruit::ActiveModel")] /// struct NewFruit { /// name: String, /// // `id` and `cake_id` are omitted - they become `NotSet` /// } /// ``` /// /// ## `set(...)` - always set absent ActiveModel fields /// /// ```rust /// # mod fruit { /// # use sea_orm::entity::prelude::*; /// # #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// # #[sea_orm(table_name = "fruit")] /// # pub struct Model { /// # #[sea_orm(primary_key)] /// # pub id: i32, /// # pub name: String, /// # pub cake_id: Option, /// # } /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] /// # pub enum Relation {} /// # impl ActiveModelBehavior for ActiveModel {} /// # } /// use sea_orm::entity::prelude::*; /// /// #[derive(DeriveIntoActiveModel)] /// #[sea_orm(active_model = "fruit::ActiveModel", set(cake_id = "None"))] /// struct NewFruit { /// name: String, /// // `cake_id` is not on the struct, but will always be `Set(None)` /// } /// ``` /// /// ## `default = "expr"` - fallback for `Option` struct fields /// /// ```rust /// # mod fruit { /// # use sea_orm::entity::prelude::*; /// # #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// # #[sea_orm(table_name = "fruit")] /// # pub struct Model { /// # #[sea_orm(primary_key)] /// # pub id: i32, /// # pub name: String, /// # pub cake_id: Option, /// # } /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] /// # pub enum Relation {} /// # impl ActiveModelBehavior for ActiveModel {} /// # } /// use sea_orm::entity::prelude::*; /// /// #[derive(DeriveIntoActiveModel)] /// #[sea_orm(active_model = "fruit::ActiveModel")] /// struct UpdateFruit { /// /// `Some("Apple")` -> `Set("Apple")`, `None` ->`Set("Unnamed")` /// #[sea_orm(default = "String::from(\"Unnamed\")")] /// name: Option, /// } /// ``` /// /// ## Combining `set(...)`, `default`, `ignore`, and `exhaustive` /// /// ```rust /// # mod fruit { /// # use sea_orm::entity::prelude::*; /// # #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// # #[sea_orm(table_name = "fruit")] /// # pub struct Model { /// # #[sea_orm(primary_key)] /// # pub id: i32, /// # pub name: String, /// # pub cake_id: Option, /// # } /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] /// # pub enum Relation {} /// # impl ActiveModelBehavior for ActiveModel {} /// # } /// use sea_orm::entity::prelude::*; /// /// #[derive(DeriveIntoActiveModel)] /// #[sea_orm(active_model = "fruit::ActiveModel", exhaustive, set(cake_id = "None"))] /// struct NewFruit { /// id: i32, /// #[sea_orm(default = "String::from(\"Unnamed\")")] /// name: Option, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveIntoActiveModel, attributes(sea_orm))] pub fn derive_into_active_model(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); derives::expand_into_active_model(input) .unwrap_or_else(Error::into_compile_error) .into() } /// Models that a user can override /// /// ### Usage /// /// ``` /// use sea_orm::entity::prelude::*; /// /// #[derive( /// Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, DeriveActiveModelBehavior, /// )] /// pub struct Model { /// pub id: i32, /// pub name: String, /// } /// /// # #[derive(Copy, Clone, Default, Debug, DeriveEntity)] /// # pub struct Entity; /// # /// # impl EntityName for Entity { /// # fn table_name(&self) -> &'static str { /// # "cake" /// # } /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// # pub enum Column { /// # Id, /// # Name, /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] /// # pub enum PrimaryKey { /// # Id, /// # } /// # /// # impl PrimaryKeyTrait for PrimaryKey { /// # type ValueType = i32; /// # /// # fn auto_increment() -> bool { /// # true /// # } /// # } /// # /// # #[derive(Copy, Clone, Debug, EnumIter)] /// # pub enum Relation {} /// # /// # impl ColumnTrait for Column { /// # type EntityName = Entity; /// # /// # fn def(&self) -> ColumnDef { /// # match self { /// # Self::Id => ColumnType::Integer.def(), /// # Self::Name => ColumnType::String(StringLen::None).def(), /// # } /// # } /// # } /// # /// # impl RelationTrait for Relation { /// # fn def(&self) -> RelationDef { /// # panic!("No Relation"); /// # } /// # } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveActiveModelBehavior)] pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); match derives::expand_derive_active_model_behavior(ident, data) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } /// A derive macro to implement `sea_orm::ActiveEnum` trait for enums. /// /// # Limitations /// /// This derive macros can only be used on enums. /// /// # Macro Attributes /// /// All macro attributes listed below have to be annotated in the form of `#[sea_orm(attr = value)]`. /// /// - For enum /// - `rs_type`: Define `ActiveEnum::Value` /// - Possible values: `String`, `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64` /// - Note that value has to be passed as string, i.e. `rs_type = "i8"` /// - `db_type`: Define `ColumnType` returned by `ActiveEnum::db_type()` /// - Possible values: all available enum variants of `ColumnType`, e.g. `String(StringLen::None)`, `String(StringLen::N(1))`, `Integer` /// - Note that value has to be passed as string, i.e. `db_type = "Integer"` /// - `enum_name`: Define `String` returned by `ActiveEnum::name()` /// - This attribute is optional with default value being the name of enum in camel-case /// - Note that value has to be passed as string, i.e. `enum_name = "MyEnum"` /// /// - For enum variant /// - `string_value` or `num_value`: /// - For `string_value`, value should be passed as string, i.e. `string_value = "A"` /// - Due to the way internal Enums are automatically derived, the following restrictions apply: /// - members cannot share identical `string_value`, case-insensitive. /// - in principle, any future Titlecased Rust keywords are not valid `string_value`. /// - For `num_value`, value should be passed as integer, i.e. `num_value = 1` or `num_value = 1i32` /// - Note that only one of it can be specified, and all variants of an enum have to annotate with the same `*_value` macro attribute /// /// # Usage /// /// ``` /// use sea_orm::{DeriveActiveEnum, entity::prelude::*}; /// /// #[derive(EnumIter, DeriveActiveEnum)] /// #[sea_orm(rs_type = "i32", db_type = "Integer")] /// pub enum Color { /// Black = 0, /// White = 1, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveActiveEnum, attributes(sea_orm))] pub fn derive_active_enum(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); match derives::expand_derive_active_enum(input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } /// Convert a query result into the corresponding Model. /// /// ### Attributes /// /// - `skip`: will not try to pull this field from the query result. And set it to the default value of the type. /// - `nested`: allows nesting models. can be any type that implements `FromQueryResult` /// - `alias` / `from_alias`: get the value from this column alias /// /// ### Usage /// /// For more complete examples, please refer to https://github.com/SeaQL/sea-orm/blob/master/tests/from_query_result_tests.rs /// /// ``` /// use sea_orm::{FromQueryResult, entity::prelude::*}; /// /// #[derive(FromQueryResult)] /// struct Cake { /// id: i32, /// name: String, /// #[sea_orm(nested)] /// bakery: Option, /// #[sea_orm(skip)] /// skip_me: i32, /// } /// /// #[derive(FromQueryResult)] /// struct CakeBakery { /// #[sea_orm(alias = "bakery_id")] /// id: i32, /// #[sea_orm(alias = "bakery_name")] /// title: String, /// } /// ``` /// /// You can compose this with regular Models, if there's no column collision: /// /// ```ignore /// #[derive(FromQueryResult)] /// struct CakePlain { /// id: i32, /// name: String, /// price: Decimal, /// #[sea_orm(nested)] /// baker: Option, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(FromQueryResult, attributes(sea_orm))] pub fn derive_from_query_result(input: TokenStream) -> TokenStream { let derive_input = parse_macro_input!(input); match derives::expand_derive_from_query_result(derive_input) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } /// The DeriveRelation derive macro will implement RelationTrait for Relation. /// /// ### Usage /// /// ``` /// # use sea_orm::tests_cfg::fruit::Entity; /// use sea_orm::entity::prelude::*; /// /// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] /// pub enum Relation { /// #[sea_orm( /// belongs_to = "sea_orm::tests_cfg::cake::Entity", /// from = "sea_orm::tests_cfg::fruit::Column::CakeId", /// to = "sea_orm::tests_cfg::cake::Column::Id" /// )] /// Cake, /// #[sea_orm( /// belongs_to = "sea_orm::tests_cfg::cake_expanded::Entity", /// from = "sea_orm::tests_cfg::fruit::Column::CakeId", /// to = "sea_orm::tests_cfg::cake_expanded::Column::Id" /// )] /// CakeExpanded, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveRelation, attributes(sea_orm))] pub fn derive_relation(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); derives::expand_derive_relation(input) .unwrap_or_else(Error::into_compile_error) .into() } /// The DeriveRelatedEntity derive macro will implement seaography::RelationBuilder for RelatedEntity enumeration. /// /// ### Usage /// /// ```ignore /// use sea_orm::entity::prelude::*; /// /// // ... /// // Model, Relation enum, etc. /// // ... /// /// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] /// pub enum RelatedEntity { /// #[sea_orm(entity = "super::address::Entity")] /// Address, /// #[sea_orm(entity = "super::payment::Entity")] /// Payment, /// #[sea_orm(entity = "super::rental::Entity")] /// Rental, /// #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] /// SelfRef, /// #[sea_orm(entity = "super::store::Entity")] /// Store, /// #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] /// SelfRefRev, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveRelatedEntity, attributes(sea_orm))] pub fn derive_related_entity(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); derives::expand_derive_related_entity(input) .unwrap_or_else(Error::into_compile_error) .into() } /// The DeriveMigrationName derive macro will implement `sea_orm_migration::MigrationName` for a migration. /// /// ### Usage /// /// ```ignore /// #[derive(DeriveMigrationName)] /// pub struct Migration; /// ``` /// /// The derive macro above will provide following implementation, /// given the file name is `m20220120_000001_create_post_table.rs`. /// /// ```ignore /// impl MigrationName for Migration { /// fn name(&self) -> &str { /// "m20220120_000001_create_post_table" /// } /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveMigrationName)] pub fn derive_migration_name(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); derives::expand_derive_migration_name(input) .unwrap_or_else(Error::into_compile_error) .into() } #[cfg(feature = "derive")] #[proc_macro_derive(FromJsonQueryResult)] pub fn derive_from_json_query_result(input: TokenStream) -> TokenStream { let DeriveInput { ident, .. } = parse_macro_input!(input); match derives::expand_derive_from_json_query_result(ident) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } /// The DerivePartialModel derive macro will implement [`sea_orm::PartialModelTrait`] for simplify partial model queries. /// Since 2.0, this macro cannot be used with the `FromQueryResult` macro. /// /// ## Usage /// /// For more complete examples, please refer to https://github.com/SeaQL/sea-orm/blob/master/tests/partial_model_tests.rs /// /// ```rust /// use sea_orm::sea_query::ExprTrait; /// use sea_orm::{DerivePartialModel, entity::prelude::*}; /// /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// #[sea_orm(table_name = "posts")] /// pub struct Model { /// #[sea_orm(primary_key)] /// pub id: i32, /// pub title: String, /// #[sea_orm(column_type = "Text")] /// pub text: String, /// } /// # #[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] /// # pub enum Relation {} /// # impl ActiveModelBehavior for ActiveModel {} /// /// #[derive(Debug, DerivePartialModel)] /// #[sea_orm(entity = "Entity")] /// struct SelectResult { /// title: String, /// #[sea_orm(from_col = "text")] /// content: String, /// #[sea_orm(from_expr = "Expr::val(1).add(1)")] /// sum: i32, /// } /// ``` /// /// If all fields in the partial model is `from_expr`, the specifying the `entity` can be skipped. /// ``` /// use sea_orm::{ /// DerivePartialModel, /// entity::prelude::*, /// sea_query::{Expr, ExprTrait}, /// }; /// /// #[derive(Debug, DerivePartialModel)] /// struct SelectResult { /// #[sea_orm(from_expr = "Expr::val(1).add(1)")] /// sum: i32, /// } /// ``` /// /// Since SeaORM 1.1.7, `DerivePartialModel` can also derive `FromQueryResult`. /// This is necessary to support nested partial models. /// Since 2.0, `from_query_result` is implemented by default, unless `from_query_result = "false"`. /// /// ``` /// use sea_orm::DerivePartialModel; /// # /// # mod cake { /// # use sea_orm::entity::prelude::*; /// # #[derive(Clone, Debug, DeriveEntityModel)] /// # #[sea_orm(table_name = "cake")] /// # pub struct Model { /// # #[sea_orm(primary_key)] /// # pub id: i32, /// # pub name: String, /// # } /// # #[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] /// # pub enum Relation {} /// # impl ActiveModelBehavior for ActiveModel {} /// # } /// # /// # mod bakery { /// # use sea_orm::entity::prelude::*; /// # #[derive(Clone, Debug, DeriveEntityModel)] /// # #[sea_orm(table_name = "bakery")] /// # pub struct Model { /// # #[sea_orm(primary_key)] /// # pub id: i32, /// # pub name: String, /// # } /// # #[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] /// # pub enum Relation {} /// # impl ActiveModelBehavior for ActiveModel {} /// # } /// /// #[derive(DerivePartialModel)] /// #[sea_orm(entity = "cake::Entity")] /// struct Cake { /// id: i32, /// name: String, /// #[sea_orm(nested)] /// bakery: Option, /// #[sea_orm(skip)] /// ignore: String, /// } /// /// #[derive(DerivePartialModel)] /// #[sea_orm(entity = "bakery::Entity")] /// struct Bakery { /// id: i32, /// #[sea_orm(from_col = "Name")] /// title: String, /// } /// /// // In addition, there's an `alias` attribute to select the columns from an alias: /// /// #[derive(DerivePartialModel)] /// #[sea_orm(entity = "bakery::Entity", alias = "factory")] /// struct Factory { /// id: i32, /// #[sea_orm(from_col = "name")] /// plant: String, /// } /// /// #[derive(DerivePartialModel)] /// #[sea_orm(entity = "cake::Entity")] /// struct CakeFactory { /// id: i32, /// name: String, /// #[sea_orm(nested)] /// bakery: Option, /// } /// ``` /// /// ```ignore /// let cake: CakeFactory = cake::Entity::find() /// .join_as( /// JoinType::LeftJoin, /// cake::Relation::Bakery.def(), /// "factory", /// ) /// .order_by_asc(cake::Column::Id) /// .into_partial_model() /// .one(&db) /// .await /// .unwrap() /// .unwrap() /// /// SELECT /// "cake"."id" AS "id", "cake"."name" AS "name", /// "factory"."id" AS "bakery_id", "factory"."name" AS "bakery_plant" /// FROM "cake" /// LEFT JOIN "bakery" AS "factory" ON "cake"."bakery_id" = "factory"."id" /// LIMIT 1 /// ``` /// /// A field cannot have attributes `from_col`, `from_expr` or `nested` at the same time. /// Or, it will result in a compile error. /// /// ```compile_fail /// use sea_orm::{entity::prelude::*, DerivePartialModel, sea_query::Expr}; /// /// #[derive(Debug, DerivePartialModel)] /// #[sea_orm(entity = "Entity")] /// struct SelectResult { /// #[sea_orm(from_expr = "Expr::val(1).add(1)", from_col = "foo")] /// sum: i32, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DerivePartialModel, attributes(sea_orm))] pub fn derive_partial_model(input: TokenStream) -> TokenStream { let derive_input = parse_macro_input!(input); match derives::expand_derive_partial_model(derive_input) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } #[doc(hidden)] #[cfg(feature = "derive")] #[proc_macro_attribute] pub fn test(_: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as syn::ItemFn); let ret = &input.sig.output; let name = &input.sig.ident; let body = &input.block; let attrs = &input.attrs; if cfg!(feature = "async") { quote::quote! ( #[test] #[cfg(any( feature = "sqlx-mysql", feature = "sqlx-sqlite", feature = "sqlx-postgres", ))] #(#attrs)* fn #name() #ret { let _ = ::tracing_subscriber::fmt() .with_max_level(::tracing::Level::DEBUG) .with_test_writer() .try_init(); crate::block_on!(async { #body }) } ) .into() } else { quote::quote! ( #[test] #[cfg(any( feature = "rusqlite", ))] #(#attrs)* fn #name() #ret { let _ = ::tracing_subscriber::fmt() .with_max_level(::tracing::Level::DEBUG) .with_test_writer() .try_init(); #body } ) .into() } } /// Creates a new type that iterates of the variants of an enum. /// /// Iterate over the variants of an Enum. Any additional data on your variants will be set to `Default::default()`. /// The macro implements `strum::IntoEnumIterator` on your enum and creates a new type called `YourEnumIter` that is the iterator object. /// You cannot derive `EnumIter` on any type with a lifetime bound (`<'a>`) because the iterator would surely /// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). #[cfg(feature = "strum")] #[proc_macro_derive(EnumIter, attributes(strum))] pub fn enum_iter(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); strum::enum_iter::enum_iter_inner(&ast) .unwrap_or_else(Error::into_compile_error) .into() } /// Implements traits for types that wrap a database value type. /// /// This procedure macro implements `From for Value`, `sea_orm::TryGetTable`, and /// `sea_query::ValueType` for the wrapper type `T`. /// /// It also implements `sea_query::postgres_array::NotU8`. /// It implements `TryFromU64` if the wrapped types are recognized primitives, i.e. /// `"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64"`. /// /// The wrapped type must be `sea_orm::Value` compatible. /// /// ## Usage /// /// ```rust /// use sea_orm::DeriveValueType; /// /// #[derive(DeriveValueType)] /// struct MyString(String); /// /// #[derive(DeriveValueType)] /// struct MyNumber(i32); /// ``` /// /// It's also possible to derive value type for enum-strings. /// Basically the underlying type is String, and the custom must implement methods `to_str` and `from_str`. /// /// ## Example /// /// ```rust /// use sea_orm::{DeriveValueType, sea_query::ValueTypeErr}; /// /// #[derive(DeriveValueType)] /// #[sea_orm(value_type = "String")] /// pub enum Tag { /// Hard, /// Soft, /// } /// /// impl std::fmt::Display for Tag { /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// write!( /// f, /// "{}", /// match self { /// Self::Hard => "hard", /// Self::Soft => "soft", /// } /// ) /// } /// } /// /// impl std::str::FromStr for Tag { /// type Err = ValueTypeErr; /// /// fn from_str(s: &str) -> Result { /// Ok(match s { /// "hard" => Self::Hard, /// "soft" => Self::Soft, /// _ => return Err(ValueTypeErr), /// }) /// } /// } /// ``` /// /// `from_str` defaults to `std::str::FromStr::from_str`. `to_str` defaults to `std::string::ToString::to_string`. /// They can be overridden with custom functions. /// /// ```rust /// use sea_orm::{DeriveValueType, sea_query::ValueTypeErr}; /// /// #[derive(DeriveValueType)] /// #[sea_orm( /// value_type = "String", /// from_str = "Tag::from_str", /// to_str = "Tag::to_str" /// )] /// pub enum Tag { /// Color, /// Grey, /// } /// /// impl Tag { /// fn to_str(&self) -> &'static str { /// match self { /// Self::Color => "color", /// Self::Grey => "grey", /// } /// } /// /// fn from_str(s: &str) -> Result { /// Ok(match s { /// "color" => Self::Color, /// "grey" => Self::Grey, /// _ => return Err(ValueTypeErr), /// }) /// } /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveValueType, attributes(sea_orm))] pub fn derive_value_type(input: TokenStream) -> TokenStream { let derive_input = parse_macro_input!(input as DeriveInput); match derives::expand_derive_value_type(derive_input) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } #[cfg(feature = "derive")] #[proc_macro_derive(DeriveDisplay, attributes(sea_orm))] pub fn derive_active_enum_display(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); match derives::expand_derive_active_enum_display(input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } /// The DeriveIden derive macro will implement `sea_orm::Iden` for simplify Iden implementation. /// /// ## Usage /// /// ```rust /// use sea_orm::{DeriveIden, Iden}; /// /// #[derive(DeriveIden)] /// pub enum MyClass { /// Table, // this is a special case, which maps to the enum's name /// Id, /// #[sea_orm(iden = "turtle")] /// Title, /// Text, /// } /// /// #[derive(DeriveIden)] /// struct MyOther; /// /// assert_eq!(MyClass::Table.to_string(), "my_class"); /// assert_eq!(MyClass::Id.to_string(), "id"); /// assert_eq!(MyClass::Title.to_string(), "turtle"); // renamed! /// assert_eq!(MyClass::Text.to_string(), "text"); /// assert_eq!(MyOther.to_string(), "my_other"); /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveIden, attributes(sea_orm))] pub fn derive_iden(input: TokenStream) -> TokenStream { let derive_input = parse_macro_input!(input as DeriveInput); match derives::expand_derive_iden(derive_input) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } /// The DeriveArrowSchema derive macro generates an `arrow_schema()` method /// that returns an Apache Arrow schema from a SeaORM entity definition. /// /// This macro is automatically applied when `#[sea_orm(arrow_schema)]` is specified /// on a model. You typically don't need to use this derive macro directly. /// /// ## Usage /// /// ```rust,ignore /// use sea_orm::entity::prelude::*; /// /// #[sea_orm::model] /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// #[sea_orm(table_name = "user", arrow_schema)] /// pub struct Model { /// #[sea_orm(primary_key)] /// pub id: i64, /// pub name: String, /// #[sea_orm(column_type = "Decimal(Some((20, 4)))")] /// pub amount: Decimal, /// } /// ``` #[cfg(feature = "derive")] #[proc_macro_derive(DeriveArrowSchema, attributes(sea_orm))] pub fn derive_arrow_schema(input: TokenStream) -> TokenStream { let derive_input = parse_macro_input!(input as DeriveInput); match derives::expand_derive_arrow_schema( derive_input.ident, derive_input.data, derive_input.attrs, ) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } #[proc_macro] pub fn raw_sql(input: TokenStream) -> TokenStream { match raw_sql::expand(input) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } #[cfg(feature = "derive")] #[proc_macro_attribute] pub fn sea_orm_model(_attr: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as syn::ItemStruct); match derives::expand_sea_orm_model(input, false) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } #[cfg(feature = "derive")] #[proc_macro_attribute] pub fn sea_orm_compact_model(_attr: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as syn::ItemStruct); match derives::expand_sea_orm_model(input, true) { Ok(token_stream) => token_stream.into(), Err(e) => e.to_compile_error().into(), } } ================================================ FILE: sea-orm-macros/src/raw_sql.rs ================================================ use proc_macro2::TokenStream; use quote::quote; use syn::{ Ident, LitStr, Token, parse::{Parse, ParseStream}, }; struct CallArgs { backend: Ident, _comma: Token![,], sql_string: LitStr, } impl Parse for CallArgs { fn parse(input: ParseStream) -> syn::Result { Ok(CallArgs { backend: input.parse()?, _comma: input.parse()?, sql_string: input.parse()?, }) } } pub fn expand(input: proc_macro::TokenStream) -> syn::Result { let CallArgs { backend, sql_string, .. } = syn::parse(input)?; let builder = match backend.to_string().as_str() { "MySql" => quote!(MysqlQueryBuilder), "Postgres" => quote!(PostgresQueryBuilder), "Sqlite" => quote!(SqliteQueryBuilder), backend => panic!("Unsupported backend {backend}"), }; Ok(quote! {{ use sea_orm::sea_query; let query = sea_query::raw_query!(#builder, #sql_string); sea_orm::Statement { sql: query.sql, values: Some(query.values), db_backend: sea_orm::DbBackend::#backend, } }}) } ================================================ FILE: sea-orm-macros/src/strum/LICENSE ================================================ > The `strum` module is adapted from https://github.com/Peternator7/strum MIT License Copyright (c) 2019 Peter Glotfelty Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: sea-orm-macros/src/strum/enum_iter.rs ================================================ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Data, DeriveInput, Fields, Ident}; use super::helpers::{HasStrumVariantProperties, HasTypeProperties, non_enum_error}; pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let r#gen = &ast.generics; let (impl_generics, ty_generics, where_clause) = r#gen.split_for_impl(); let vis = &ast.vis; let type_properties = ast.get_type_properties()?; let strum_module_path = type_properties.crate_module_path(); let doc_comment = format!("An iterator over the variants of [{name}]"); if r#gen.lifetimes().count() > 0 { return Err(syn::Error::new( Span::call_site(), "This macro doesn't support enums with lifetimes. \ The resulting enums would be unbounded.", )); } let phantom_data = if r#gen.type_params().count() > 0 { let g = r#gen.type_params().map(|param| ¶m.ident); quote! { < ( #(#g),* ) > } } else { quote! { < () > } }; let variants = match &ast.data { Data::Enum(v) => &v.variants, _ => return Err(non_enum_error()), }; let mut arms = Vec::new(); let mut idx = 0usize; for variant in variants { if variant.get_variant_properties()?.disabled.is_some() { continue; } let ident = &variant.ident; let params = match &variant.fields { Fields::Unit => quote! {}, Fields::Unnamed(fields) => { let defaults = ::core::iter::repeat_n( quote!(::core::default::Default::default()), fields.unnamed.len(), ); quote! { (#(#defaults),*) } } Fields::Named(fields) => { let fields = fields .named .iter() .map(|field| field.ident.as_ref().unwrap()); quote! { {#(#fields: ::core::default::Default::default()),*} } } }; arms.push(quote! {#idx => ::core::option::Option::Some(#name::#ident #params)}); idx += 1; } let variant_count = arms.len(); arms.push(quote! { _ => ::core::option::Option::None }); let iter_name = syn::parse_str::(&format!("{name}Iter")).unwrap(); // Create a string literal "MyEnumIter" to use in the debug impl. let iter_name_debug_struct = syn::parse_str::(&format!("\"{iter_name}\"")).unwrap(); Ok(quote! { #[doc = #doc_comment] #[allow( missing_copy_implementations, )] #vis struct #iter_name #impl_generics { idx: usize, back_idx: usize, marker: ::core::marker::PhantomData #phantom_data, } impl #impl_generics ::core::fmt::Debug for #iter_name #ty_generics #where_clause { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { // We don't know if the variants implement debug themselves so the only thing we // can really show is how many elements are left. f.debug_struct(#iter_name_debug_struct) .field("len", &self.len()) .finish() } } impl #impl_generics #iter_name #ty_generics #where_clause { fn get(&self, idx: usize) -> ::core::option::Option<#name #ty_generics> { match idx { #(#arms),* } } } impl #impl_generics #strum_module_path::IntoEnumIterator for #name #ty_generics #where_clause { type Iterator = #iter_name #ty_generics; fn iter() -> #iter_name #ty_generics { #iter_name { idx: 0, back_idx: 0, marker: ::core::marker::PhantomData, } } } impl #impl_generics Iterator for #iter_name #ty_generics #where_clause { type Item = #name #ty_generics; fn next(&mut self) -> ::core::option::Option<::Item> { self.nth(0) } fn size_hint(&self) -> (usize, ::core::option::Option) { let t = if self.idx + self.back_idx >= #variant_count { 0 } else { #variant_count - self.idx - self.back_idx }; (t, Some(t)) } fn nth(&mut self, n: usize) -> ::core::option::Option<::Item> { let idx = self.idx + n + 1; if idx + self.back_idx > #variant_count { // We went past the end of the iterator. Freeze idx at #variant_count // so that it doesn't overflow if the user calls this repeatedly. // See PR #76 for context. self.idx = #variant_count; ::core::option::Option::None } else { self.idx = idx; #iter_name::get(self, idx - 1) } } } impl #impl_generics ExactSizeIterator for #iter_name #ty_generics #where_clause { fn len(&self) -> usize { self.size_hint().0 } } impl #impl_generics DoubleEndedIterator for #iter_name #ty_generics #where_clause { fn next_back(&mut self) -> ::core::option::Option<::Item> { let back_idx = self.back_idx + 1; if self.idx + back_idx > #variant_count { // We went past the end of the iterator. Freeze back_idx at #variant_count // so that it doesn't overflow if the user calls this repeatedly. // See PR #76 for context. self.back_idx = #variant_count; ::core::option::Option::None } else { self.back_idx = back_idx; #iter_name::get(self, #variant_count - self.back_idx) } } } impl #impl_generics ::core::iter::FusedIterator for #iter_name #ty_generics #where_clause { } impl #impl_generics Clone for #iter_name #ty_generics #where_clause { fn clone(&self) -> #iter_name #ty_generics { #iter_name { idx: self.idx, back_idx: self.back_idx, marker: self.marker.clone(), } } } }) } ================================================ FILE: sea-orm-macros/src/strum/helpers/case_style.rs ================================================ use heck::{ ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, }; use std::str::FromStr; use syn::{ Ident, LitStr, parse::{Parse, ParseStream}, }; #[allow(clippy::enum_variant_names)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum CaseStyle { CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase, UpperCase, LowerCase, ScreamingKebabCase, PascalCase, } const VALID_CASE_STYLES: &[&str] = &[ "camelCase", "PascalCase", "kebab-case", "snake_case", "SCREAMING_SNAKE_CASE", "SCREAMING-KEBAB-CASE", "lowercase", "UPPERCASE", "title_case", "mixed_case", ]; impl Parse for CaseStyle { fn parse(input: ParseStream) -> syn::Result { let text = input.parse::()?; let val = text.value(); val.as_str().parse().map_err(|_| { syn::Error::new_spanned( &text, format!( "Unexpected case style for serialize_all: `{val}`. Valid values are: `{VALID_CASE_STYLES:?}`", ), ) }) } } impl FromStr for CaseStyle { type Err = (); fn from_str(text: &str) -> Result { Ok(match text { "camel_case" | "PascalCase" => CaseStyle::PascalCase, "camelCase" => CaseStyle::CamelCase, "snake_case" | "snek_case" => CaseStyle::SnakeCase, "kebab_case" | "kebab-case" => CaseStyle::KebabCase, "SCREAMING-KEBAB-CASE" => CaseStyle::ScreamingKebabCase, "shouty_snake_case" | "shouty_snek_case" | "SCREAMING_SNAKE_CASE" => { CaseStyle::ShoutySnakeCase } "title_case" => CaseStyle::TitleCase, "mixed_case" => CaseStyle::MixedCase, "lowercase" => CaseStyle::LowerCase, "UPPERCASE" => CaseStyle::UpperCase, _ => return Err(()), }) } } pub trait CaseStyleHelpers { fn convert_case(&self, case_style: Option) -> String; } impl CaseStyleHelpers for Ident { fn convert_case(&self, case_style: Option) -> String { let ident_string = self.to_string(); if let Some(case_style) = case_style { match case_style { CaseStyle::PascalCase => ident_string.to_upper_camel_case(), CaseStyle::KebabCase => ident_string.to_kebab_case(), CaseStyle::MixedCase => ident_string.to_lower_camel_case(), CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(), CaseStyle::SnakeCase => ident_string.to_snake_case(), CaseStyle::TitleCase => ident_string.to_title_case(), CaseStyle::UpperCase => ident_string.to_uppercase(), CaseStyle::LowerCase => ident_string.to_lowercase(), CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(), CaseStyle::CamelCase => { let camel_case = ident_string.to_upper_camel_case(); let mut pascal = String::with_capacity(camel_case.len()); let mut it = camel_case.chars(); if let Some(ch) = it.next() { pascal.extend(ch.to_lowercase()); } pascal.extend(it); pascal } } } else { ident_string } } } #[test] fn test_convert_case() { let id = Ident::new("test_me", proc_macro2::Span::call_site()); assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase))); assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase))); } ================================================ FILE: sea-orm-macros/src/strum/helpers/metadata.rs ================================================ use proc_macro2::TokenStream; use syn::{ Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path, Token, Variant, Visibility, parenthesized, parse::{Parse, ParseStream}, parse_str, parse2, punctuated::Punctuated, }; use super::case_style::CaseStyle; pub mod kw { use syn::custom_keyword; pub use syn::token::Crate; // enum metadata custom_keyword!(serialize_all); custom_keyword!(use_phf); // enum discriminant metadata custom_keyword!(derive); custom_keyword!(name); custom_keyword!(vis); // variant metadata custom_keyword!(message); custom_keyword!(detailed_message); custom_keyword!(serialize); custom_keyword!(to_string); custom_keyword!(disabled); custom_keyword!(default); custom_keyword!(props); custom_keyword!(ascii_case_insensitive); } pub enum EnumMeta { SerializeAll { kw: kw::serialize_all, case_style: CaseStyle, }, AsciiCaseInsensitive(kw::ascii_case_insensitive), Crate { kw: kw::Crate, crate_module_path: Path, }, UsePhf(kw::use_phf), } impl Parse for EnumMeta { fn parse(input: ParseStream) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(kw::serialize_all) { let kw = input.parse::()?; input.parse::()?; let case_style = input.parse()?; Ok(EnumMeta::SerializeAll { kw, case_style }) } else if lookahead.peek(kw::Crate) { let kw = input.parse::()?; input.parse::()?; let path_str: LitStr = input.parse()?; let path_tokens = parse_str(&path_str.value())?; let crate_module_path = parse2(path_tokens)?; Ok(EnumMeta::Crate { kw, crate_module_path, }) } else if lookahead.peek(kw::ascii_case_insensitive) { Ok(EnumMeta::AsciiCaseInsensitive(input.parse()?)) } else if lookahead.peek(kw::use_phf) { Ok(EnumMeta::UsePhf(input.parse()?)) } else { Err(lookahead.error()) } } } pub enum EnumDiscriminantsMeta { Derive { kw: kw::derive, paths: Vec }, Name { kw: kw::name, name: Ident }, Vis { kw: kw::vis, vis: Visibility }, Other { path: Path, nested: TokenStream }, } impl Parse for EnumDiscriminantsMeta { fn parse(input: ParseStream) -> syn::Result { if input.peek(kw::derive) { let kw = input.parse()?; let content; parenthesized!(content in input); let paths = content.parse_terminated(Path::parse, Token![,])?; Ok(EnumDiscriminantsMeta::Derive { kw, paths: paths.into_iter().collect(), }) } else if input.peek(kw::name) { let kw = input.parse()?; let content; parenthesized!(content in input); let name = content.parse()?; Ok(EnumDiscriminantsMeta::Name { kw, name }) } else if input.peek(kw::vis) { let kw = input.parse()?; let content; parenthesized!(content in input); let vis = content.parse()?; Ok(EnumDiscriminantsMeta::Vis { kw, vis }) } else { let path = input.parse()?; let content; parenthesized!(content in input); let nested = content.parse()?; Ok(EnumDiscriminantsMeta::Other { path, nested }) } } } pub trait DeriveInputExt { /// Get all the strum metadata associated with an enum. fn get_metadata(&self) -> syn::Result>; /// Get all the `strum_discriminants` metadata associated with an enum. fn get_discriminants_metadata(&self) -> syn::Result>; } impl DeriveInputExt for DeriveInput { fn get_metadata(&self) -> syn::Result> { get_metadata_inner("strum", &self.attrs) } fn get_discriminants_metadata(&self) -> syn::Result> { get_metadata_inner("strum_discriminants", &self.attrs) } } pub enum VariantMeta { Message { kw: kw::message, value: LitStr, }, DetailedMessage { kw: kw::detailed_message, value: LitStr, }, Serialize { kw: kw::serialize, value: LitStr, }, Documentation { value: LitStr, }, ToString { kw: kw::to_string, value: LitStr, }, Disabled(kw::disabled), Default(kw::default), AsciiCaseInsensitive { kw: kw::ascii_case_insensitive, value: bool, }, Props { kw: kw::props, props: Vec<(LitStr, LitStr)>, }, } impl Parse for VariantMeta { fn parse(input: ParseStream) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(kw::message) { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; Ok(VariantMeta::Message { kw, value }) } else if lookahead.peek(kw::detailed_message) { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; Ok(VariantMeta::DetailedMessage { kw, value }) } else if lookahead.peek(kw::serialize) { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; Ok(VariantMeta::Serialize { kw, value }) } else if lookahead.peek(kw::to_string) { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; Ok(VariantMeta::ToString { kw, value }) } else if lookahead.peek(kw::disabled) { Ok(VariantMeta::Disabled(input.parse()?)) } else if lookahead.peek(kw::default) { Ok(VariantMeta::Default(input.parse()?)) } else if lookahead.peek(kw::ascii_case_insensitive) { let kw = input.parse()?; let value = if input.peek(Token![=]) { let _: Token![=] = input.parse()?; input.parse::()?.value } else { true }; Ok(VariantMeta::AsciiCaseInsensitive { kw, value }) } else if lookahead.peek(kw::props) { let kw = input.parse()?; let content; parenthesized!(content in input); let props = content.parse_terminated(Prop::parse, Token![,])?; Ok(VariantMeta::Props { kw, props: props .into_iter() .map(|Prop(k, v)| (LitStr::new(&k.to_string(), k.span()), v)) .collect(), }) } else { Err(lookahead.error()) } } } struct Prop(Ident, LitStr); impl Parse for Prop { fn parse(input: ParseStream) -> syn::Result { use syn::ext::IdentExt; let k = Ident::parse_any(input)?; let _: Token![=] = input.parse()?; let v = input.parse()?; Ok(Prop(k, v)) } } pub trait VariantExt { /// Get all the metadata associated with an enum variant. fn get_metadata(&self) -> syn::Result>; } impl VariantExt for Variant { fn get_metadata(&self) -> syn::Result> { let result = get_metadata_inner("strum", &self.attrs)?; self.attrs .iter() .filter(|attr| attr.path().is_ident("doc")) .try_fold(result, |mut vec, attr| { if let Meta::NameValue(MetaNameValue { value: Expr::Lit(ExprLit { lit: Lit::Str(value), .. }), .. }) = &attr.meta { vec.push(VariantMeta::Documentation { value: value.clone(), }) } Ok(vec) }) } } fn get_metadata_inner<'a, T: Parse>( ident: &str, it: impl IntoIterator, ) -> syn::Result> { it.into_iter() .filter(|attr| attr.path().is_ident(ident)) .try_fold(Vec::new(), |mut vec, attr| { vec.extend(attr.parse_args_with(Punctuated::::parse_terminated)?); Ok(vec) }) } ================================================ FILE: sea-orm-macros/src/strum/helpers/mod.rs ================================================ pub use self::type_props::HasTypeProperties; pub use self::variant_props::HasStrumVariantProperties; pub mod case_style; mod metadata; pub mod type_props; pub mod variant_props; use proc_macro2::Span; use quote::ToTokens; pub fn non_enum_error() -> syn::Error { syn::Error::new(Span::call_site(), "This macro only supports enums.") } pub fn occurrence_error(fst: T, snd: T, attr: &str) -> syn::Error { let mut e = syn::Error::new_spanned(snd, format!("Found multiple occurrences of strum({attr})")); e.combine(syn::Error::new_spanned(fst, "first one here")); e } ================================================ FILE: sea-orm-macros/src/strum/helpers/type_props.rs ================================================ use proc_macro2::TokenStream; use quote::quote; use std::default::Default; use syn::{DeriveInput, Ident, Path, Visibility, parse_quote}; use super::case_style::CaseStyle; use super::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta}; use super::occurrence_error; pub trait HasTypeProperties { fn get_type_properties(&self) -> syn::Result; } #[derive(Debug, Clone, Default)] pub struct StrumTypeProperties { pub case_style: Option, pub ascii_case_insensitive: bool, pub crate_module_path: Option, pub discriminant_derives: Vec, pub discriminant_name: Option, pub discriminant_others: Vec, pub discriminant_vis: Option, pub use_phf: bool, } impl HasTypeProperties for DeriveInput { fn get_type_properties(&self) -> syn::Result { let mut output = StrumTypeProperties::default(); let strum_meta = self.get_metadata()?; let discriminants_meta = self.get_discriminants_metadata()?; let mut serialize_all_kw = None; let mut ascii_case_insensitive_kw = None; let mut use_phf_kw = None; let mut crate_module_path_kw = None; for meta in strum_meta { match meta { EnumMeta::SerializeAll { case_style, kw } => { if let Some(fst_kw) = serialize_all_kw { return Err(occurrence_error(fst_kw, kw, "serialize_all")); } serialize_all_kw = Some(kw); output.case_style = Some(case_style); } EnumMeta::AsciiCaseInsensitive(kw) => { if let Some(fst_kw) = ascii_case_insensitive_kw { return Err(occurrence_error(fst_kw, kw, "ascii_case_insensitive")); } ascii_case_insensitive_kw = Some(kw); output.ascii_case_insensitive = true; } EnumMeta::UsePhf(kw) => { if let Some(fst_kw) = use_phf_kw { return Err(occurrence_error(fst_kw, kw, "use_phf")); } use_phf_kw = Some(kw); output.use_phf = true; } EnumMeta::Crate { crate_module_path, kw, } => { if let Some(fst_kw) = crate_module_path_kw { return Err(occurrence_error(fst_kw, kw, "Crate")); } crate_module_path_kw = Some(kw); output.crate_module_path = Some(crate_module_path); } } } let mut name_kw = None; let mut vis_kw = None; for meta in discriminants_meta { match meta { EnumDiscriminantsMeta::Derive { paths, .. } => { output.discriminant_derives.extend(paths); } EnumDiscriminantsMeta::Name { name, kw } => { if let Some(fst_kw) = name_kw { return Err(occurrence_error(fst_kw, kw, "name")); } name_kw = Some(kw); output.discriminant_name = Some(name); } EnumDiscriminantsMeta::Vis { vis, kw } => { if let Some(fst_kw) = vis_kw { return Err(occurrence_error(fst_kw, kw, "vis")); } vis_kw = Some(kw); output.discriminant_vis = Some(vis); } EnumDiscriminantsMeta::Other { path, nested } => { output.discriminant_others.push(quote! { #path(#nested) }); } } } Ok(output) } } impl StrumTypeProperties { pub fn crate_module_path(&self) -> Path { self.crate_module_path .as_ref() .map_or_else(|| parse_quote!(sea_orm::strum), |path| parse_quote!(#path)) } } ================================================ FILE: sea-orm-macros/src/strum/helpers/variant_props.rs ================================================ use std::default::Default; use syn::{Ident, LitStr, Variant}; use super::metadata::{VariantExt, VariantMeta, kw}; use super::occurrence_error; pub trait HasStrumVariantProperties { fn get_variant_properties(&self) -> syn::Result; } #[derive(Clone, Eq, PartialEq, Debug, Default)] pub struct StrumVariantProperties { pub disabled: Option, pub default: Option, pub ascii_case_insensitive: Option, pub message: Option, pub detailed_message: Option, pub documentation: Vec, pub string_props: Vec<(LitStr, LitStr)>, serialize: Vec, to_string: Option, ident: Option, } impl HasStrumVariantProperties for Variant { fn get_variant_properties(&self) -> syn::Result { let mut output = StrumVariantProperties { ident: Some(self.ident.clone()), ..Default::default() }; let mut message_kw = None; let mut detailed_message_kw = None; let mut to_string_kw = None; let mut disabled_kw = None; let mut default_kw = None; let mut ascii_case_insensitive_kw = None; for meta in self.get_metadata()? { match meta { VariantMeta::Message { value, kw } => { if let Some(fst_kw) = message_kw { return Err(occurrence_error(fst_kw, kw, "message")); } message_kw = Some(kw); output.message = Some(value); } VariantMeta::DetailedMessage { value, kw } => { if let Some(fst_kw) = detailed_message_kw { return Err(occurrence_error(fst_kw, kw, "detailed_message")); } detailed_message_kw = Some(kw); output.detailed_message = Some(value); } VariantMeta::Documentation { value } => { output.documentation.push(value); } VariantMeta::Serialize { value, .. } => { output.serialize.push(value); } VariantMeta::ToString { value, kw } => { if let Some(fst_kw) = to_string_kw { return Err(occurrence_error(fst_kw, kw, "to_string")); } to_string_kw = Some(kw); output.to_string = Some(value); } VariantMeta::Disabled(kw) => { if let Some(fst_kw) = disabled_kw { return Err(occurrence_error(fst_kw, kw, "disabled")); } disabled_kw = Some(kw); output.disabled = Some(kw); } VariantMeta::Default(kw) => { if let Some(fst_kw) = default_kw { return Err(occurrence_error(fst_kw, kw, "default")); } default_kw = Some(kw); output.default = Some(kw); } VariantMeta::AsciiCaseInsensitive { kw, value } => { if let Some(fst_kw) = ascii_case_insensitive_kw { return Err(occurrence_error(fst_kw, kw, "ascii_case_insensitive")); } ascii_case_insensitive_kw = Some(kw); output.ascii_case_insensitive = Some(value); } VariantMeta::Props { props, .. } => { output.string_props.extend(props); } } } Ok(output) } } ================================================ FILE: sea-orm-macros/src/strum/mod.rs ================================================ //! Source code adapted from https://github.com/Peternator7/strum #![allow(dead_code)] pub mod enum_iter; pub mod helpers; ================================================ FILE: sea-orm-macros/tests/derive_active_enum_test.rs ================================================ use sea_orm::{ActiveEnum, entity::prelude::StringLen}; use sea_orm_macros::{DeriveActiveEnum, EnumIter}; #[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)] #[sea_orm( rs_type = "String", db_type = "Enum", enum_name = "test_enum", rename_all = "camelCase" )] enum TestEnum { DefaultVariant, #[sea_orm(rename = "camelCase")] VariantCamelCase, #[sea_orm(rename = "kebab-case")] VariantKebabCase, #[sea_orm(rename = "mixed_case")] VariantMixedCase, #[sea_orm(rename = "SCREAMING_SNAKE_CASE")] VariantShoutySnakeCase, #[sea_orm(rename = "snake_case")] VariantSnakeCase, #[sea_orm(rename = "title_case")] VariantTitleCase, #[sea_orm(rename = "UPPERCASE")] VariantUpperCase, #[sea_orm(rename = "lowercase")] VariantLowerCase, #[sea_orm(rename = "SCREAMING-KEBAB-CASE")] VariantScreamingKebabCase, #[sea_orm(rename = "PascalCase")] VariantPascalCase, #[sea_orm(string_value = "CuStOmStRiNgVaLuE")] CustomStringValue, } #[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)] #[sea_orm( rs_type = "String", db_type = "Enum", enum_name = "test_enum", rename_all = "camelCase" )] enum TestRenameAllWithoutCasesEnum { HelloWorld, } #[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)] #[sea_orm( rs_type = "String", db_type = "String(StringLen::None)", rename_all = "snake_case" )] pub enum TestEnum2 { HelloWorld, #[sea_orm(rename = "camelCase")] HelloWorldTwo, } #[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)] #[sea_orm( rs_type = "String", db_type = "String(StringLen::None)", rename_all = "snake_case" )] pub enum TestEnum3 { HelloWorld, } #[test] fn derive_active_enum_value() { assert_eq!(TestEnum::DefaultVariant.to_value(), "defaultVariant"); assert_eq!(TestEnum::VariantCamelCase.to_value(), "variantCamelCase"); assert_eq!(TestEnum::VariantKebabCase.to_value(), "variant-kebab-case"); assert_eq!(TestEnum::VariantMixedCase.to_value(), "variantMixedCase"); assert_eq!( TestEnum::VariantShoutySnakeCase.to_value(), "VARIANT_SHOUTY_SNAKE_CASE" ); assert_eq!(TestEnum::VariantSnakeCase.to_value(), "variant_snake_case"); assert_eq!(TestEnum::VariantTitleCase.to_value(), "Variant Title Case"); assert_eq!(TestEnum::VariantUpperCase.to_value(), "VARIANTUPPERCASE"); assert_eq!(TestEnum::VariantLowerCase.to_value(), "variantlowercase"); assert_eq!( TestEnum::VariantScreamingKebabCase.to_value(), "VARIANT-SCREAMING-KEBAB-CASE" ); assert_eq!(TestEnum::VariantPascalCase.to_value(), "VariantPascalCase"); assert_eq!(TestEnum::CustomStringValue.to_value(), "CuStOmStRiNgVaLuE"); } #[test] fn derive_active_enum_from_value() { assert_eq!( TestEnum::try_from_value(&"defaultVariant".to_string()), Ok(TestEnum::DefaultVariant) ); assert_eq!( TestEnum::try_from_value(&"variantCamelCase".to_string()), Ok(TestEnum::VariantCamelCase) ); assert_eq!( TestEnum::try_from_value(&"variant-kebab-case".to_string()), Ok(TestEnum::VariantKebabCase) ); assert_eq!( TestEnum::try_from_value(&"variantMixedCase".to_string()), Ok(TestEnum::VariantMixedCase) ); assert_eq!( TestEnum::try_from_value(&"VARIANT_SHOUTY_SNAKE_CASE".to_string()), Ok(TestEnum::VariantShoutySnakeCase), ); assert_eq!( TestEnum::try_from_value(&"variant_snake_case".to_string()), Ok(TestEnum::VariantSnakeCase) ); assert_eq!( TestEnum::try_from_value(&"Variant Title Case".to_string()), Ok(TestEnum::VariantTitleCase) ); assert_eq!( TestEnum::try_from_value(&"VARIANTUPPERCASE".to_string()), Ok(TestEnum::VariantUpperCase) ); assert_eq!( TestEnum::try_from_value(&"variantlowercase".to_string()), Ok(TestEnum::VariantLowerCase) ); assert_eq!( TestEnum::try_from_value(&"VARIANT-SCREAMING-KEBAB-CASE".to_string()), Ok(TestEnum::VariantScreamingKebabCase), ); assert_eq!( TestEnum::try_from_value(&"VariantPascalCase".to_string()), Ok(TestEnum::VariantPascalCase) ); assert_eq!( TestEnum::try_from_value(&"CuStOmStRiNgVaLuE".to_string()), Ok(TestEnum::CustomStringValue) ); } #[test] fn derive_active_enum_value_2() { assert_eq!(TestEnum2::HelloWorld.to_value(), "hello_world"); assert_eq!(TestEnum2::HelloWorldTwo.to_value(), "helloWorldTwo"); assert_eq!(TestEnum3::HelloWorld.to_value(), "hello_world"); assert_eq!( TestRenameAllWithoutCasesEnum::HelloWorld.to_value(), "helloWorld" ); } ================================================ FILE: sea-orm-macros/tests/derive_entity_model_auto_increment_test.rs ================================================ use sea_orm::entity::prelude::*; use sea_orm_macros::DeriveEntityModel; mod string_pk { use super::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "string_pk")] pub struct Model { #[sea_orm(primary_key)] pub id: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } mod string_pk_set_true { use super::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "string_pk")] pub struct Model { #[sea_orm(primary_key, auto_increment = true)] pub id: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } #[test] fn test_auto_increment_default_by_type() { assert!(!string_pk::PrimaryKey::auto_increment()); assert!(string_pk_set_true::PrimaryKey::auto_increment()); } ================================================ FILE: sea-orm-macros/tests/derive_entity_model_column_name_test.rs ================================================ use std::str::FromStr; use sea_orm::Iden; use sea_orm::Iterable; use sea_orm::prelude::*; use sea_orm_macros::DeriveEntityModel; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "user", rename_all = "camelCase")] pub struct Model { #[sea_orm(primary_key)] id: i32, username: String, first_name: String, middle_name: String, #[sea_orm(column_name = "lAsTnAmE")] last_name: String, orders_count: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} #[test] fn test_column_names() { let columns: Vec = Column::iter().map(|item| item.to_string()).collect(); assert_eq!( columns, vec![ "id", "username", "firstName", "middleName", "lAsTnAmE", "ordersCount", ] ); let col = Column::from_str("firstName").expect("column from str should recognize column_name attr"); assert!(matches!(col, Column::FirstName)); let col = Column::from_str("first_name").expect("column from str should recognize column_name attr"); assert!(matches!(col, Column::FirstName)); let col = Column::from_str("lastName").expect("column from str should recognize column_name attr"); assert!(matches!(col, Column::LastName)); let col = Column::from_str("last_name").expect("column from str should recognize column_name attr"); assert!(matches!(col, Column::LastName)); let col = Column::from_str("lAsTnAmE").expect("column from str should recognize column_name attr"); assert!(matches!(col, Column::LastName)); } ================================================ FILE: sea-orm-macros/tests/derive_value_type_test.rs ================================================ #[test] fn when_user_import_nothing_macro_still_works_test() { #[derive(sea_orm::DeriveValueType)] struct MyString(String); } #[test] fn when_user_alias_result_macro_still_works_test() { #[allow(dead_code)] type Result = std::result::Result; #[derive(sea_orm::DeriveValueType)] struct MyString(String); } #[test] fn when_stringy_newtype_works_test() { #[allow(dead_code)] #[derive(sea_orm::DeriveValueType)] #[sea_orm(value_type = "String")] struct Foo { inner: i32, } impl std::fmt::Display for Foo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.inner.fmt(f) } } impl std::str::FromStr for Foo { type Err = std::num::ParseIntError; fn from_str(s: &str) -> Result { Ok(Self { inner: s.parse()? }) } } } #[test] fn when_explicit_stringy_newtype_works_test() { #[derive(sea_orm::DeriveValueType)] #[sea_orm(value_type = "String")] struct Foo(i32); impl std::fmt::Display for Foo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl std::str::FromStr for Foo { type Err = std::num::ParseIntError; fn from_str(s: &str) -> Result { Ok(Self(s.parse()?)) } } } #[test] fn when_custom_from_str_works() { #[derive(sea_orm::DeriveValueType)] #[sea_orm( value_type = "String", from_str = "Foo::from_str", to_str = "Foo::to_str" )] struct Foo(i32); impl Foo { fn from_str(_s: &str) -> Result { Ok(Self(42)) } fn to_str(&self) -> String { 42.to_string() } } } ================================================ FILE: sea-orm-migration/Cargo.toml ================================================ [workspace] # A separate workspace [package] authors = ["Billy Chan "] categories = ["database"] description = "Migration utility for SeaORM" documentation = "https://docs.rs/sea-orm" edition = "2024" homepage = "https://www.sea-ql.org/SeaORM" keywords = ["async", "orm", "mysql", "postgres", "sqlite"] license = "MIT OR Apache-2.0" name = "sea-orm-migration" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "2.0.0-rc.37" [lib] name = "sea_orm_migration" path = "src/lib.rs" [dependencies] async-trait = { version = "0.1", default-features = false } clap = { version = "4.3", features = ["env", "derive"], optional = true } dotenvy = { version = "0.15", default-features = false, optional = true } sea-orm = { version = "~2.0.0-rc.37", path = "../", features = [ "schema-sync", ] } sea-orm-cli = { version = "~2.0.0-rc.37", path = "../sea-orm-cli", default-features = false, optional = true } sea-schema = { version = "0.17.0-rc", default-features = false, features = [ "discovery", "writer", "probe", ] } tracing = { version = "0.1", default-features = false, features = ["log"] } tracing-subscriber = { version = "0.3.17", default-features = false, features = [ "env-filter", "fmt", ] } [dev-dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros"] } [features] cli = ["clap", "dotenvy", "sea-orm-cli/cli"] default = ["cli"] entity-registry = ["sea-orm/entity-registry"] runtime-async-std = [ "sea-orm/runtime-async-std", "sea-schema/runtime-async-std", "sea-orm-cli?/runtime-async-std", ] runtime-async-std-native-tls = [ "sea-orm/runtime-async-std-native-tls", "sea-schema/runtime-async-std-native-tls", "sea-orm-cli?/runtime-async-std-native-tls", ] runtime-async-std-rustls = [ "sea-orm/runtime-async-std-rustls", "sea-schema/runtime-async-std-rustls", "sea-orm-cli?/runtime-async-std-rustls", ] runtime-tokio = [ "sea-orm/runtime-tokio", "sea-schema/runtime-tokio", "sea-orm-cli?/runtime-tokio", ] runtime-tokio-native-tls = [ "sea-orm/runtime-tokio-native-tls", "sea-schema/runtime-tokio-native-tls", "sea-orm-cli?/runtime-tokio-native-tls", ] runtime-tokio-rustls = [ "sea-orm/runtime-tokio-rustls", "sea-schema/runtime-tokio-rustls", "sea-orm-cli?/runtime-tokio-rustls", ] sqlite-use-returning-for-3_35 = ["sea-orm/sqlite-use-returning-for-3_35"] sqlx-all = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite"] sqlx-dep = [] sqlx-mysql = ["sqlx-dep", "sea-orm/sqlx-mysql", "sea-orm-cli?/sqlx-mysql"] sqlx-postgres = [ "sqlx-dep", "sea-orm/sqlx-postgres", "sea-orm-cli?/sqlx-postgres", ] sqlx-sqlite = ["sqlx-dep", "sea-orm/sqlx-sqlite", "sea-orm-cli?/sqlx-sqlite"] with-bigdecimal = ["sea-orm/with-bigdecimal"] with-chrono = ["sea-orm/with-chrono"] with-ipnetwork = ["sea-orm/with-ipnetwork"] with-json = ["sea-orm/with-json"] with-rust_decimal = ["sea-orm/with-rust_decimal"] with-time = ["sea-orm/with-time"] with-uuid = ["sea-orm/with-uuid"] [patch.crates-io] # sea-query = { path = "../sea-query" } ================================================ FILE: sea-orm-migration/README.md ================================================ # SeaORM CLI Install and Usage: ```sh > cargo install sea-orm-cli > sea-orm-cli help ``` Or: ```sh > cargo install --bin sea > sea help ``` Getting Help: ```sh cargo run -- -h ``` ## Running Entity Generator: ```sh # MySQL (`--database-schema` option is ignored) cargo run -- generate entity -u mysql://sea:sea@localhost/bakery -o out # SQLite (`--database-schema` option is ignored) cargo run -- generate entity -u sqlite://bakery.db -o out # PostgreSQL cargo run -- generate entity -u postgres://sea:sea@localhost/bakery -s public -o out ``` ## Running Migration: - Initialize migration directory ```sh cargo run -- migrate init ``` - Apply all pending migrations ```sh cargo run -- migrate ``` ```sh cargo run -- migrate up ``` - Apply first 10 pending migrations ```sh cargo run -- migrate up -n 10 ``` - Rollback last applied migrations ```sh cargo run -- migrate down ``` - Rollback last 10 applied migrations ```sh cargo run -- migrate down -n 10 ``` - Drop all tables from the database, then reapply all migrations ```sh cargo run -- migrate fresh ``` - Rollback all applied migrations, then reapply all migrations ```sh cargo run -- migrate refresh ``` - Rollback all applied migrations ```sh cargo run -- migrate reset ``` - Check the status of all migrations ```sh cargo run -- migrate status ``` ================================================ FILE: sea-orm-migration/src/cli.rs ================================================ use std::future::Future; use clap::Parser; use dotenvy::dotenv; use std::{error::Error, fmt::Display, process::exit}; use tracing_subscriber::{EnvFilter, prelude::*}; use sea_orm::{ConnectOptions, Database, DbConn, DbErr}; use sea_orm_cli::{MigrateSubcommands, run_migrate_generate, run_migrate_init}; use super::MigratorTraitSelf; const MIGRATION_DIR: &str = "./"; pub async fn run_cli(migrator: M) where M: MigratorTraitSelf, { run_cli_with_connection(migrator, Database::connect).await; } /// Same as [`run_cli`] where you provide the function to create the [`DbConn`]. /// /// This allows configuring the database connection as you see fit. /// E.g. you can change settings in [`ConnectOptions`] or you can load sqlite /// extensions. pub async fn run_cli_with_connection(migrator: M, make_connection: F) where M: MigratorTraitSelf, F: FnOnce(ConnectOptions) -> Fut, Fut: Future>, { dotenv().ok(); let cli = Cli::parse(); let url = cli .database_url .expect("Environment variable 'DATABASE_URL' not set"); let schema = cli.database_schema.unwrap_or_else(|| "public".to_owned()); let connect_options = ConnectOptions::new(url) .set_schema_search_path(schema) .to_owned(); let db = make_connection(connect_options) .await .expect("Fail to acquire database connection"); run_migrate(migrator, &db, cli.command, cli.verbose) .await .unwrap_or_else(handle_error); } pub async fn run_migrate( migrator: M, db: &DbConn, command: Option, verbose: bool, ) -> Result<(), Box> where M: MigratorTraitSelf, { let filter = match verbose { true => "debug", false => "sea_orm_migration=info", }; let filter_layer = EnvFilter::try_new(filter).unwrap(); if verbose { let fmt_layer = tracing_subscriber::fmt::layer(); tracing_subscriber::registry() .with(filter_layer) .with(fmt_layer) .init() } else { let fmt_layer = tracing_subscriber::fmt::layer() .with_target(false) .with_level(false) .without_time(); tracing_subscriber::registry() .with(filter_layer) .with(fmt_layer) .init() }; match command { Some(MigrateSubcommands::Fresh) => migrator.fresh(db).await?, Some(MigrateSubcommands::Refresh) => migrator.refresh(db).await?, Some(MigrateSubcommands::Reset) => migrator.reset(db).await?, Some(MigrateSubcommands::Status) => migrator.status(db).await?, Some(MigrateSubcommands::Up { num }) => migrator.up(db, num).await?, Some(MigrateSubcommands::Down { num }) => migrator.down(db, Some(num)).await?, Some(MigrateSubcommands::Init) => run_migrate_init(MIGRATION_DIR)?, Some(MigrateSubcommands::Generate { migration_name, universal_time: _, local_time, }) => run_migrate_generate(MIGRATION_DIR, &migration_name, !local_time)?, _ => migrator.up(db, None).await?, }; Ok(()) } #[derive(Parser)] #[command(version)] pub struct Cli { #[arg(short = 'v', long, global = true, help = "Show debug messages")] verbose: bool, #[arg( global = true, short = 's', long, env = "DATABASE_SCHEMA", long_help = "Database schema\n \ - For MySQL and SQLite, this argument is ignored.\n \ - For PostgreSQL, this argument is optional with default value 'public'.\n" )] database_schema: Option, #[arg( global = true, short = 'u', long, env = "DATABASE_URL", help = "Database URL" )] database_url: Option, #[command(subcommand)] command: Option, } fn handle_error(error: E) where E: Display, { eprintln!("{error}"); exit(1); } ================================================ FILE: sea-orm-migration/src/connection.rs ================================================ pub use sea_orm::{ DatabaseExecutor as SchemaManagerConnection, IntoDatabaseExecutor as IntoSchemaManagerConnection, }; ================================================ FILE: sea-orm-migration/src/lib.rs ================================================ #[cfg(feature = "cli")] pub mod cli; pub mod connection; pub mod manager; pub mod migrator; pub mod prelude; pub mod schema; pub mod seaql_migrations; pub mod util; pub use connection::*; pub use manager::*; pub use migrator::*; pub use async_trait; pub use sea_orm; pub use sea_orm::DbErr; pub use sea_orm::sea_query; pub trait MigrationName { fn name(&self) -> &str; } /// The migration definition #[async_trait::async_trait] pub trait MigrationTrait: MigrationName + Send + Sync { /// Define actions to perform when applying the migration async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; /// Define actions to perform when rolling back the migration async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { Err(DbErr::Migration("We Don't Do That Here".to_owned())) } /// Control whether this migration runs inside a transaction. /// /// - `None` (default): follow backend convention (Postgres = transaction, MySQL/SQLite = no transaction) /// - `Some(true)`: force wrapping in a transaction on any backend /// - `Some(false)`: disable automatic transaction wrapping (use `manager.begin()` for manual control) fn use_transaction(&self) -> Option { None } } ================================================ FILE: sea-orm-migration/src/manager.rs ================================================ use super::{IntoSchemaManagerConnection, SchemaManagerConnection}; use sea_orm::sea_query::{ ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, SelectStatement, TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement, TableTruncateStatement, extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement}, }; use sea_orm::{ConnectionTrait, DbBackend, DbErr, StatementBuilder, TransactionTrait}; #[allow(unused_imports)] use sea_schema::probe::SchemaProbe; /// Helper struct for writing migration scripts in migration file pub struct SchemaManager<'c> { conn: SchemaManagerConnection<'c>, } impl<'c> SchemaManager<'c> { pub fn new(conn: T) -> Self where T: IntoSchemaManagerConnection<'c>, { Self { conn: conn.into_database_executor(), } } pub async fn execute(&self, stmt: S) -> Result<(), DbErr> where S: StatementBuilder, { self.conn.execute(&stmt).await.map(|_| ()) } #[doc(hidden)] pub async fn exec_stmt(&self, stmt: S) -> Result<(), DbErr> where S: StatementBuilder, { self.conn.execute(&stmt).await.map(|_| ()) } pub fn get_database_backend(&self) -> DbBackend { self.conn.get_database_backend() } pub fn get_connection(&self) -> &SchemaManagerConnection<'c> { &self.conn } } /// Transaction Control impl SchemaManager<'_> { /// Begin a new transaction, returning an owned `SchemaManager` backed by it. /// /// Useful in migrations with `use_transaction() -> Some(false)` for manual /// transaction management (e.g., separating DDL and DML into distinct transactions). pub async fn begin(&self) -> Result, DbErr> { let txn = self.conn.begin().await?; Ok(SchemaManager { conn: SchemaManagerConnection::OwnedTransaction(txn), }) } /// Commit the owned transaction. Only valid on a `SchemaManager` created by [`begin()`](Self::begin). pub async fn commit(self) -> Result<(), DbErr> { match self.conn { SchemaManagerConnection::OwnedTransaction(txn) => txn.commit().await, _ => Err(DbErr::Custom( "Cannot commit: SchemaManager does not own a transaction".into(), )), } } } /// Schema Creation impl SchemaManager<'_> { pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> { self.execute(stmt).await } } /// Schema Mutation impl SchemaManager<'_> { pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> { self.execute(stmt).await } pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> { self.execute(stmt).await } } /// Schema Inspection. impl SchemaManager<'_> { pub async fn has_table(&self, table: T) -> Result where T: AsRef, { has_table(&self.conn, table).await } pub async fn has_column(&self, _table: T, _column: C) -> Result where T: AsRef, C: AsRef, { let _stmt: SelectStatement = match self.conn.get_database_backend() { #[cfg(feature = "sqlx-mysql")] DbBackend::MySql => sea_schema::mysql::MySql.has_column(_table, _column), #[cfg(feature = "sqlx-postgres")] DbBackend::Postgres => sea_schema::postgres::Postgres.has_column(_table, _column), #[cfg(feature = "sqlx-sqlite")] DbBackend::Sqlite => sea_schema::sqlite::Sqlite.has_column(_table, _column), #[allow(unreachable_patterns)] other => { return Err(DbErr::BackendNotSupported { db: other.as_str(), ctx: "has_column", }); } }; #[allow(unreachable_code)] let res = self .conn .query_one(&_stmt) .await? .ok_or_else(|| DbErr::Custom("Failed to check column exists".to_owned()))?; res.try_get("", "has_column") } pub async fn has_index(&self, _table: T, _index: I) -> Result where T: AsRef, I: AsRef, { let _stmt: SelectStatement = match self.conn.get_database_backend() { #[cfg(feature = "sqlx-mysql")] DbBackend::MySql => sea_schema::mysql::MySql.has_index(_table, _index), #[cfg(feature = "sqlx-postgres")] DbBackend::Postgres => sea_schema::postgres::Postgres.has_index(_table, _index), #[cfg(feature = "sqlx-sqlite")] DbBackend::Sqlite => sea_schema::sqlite::Sqlite.has_index(_table, _index), #[allow(unreachable_patterns)] other => { return Err(DbErr::BackendNotSupported { db: other.as_str(), ctx: "has_index", }); } }; #[allow(unreachable_code)] let res = self .conn .query_one(&_stmt) .await? .ok_or_else(|| DbErr::Custom("Failed to check index exists".to_owned()))?; res.try_get("", "has_index") } } pub(crate) async fn has_table(conn: &C, _table: T) -> Result where C: ConnectionTrait, T: AsRef, { let _stmt: SelectStatement = match conn.get_database_backend() { #[cfg(feature = "sqlx-mysql")] DbBackend::MySql => sea_schema::mysql::MySql.has_table(_table), #[cfg(feature = "sqlx-postgres")] DbBackend::Postgres => sea_schema::postgres::Postgres.has_table(_table), #[cfg(feature = "sqlx-sqlite")] DbBackend::Sqlite => sea_schema::sqlite::Sqlite.has_table(_table), #[allow(unreachable_patterns)] other => { return Err(DbErr::BackendNotSupported { db: other.as_str(), ctx: "has_table", }); } }; #[allow(unreachable_code)] let res = conn .query_one(&_stmt) .await? .ok_or_else(|| DbErr::Custom("Failed to check table exists".to_owned()))?; res.try_get("", "has_table") } ================================================ FILE: sea-orm-migration/src/migrator/exec.rs ================================================ use std::collections::HashSet; #[cfg(not(feature = "with-time"))] use std::time::SystemTime; use tracing::info; use super::{Migration, MigrationStatus, queries::*}; use crate::{SchemaManager, seaql_migrations}; use sea_orm::sea_query::{ Alias, Expr, ExprTrait, ForeignKey, IntoIden, Order, Query, Table, extension::postgres::Type, }; use sea_orm::{ ActiveValue, ConnectionTrait, DbBackend, DbErr, DynIden, EntityTrait, FromQueryResult, Iterable, QueryFilter, Schema, Statement, TransactionSession, TransactionTrait, }; pub async fn get_migration_models( db: &C, migration_table_name: DynIden, ) -> Result, DbErr> where C: ConnectionTrait, { let stmt = Query::select() .table_name(migration_table_name) .columns(seaql_migrations::Column::iter().map(IntoIden::into_iden)) .order_by(seaql_migrations::Column::Version, Order::Asc) .take(); db.query_all(&stmt) .await? .into_iter() .map(|row| seaql_migrations::Model::from_query_result(&row, "")) .collect() } pub fn get_migration_with_status( migration_files: Vec, migration_models: Vec, ) -> Result, DbErr> { let mut migration_files = migration_files; let migration_in_db: HashSet = migration_models .into_iter() .map(|model| model.version) .collect(); let migration_in_fs: HashSet = migration_files .iter() .map(|file| file.migration.name().to_string()) .collect(); let pending_migrations = &migration_in_fs - &migration_in_db; for migration_file in migration_files.iter_mut() { if !pending_migrations.contains(migration_file.migration.name()) { migration_file.status = MigrationStatus::Applied; } } let missing_migrations_in_fs = &migration_in_db - &migration_in_fs; let errors: Vec = missing_migrations_in_fs .iter() .map(|missing_migration| { format!("Migration file of version '{missing_migration}' is missing, this migration has been applied but its file is missing") }).collect(); if !errors.is_empty() { Err(DbErr::Custom(errors.join("\n"))) } else { Ok(migration_files) } } pub async fn install(db: &C, migration_table_name: DynIden) -> Result<(), DbErr> where C: ConnectionTrait, { let builder = db.get_database_backend(); let schema = Schema::new(builder); let mut stmt = schema .create_table_from_entity(seaql_migrations::Entity) .table_name(migration_table_name); stmt.if_not_exists(); db.execute(&stmt).await?; Ok(()) } pub async fn uninstall( manager: &SchemaManager<'_>, migration_table_name: DynIden, ) -> Result<(), DbErr> { let mut stmt = Table::drop(); stmt.table(migration_table_name).if_exists().cascade(); manager.drop_table(stmt).await?; Ok(()) } pub async fn drop_everything(db: &C) -> Result<(), DbErr> { if db.get_database_backend() == DbBackend::Postgres { let transaction = db.begin().await?; drop_everything_impl(&transaction).await?; transaction.commit().await } else { drop_everything_impl(db).await } } async fn drop_everything_impl(db: &C) -> Result<(), DbErr> { let db_backend = db.get_database_backend(); // Temporarily disable the foreign key check if db_backend == DbBackend::Sqlite { info!("Disabling foreign key check"); db.execute_raw(Statement::from_string( db_backend, "PRAGMA foreign_keys = OFF".to_owned(), )) .await?; info!("Foreign key check disabled"); } // Drop all foreign keys if db_backend == DbBackend::MySql { info!("Dropping all foreign keys"); let stmt = query_mysql_foreign_keys(db); let rows = db.query_all(&stmt).await?; for row in rows.into_iter() { let constraint_name: String = row.try_get("", "CONSTRAINT_NAME")?; let table_name: String = row.try_get("", "TABLE_NAME")?; info!( "Dropping foreign key '{}' from table '{}'", constraint_name, table_name ); let mut stmt = ForeignKey::drop(); stmt.table(Alias::new(table_name.as_str())) .name(constraint_name.as_str()); db.execute(&stmt).await?; info!("Foreign key '{}' has been dropped", constraint_name); } info!("All foreign keys dropped"); } // Drop all tables let stmt = query_tables(db)?; let rows = db.query_all(&stmt).await?; for row in rows.into_iter() { let table_name: String = row.try_get("", "table_name")?; info!("Dropping table '{}'", table_name); let mut stmt = Table::drop(); stmt.table(Alias::new(table_name.as_str())) .if_exists() .cascade(); db.execute(&stmt).await?; info!("Table '{}' has been dropped", table_name); } // Drop all types if db_backend == DbBackend::Postgres { info!("Dropping all types"); let stmt = query_pg_types(db); let rows = db.query_all(&stmt).await?; for row in rows { let type_name: String = row.try_get("", "typname")?; info!("Dropping type '{}'", type_name); let mut stmt = Type::drop(); stmt.name(Alias::new(&type_name)); db.execute(&stmt).await?; info!("Type '{}' has been dropped", type_name); } } // Restore the foreign key check if db_backend == DbBackend::Sqlite { info!("Restoring foreign key check"); db.execute_raw(Statement::from_string( db_backend, "PRAGMA foreign_keys = ON".to_owned(), )) .await?; info!("Foreign key check restored"); } Ok(()) } fn should_use_transaction(migration: &dyn crate::MigrationTrait, backend: DbBackend) -> bool { match migration.use_transaction() { Some(v) => v, None => backend == DbBackend::Postgres, } } async fn insert_migration_record( db: &C, name: &str, migration_table_name: DynIden, ) -> Result<(), DbErr> { #[cfg(not(feature = "with-time"))] let applied_at = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .expect("SystemTime before UNIX EPOCH!") .as_secs() as i64; #[cfg(feature = "with-time")] let applied_at = sea_orm::prelude::TimeDateTimeWithTimeZone::now_utc().unix_timestamp(); seaql_migrations::Entity::insert(seaql_migrations::ActiveModel { version: ActiveValue::Set(name.to_owned()), applied_at: ActiveValue::Set(applied_at), }) .table_name(migration_table_name) .exec(db) .await?; Ok(()) } async fn delete_migration_record( db: &C, name: &str, migration_table_name: DynIden, ) -> Result<(), DbErr> { seaql_migrations::Entity::delete_many() .filter(Expr::col(seaql_migrations::Column::Version).eq(name)) .table_name(migration_table_name) .exec(db) .await?; Ok(()) } pub async fn exec_up_with( manager: &SchemaManager<'_>, mut steps: Option, pending_migrations: Vec, migration_table_name: DynIden, ) -> Result<(), DbErr> { let db = manager.get_connection(); if let Some(steps) = steps { info!("Applying {} pending migrations", steps); } else { info!("Applying all pending migrations"); } if pending_migrations.is_empty() { info!("No pending migrations"); } for Migration { migration, .. } in pending_migrations { if let Some(steps) = steps.as_mut() { if steps == &0 { break; } *steps -= 1; } let use_txn = should_use_transaction(migration.as_ref(), db.get_database_backend()); info!("Applying migration '{}'", migration.name()); if use_txn { let transaction = db.begin().await?; let txn_manager = SchemaManager::new(&transaction); migration.up(&txn_manager).await?; info!("Migration '{}' has been applied", migration.name()); insert_migration_record(&transaction, migration.name(), migration_table_name.clone()) .await?; transaction.commit().await?; } else { migration.up(manager).await?; info!("Migration '{}' has been applied", migration.name()); insert_migration_record(db, migration.name(), migration_table_name.clone()).await?; } } Ok(()) } pub async fn exec_down_with( manager: &SchemaManager<'_>, mut steps: Option, applied_migrations: Vec, migration_table_name: DynIden, ) -> Result<(), DbErr> { let db = manager.get_connection(); if let Some(steps) = steps { info!("Rolling back {} applied migrations", steps); } else { info!("Rolling back all applied migrations"); } if applied_migrations.is_empty() { info!("No applied migrations"); } for Migration { migration, .. } in applied_migrations.into_iter().rev() { if let Some(steps) = steps.as_mut() { if steps == &0 { break; } *steps -= 1; } let use_txn = should_use_transaction(migration.as_ref(), db.get_database_backend()); info!("Rolling back migration '{}'", migration.name()); if use_txn { let transaction = db.begin().await?; let txn_manager = SchemaManager::new(&transaction); migration.down(&txn_manager).await?; info!("Migration '{}' has been rolled back", migration.name()); delete_migration_record(&transaction, migration.name(), migration_table_name.clone()) .await?; transaction.commit().await?; } else { migration.down(manager).await?; info!("Migration '{}' has been rolled back", migration.name()); delete_migration_record(db, migration.name(), migration_table_name.clone()).await?; } } Ok(()) } ================================================ FILE: sea-orm-migration/src/migrator/queries.rs ================================================ use sea_orm::sea_query::{self, Expr, ExprTrait, Query, SelectStatement, SimpleExpr}; use sea_orm::{ ActiveModelTrait, Condition, ConnectionTrait, DbErr, DeriveIden, DynIden, EntityTrait, }; #[allow(unused_imports)] use sea_schema::probe::SchemaProbe; pub fn query_tables(db: &C) -> Result where C: ConnectionTrait, { #[allow(unused_imports)] use sea_orm::DbBackend; match db.get_database_backend() { #[cfg(feature = "sqlx-mysql")] DbBackend::MySql => Ok(sea_schema::mysql::MySql.query_tables()), #[cfg(feature = "sqlx-postgres")] DbBackend::Postgres => Ok(sea_schema::postgres::Postgres.query_tables()), #[cfg(feature = "sqlx-sqlite")] DbBackend::Sqlite => Ok(sea_schema::sqlite::Sqlite.query_tables()), #[allow(unreachable_patterns)] other => Err(DbErr::BackendNotSupported { db: other.as_str(), ctx: "query_tables", }), } } // this function is only called after checking db backend, the panic is unreachable pub fn get_current_schema(db: &C) -> SimpleExpr where C: ConnectionTrait, { #[allow(unused_imports)] use sea_orm::DbBackend; match db.get_database_backend() { #[cfg(feature = "sqlx-mysql")] DbBackend::MySql => sea_schema::mysql::MySql::get_current_schema(), #[cfg(feature = "sqlx-postgres")] DbBackend::Postgres => sea_schema::postgres::Postgres::get_current_schema(), #[cfg(feature = "sqlx-sqlite")] DbBackend::Sqlite => sea_schema::sqlite::Sqlite::get_current_schema(), #[allow(unreachable_patterns)] other => panic!("{other:?} feature is off"), } } #[derive(DeriveIden)] enum InformationSchema { #[sea_orm(iden = "information_schema")] Schema, #[sea_orm(iden = "TABLE_NAME")] TableName, #[sea_orm(iden = "CONSTRAINT_NAME")] ConstraintName, TableConstraints, TableSchema, ConstraintType, } pub fn query_mysql_foreign_keys(db: &C) -> SelectStatement where C: ConnectionTrait, { let mut stmt = Query::select(); stmt.columns([ InformationSchema::TableName, InformationSchema::ConstraintName, ]) .from(( InformationSchema::Schema, InformationSchema::TableConstraints, )) .cond_where( Condition::all() .add(get_current_schema(db).equals(( InformationSchema::TableConstraints, InformationSchema::TableSchema, ))) .add( Expr::col(( InformationSchema::TableConstraints, InformationSchema::ConstraintType, )) .eq("FOREIGN KEY"), ), ); stmt } #[derive(DeriveIden)] enum PgType { Table, Oid, Typname, Typnamespace, Typelem, } #[derive(DeriveIden)] enum PgDepend { Table, Objid, Deptype, Refclassid, } #[derive(DeriveIden)] enum PgNamespace { Table, Oid, Nspname, } pub fn query_pg_types(db: &C) -> SelectStatement where C: ConnectionTrait, { Query::select() .column(PgType::Typname) .from(PgType::Table) .left_join( PgNamespace::Table, Expr::col((PgNamespace::Table, PgNamespace::Oid)) .equals((PgType::Table, PgType::Typnamespace)), ) .left_join( PgDepend::Table, Expr::col((PgDepend::Table, PgDepend::Objid)) .equals((PgType::Table, PgType::Oid)) .and( Expr::col((PgDepend::Table, PgDepend::Refclassid)) .eq(Expr::cust("'pg_extension'::regclass::oid")), ) .and(Expr::col((PgDepend::Table, PgDepend::Deptype)).eq(Expr::cust("'e'"))), ) .and_where(get_current_schema(db).equals((PgNamespace::Table, PgNamespace::Nspname))) .and_where(Expr::col((PgType::Table, PgType::Typelem)).eq(0)) .and_where(Expr::col((PgDepend::Table, PgDepend::Objid)).is_null()) .take() } pub trait QueryTable { type Statement; fn table_name(self, table_name: DynIden) -> Self::Statement; } impl QueryTable for SelectStatement { type Statement = SelectStatement; fn table_name(mut self, table_name: DynIden) -> SelectStatement { self.from(table_name); self } } impl QueryTable for sea_query::TableCreateStatement { type Statement = sea_query::TableCreateStatement; fn table_name(mut self, table_name: DynIden) -> sea_query::TableCreateStatement { self.table(table_name); self } } impl QueryTable for sea_orm::Insert where A: ActiveModelTrait, { type Statement = sea_orm::Insert; fn table_name(mut self, table_name: DynIden) -> sea_orm::Insert { sea_orm::QueryTrait::query(&mut self).into_table(table_name); self } } impl QueryTable for sea_orm::DeleteMany where E: EntityTrait, { type Statement = sea_orm::DeleteMany; fn table_name(mut self, table_name: DynIden) -> sea_orm::DeleteMany { sea_orm::QueryTrait::query(&mut self).from_table(table_name); self } } ================================================ FILE: sea-orm-migration/src/migrator/with_self.rs ================================================ use super::{Migration, MigrationStatus, exec::*}; use crate::{IntoSchemaManagerConnection, MigrationTrait, SchemaManager, seaql_migrations}; use sea_orm::sea_query::IntoIden; use sea_orm::{ConnectionTrait, DbErr, DynIden}; use tracing::info; /// Performing migrations on a database #[async_trait::async_trait] pub trait MigratorTraitSelf: Sized + Send + Sync { /// Vector of migrations in time sequence fn migrations(&self) -> Vec>; /// Name of the migration table, it is `seaql_migrations` by default fn migration_table_name(&self) -> DynIden { seaql_migrations::Entity.into_iden() } /// Get list of migrations wrapped in `Migration` struct fn get_migration_files(&self) -> Vec { self.migrations() .into_iter() .map(|migration| Migration { migration, status: MigrationStatus::Pending, }) .collect() } /// Get list of applied migrations from database async fn get_migration_models(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.install(db).await?; get_migration_models(db, self.migration_table_name()).await } /// Get list of migrations with status async fn get_migration_with_status(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.install(db).await?; get_migration_with_status( self.get_migration_files(), self.get_migration_models(db).await?, ) } /// Get list of pending migrations async fn get_pending_migrations(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.install(db).await?; Ok(self .get_migration_with_status(db) .await? .into_iter() .filter(|file| file.status == MigrationStatus::Pending) .collect()) } /// Get list of applied migrations async fn get_applied_migrations(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.install(db).await?; Ok(self .get_migration_with_status(db) .await? .into_iter() .filter(|file| file.status == MigrationStatus::Applied) .collect()) } /// Create migration table `seaql_migrations` in the database async fn install(&self, db: &C) -> Result<(), DbErr> where C: ConnectionTrait, { install(db, self.migration_table_name()).await } /// Check the status of all migrations async fn status(&self, db: &C) -> Result<(), DbErr> where C: ConnectionTrait, { self.install(db).await?; info!("Checking migration status"); for Migration { migration, status } in self.get_migration_with_status(db).await? { info!("Migration '{}'... {}", migration.name(), status); } Ok(()) } /// Drop all tables from the database, then reapply all migrations async fn fresh<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_fresh(self, &manager).await } /// Rollback all applied migrations, then reapply all migrations async fn refresh<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_down(self, &manager, None).await?; exec_up(self, &manager, None).await } /// Rollback all applied migrations async fn reset<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_down(self, &manager, None).await?; uninstall(&manager, self.migration_table_name()).await } /// Uninstall migration tracking table only (non-destructive) /// This will drop the `seaql_migrations` table but won't rollback other schema changes. async fn uninstall<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); uninstall(&manager, self.migration_table_name()).await } /// Apply pending migrations async fn up<'c, C>(&self, db: C, steps: Option) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_up(self, &manager, steps).await } /// Rollback applied migrations async fn down<'c, C>(&self, db: C, steps: Option) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_down(self, &manager, steps).await } } #[async_trait::async_trait] impl MigratorTraitSelf for M where M: super::MigratorTrait + Sized + Send + Sync, { fn migrations(&self) -> Vec> { M::migrations() } fn migration_table_name(&self) -> DynIden { M::migration_table_name() } fn get_migration_files(&self) -> Vec { M::get_migration_files() } async fn get_migration_models(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { M::get_migration_models(db).await } async fn get_migration_with_status(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { M::get_migration_with_status(db).await } async fn get_pending_migrations(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { M::get_pending_migrations(db).await } async fn get_applied_migrations(&self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { M::get_applied_migrations(db).await } async fn install(&self, db: &C) -> Result<(), DbErr> where C: ConnectionTrait, { M::install(db).await } /// Check the status of all migrations async fn status(&self, db: &C) -> Result<(), DbErr> where C: ConnectionTrait, { M::status(db).await } async fn fresh<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { M::fresh(db).await } async fn refresh<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { M::refresh(db).await } async fn reset<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { M::reset(db).await } async fn uninstall<'c, C>(&self, db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { M::uninstall(db).await } async fn up<'c, C>(&self, db: C, steps: Option) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { M::up(db, steps).await } async fn down<'c, C>(&self, db: C, steps: Option) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { M::down(db, steps).await } } async fn exec_fresh(migrator: &M, manager: &SchemaManager<'_>) -> Result<(), DbErr> where M: MigratorTraitSelf, { let db = manager.get_connection(); migrator.install(db).await?; drop_everything(db).await?; exec_up(migrator, manager, None).await } async fn exec_up( migrator: &M, manager: &SchemaManager<'_>, steps: Option, ) -> Result<(), DbErr> where M: MigratorTraitSelf, { let db = manager.get_connection(); migrator.install(db).await?; exec_up_with( manager, steps, migrator.get_pending_migrations(db).await?, migrator.migration_table_name(), ) .await } async fn exec_down( migrator: &M, manager: &SchemaManager<'_>, steps: Option, ) -> Result<(), DbErr> where M: MigratorTraitSelf, { let db = manager.get_connection(); migrator.install(db).await?; exec_down_with( manager, steps, migrator.get_applied_migrations(db).await?, migrator.migration_table_name(), ) .await } ================================================ FILE: sea-orm-migration/src/migrator.rs ================================================ mod queries; mod exec; use exec::*; mod with_self; pub use with_self::*; use std::fmt::Display; use tracing::info; use super::{IntoSchemaManagerConnection, MigrationTrait, SchemaManager, seaql_migrations}; use sea_orm::sea_query::IntoIden; use sea_orm::{ConnectionTrait, DbErr, DynIden}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Status of migration pub enum MigrationStatus { /// Not yet applied Pending, /// Applied Applied, } impl Display for MigrationStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let status = match self { MigrationStatus::Pending => "Pending", MigrationStatus::Applied => "Applied", }; write!(f, "{status}") } } pub struct Migration { migration: Box, status: MigrationStatus, } impl Migration { /// Get migration name from MigrationName trait implementation pub fn name(&self) -> &str { self.migration.name() } /// Get migration status pub fn status(&self) -> MigrationStatus { self.status } } /// Performing migrations on a database #[async_trait::async_trait] pub trait MigratorTrait: Send { /// Vector of migrations in time sequence fn migrations() -> Vec>; /// Name of the migration table, it is `seaql_migrations` by default fn migration_table_name() -> DynIden { seaql_migrations::Entity.into_iden() } /// Get list of migrations wrapped in `Migration` struct fn get_migration_files() -> Vec { Self::migrations() .into_iter() .map(|migration| Migration { migration, status: MigrationStatus::Pending, }) .collect() } /// Get list of applied migrations from database async fn get_migration_models(db: &C) -> Result, DbErr> where C: ConnectionTrait, { Self::install(db).await?; get_migration_models(db, Self::migration_table_name()).await } /// Get list of migrations with status async fn get_migration_with_status(db: &C) -> Result, DbErr> where C: ConnectionTrait, { Self::install(db).await?; get_migration_with_status( Self::get_migration_files(), Self::get_migration_models(db).await?, ) } /// Get list of pending migrations async fn get_pending_migrations(db: &C) -> Result, DbErr> where C: ConnectionTrait, { Self::install(db).await?; Ok(Self::get_migration_with_status(db) .await? .into_iter() .filter(|file| file.status == MigrationStatus::Pending) .collect()) } /// Get list of applied migrations async fn get_applied_migrations(db: &C) -> Result, DbErr> where C: ConnectionTrait, { Self::install(db).await?; Ok(Self::get_migration_with_status(db) .await? .into_iter() .filter(|file| file.status == MigrationStatus::Applied) .collect()) } /// Create migration table `seaql_migrations` in the database async fn install(db: &C) -> Result<(), DbErr> where C: ConnectionTrait, { install(db, Self::migration_table_name()).await } /// Check the status of all migrations async fn status(db: &C) -> Result<(), DbErr> where C: ConnectionTrait, { Self::install(db).await?; info!("Checking migration status"); for Migration { migration, status } in Self::get_migration_with_status(db).await? { info!("Migration '{}'... {}", migration.name(), status); } Ok(()) } /// Drop all tables from the database, then reapply all migrations async fn fresh<'c, C>(db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_fresh::(&manager).await } /// Rollback all applied migrations, then reapply all migrations async fn refresh<'c, C>(db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_down::(&manager, None).await?; exec_up::(&manager, None).await } /// Rollback all applied migrations async fn reset<'c, C>(db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_down::(&manager, None).await?; uninstall(&manager, Self::migration_table_name()).await } /// Uninstall migration tracking table only (non-destructive) /// This will drop the `seaql_migrations` table but won't rollback other schema changes. async fn uninstall<'c, C>(db: C) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); uninstall(&manager, Self::migration_table_name()).await } /// Apply pending migrations async fn up<'c, C>(db: C, steps: Option) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_up::(&manager, steps).await } /// Rollback applied migrations async fn down<'c, C>(db: C, steps: Option) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, { let db = db.into_database_executor(); let manager = SchemaManager::new(db); exec_down::(&manager, steps).await } } async fn exec_fresh(manager: &SchemaManager<'_>) -> Result<(), DbErr> where M: MigratorTrait + ?Sized, { let db = manager.get_connection(); M::install(db).await?; drop_everything(db).await?; exec_up::(manager, None).await } async fn exec_up(manager: &SchemaManager<'_>, steps: Option) -> Result<(), DbErr> where M: MigratorTrait + ?Sized, { let db = manager.get_connection(); M::install(db).await?; exec_up_with( manager, steps, M::get_pending_migrations(db).await?, M::migration_table_name(), ) .await } async fn exec_down(manager: &SchemaManager<'_>, steps: Option) -> Result<(), DbErr> where M: MigratorTrait + ?Sized, { let db = manager.get_connection(); M::install(db).await?; exec_down_with( manager, steps, M::get_applied_migrations(db).await?, M::migration_table_name(), ) .await } ================================================ FILE: sea-orm-migration/src/prelude.rs ================================================ #[cfg(feature = "cli")] pub use crate::cli; pub use crate::{ IntoSchemaManagerConnection, MigrationName, MigrationTrait, MigratorTrait, SchemaManager, SchemaManagerConnection, }; pub use async_trait; pub use sea_orm::{ self, ConnectionTrait, DbErr, DeriveIden, DeriveMigrationName, sea_query::{self, *}, }; ================================================ FILE: sea-orm-migration/src/schema.rs ================================================ //! > Adapted from https://github.com/loco-rs/loco/blob/master/src/schema.rs //! //! # Database Table Schema Helpers //! //! This module defines functions and helpers for creating database table //! schemas using the `sea-orm` and `sea-query` libraries. //! //! # Example //! //! The following example shows how the user migration file should be and using //! the schema helpers to create the Db fields. //! //! ```rust //! use sea_orm_migration::{prelude::*, schema::*}; //! //! #[derive(DeriveMigrationName)] //! pub struct Migration; //! //! #[async_trait::async_trait] //! impl MigrationTrait for Migration { //! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { //! let table = table_auto("users") //! .col(pk_auto("id")) //! .col(uuid("pid")) //! .col(string_uniq("email")) //! .col(string("password")) //! .col(string("name")) //! .col(string_null("reset_token")) //! .col(timestamp_null("reset_sent_at")) //! .to_owned(); //! manager.create_table(table).await?; //! Ok(()) //! } //! //! async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { //! manager //! .drop_table(Table::drop().table("users").to_owned()) //! .await //! } //! } //! ``` use crate::{prelude::Iden, sea_query}; use sea_orm::sea_query::{ Alias, ColumnDef, ColumnType, Expr, IntoIden, PgInterval, Table, TableCreateStatement, }; #[derive(Iden)] enum GeneralIds { CreatedAt, UpdatedAt, } /// Create a table with `created_at` and `updated_at` added by default pub fn table_auto(name: T) -> TableCreateStatement { timestamps(Table::create().table(name).if_not_exists().take()) } /// Create a primary key column with auto-increment pub fn pk_auto(name: T) -> ColumnDef { integer(name).auto_increment().primary_key().take() } /// Create a primary key column of big integer with auto-increment pub fn big_pk_auto(name: T) -> ColumnDef { big_integer(name).auto_increment().primary_key().take() } /// Create a UUID primary key pub fn pk_uuid(name: T) -> ColumnDef { uuid(name).primary_key().take() } pub fn char_len(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).char_len(length).not_null().take() } pub fn char_len_null(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).char_len(length).null().take() } pub fn char_len_uniq(col: T, length: u32) -> ColumnDef { char_len(col, length).unique_key().take() } pub fn char(col: T) -> ColumnDef { ColumnDef::new(col).char().not_null().take() } pub fn char_null(col: T) -> ColumnDef { ColumnDef::new(col).char().null().take() } pub fn char_uniq(col: T) -> ColumnDef { char(col).unique_key().take() } pub fn string_len(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).string_len(length).not_null().take() } pub fn string_len_null(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).string_len(length).null().take() } pub fn string_len_uniq(col: T, length: u32) -> ColumnDef { string_len(col, length).unique_key().take() } pub fn string(col: T) -> ColumnDef { ColumnDef::new(col).string().not_null().take() } pub fn string_null(col: T) -> ColumnDef { ColumnDef::new(col).string().null().take() } pub fn string_uniq(col: T) -> ColumnDef { string(col).unique_key().take() } pub fn text(col: T) -> ColumnDef { ColumnDef::new(col).text().not_null().take() } pub fn text_null(col: T) -> ColumnDef { ColumnDef::new(col).text().null().take() } pub fn text_uniq(col: T) -> ColumnDef { text(col).unique_key().take() } pub fn tiny_integer(col: T) -> ColumnDef { ColumnDef::new(col).tiny_integer().not_null().take() } pub fn tiny_integer_null(col: T) -> ColumnDef { ColumnDef::new(col).tiny_integer().null().take() } pub fn tiny_integer_uniq(col: T) -> ColumnDef { tiny_integer(col).unique_key().take() } pub fn small_integer(col: T) -> ColumnDef { ColumnDef::new(col).small_integer().not_null().take() } pub fn small_integer_null(col: T) -> ColumnDef { ColumnDef::new(col).small_integer().null().take() } pub fn small_integer_uniq(col: T) -> ColumnDef { small_integer(col).unique_key().take() } pub fn integer(col: T) -> ColumnDef { ColumnDef::new(col).integer().not_null().take() } pub fn integer_null(col: T) -> ColumnDef { ColumnDef::new(col).integer().null().take() } pub fn integer_uniq(col: T) -> ColumnDef { integer(col).unique_key().take() } pub fn big_integer(col: T) -> ColumnDef { ColumnDef::new(col).big_integer().not_null().take() } pub fn big_integer_null(col: T) -> ColumnDef { ColumnDef::new(col).big_integer().null().take() } pub fn big_integer_uniq(col: T) -> ColumnDef { big_integer(col).unique_key().take() } pub fn tiny_unsigned(col: T) -> ColumnDef { ColumnDef::new(col).tiny_unsigned().not_null().take() } pub fn tiny_unsigned_null(col: T) -> ColumnDef { ColumnDef::new(col).tiny_unsigned().null().take() } pub fn tiny_unsigned_uniq(col: T) -> ColumnDef { tiny_unsigned(col).unique_key().take() } pub fn small_unsigned(col: T) -> ColumnDef { ColumnDef::new(col).small_unsigned().not_null().take() } pub fn small_unsigned_null(col: T) -> ColumnDef { ColumnDef::new(col).small_unsigned().null().take() } pub fn small_unsigned_uniq(col: T) -> ColumnDef { small_unsigned(col).unique_key().take() } pub fn unsigned(col: T) -> ColumnDef { ColumnDef::new(col).unsigned().not_null().take() } pub fn unsigned_null(col: T) -> ColumnDef { ColumnDef::new(col).unsigned().null().take() } pub fn unsigned_uniq(col: T) -> ColumnDef { unsigned(col).unique_key().take() } pub fn big_unsigned(col: T) -> ColumnDef { ColumnDef::new(col).big_unsigned().not_null().take() } pub fn big_unsigned_null(col: T) -> ColumnDef { ColumnDef::new(col).big_unsigned().null().take() } pub fn big_unsigned_uniq(col: T) -> ColumnDef { big_unsigned(col).unique_key().take() } pub fn float(col: T) -> ColumnDef { ColumnDef::new(col).float().not_null().take() } pub fn float_null(col: T) -> ColumnDef { ColumnDef::new(col).float().null().take() } pub fn float_uniq(col: T) -> ColumnDef { float(col).unique_key().take() } pub fn double(col: T) -> ColumnDef { ColumnDef::new(col).double().not_null().take() } pub fn double_null(col: T) -> ColumnDef { ColumnDef::new(col).double().null().take() } pub fn double_uniq(col: T) -> ColumnDef { double(col).unique_key().take() } pub fn decimal_len(col: T, precision: u32, scale: u32) -> ColumnDef { ColumnDef::new(col) .decimal_len(precision, scale) .not_null() .take() } pub fn decimal_len_null(col: T, precision: u32, scale: u32) -> ColumnDef { ColumnDef::new(col) .decimal_len(precision, scale) .null() .take() } pub fn decimal_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef { decimal_len(col, precision, scale).unique_key().take() } pub fn decimal(col: T) -> ColumnDef { ColumnDef::new(col).decimal().not_null().take() } pub fn decimal_null(col: T) -> ColumnDef { ColumnDef::new(col).decimal().null().take() } pub fn decimal_uniq(col: T) -> ColumnDef { decimal(col).unique_key().take() } pub fn date_time(col: T) -> ColumnDef { ColumnDef::new(col).date_time().not_null().take() } pub fn date_time_null(col: T) -> ColumnDef { ColumnDef::new(col).date_time().null().take() } pub fn date_time_uniq(col: T) -> ColumnDef { date_time(col).unique_key().take() } pub fn interval( col: T, fields: Option, precision: Option, ) -> ColumnDef { ColumnDef::new(col) .interval(fields, precision) .not_null() .take() } pub fn interval_null( col: T, fields: Option, precision: Option, ) -> ColumnDef { ColumnDef::new(col) .interval(fields, precision) .null() .take() } pub fn interval_uniq( col: T, fields: Option, precision: Option, ) -> ColumnDef { interval(col, fields, precision).unique_key().take() } pub fn timestamp(col: T) -> ColumnDef { ColumnDef::new(col).timestamp().not_null().take() } pub fn timestamp_null(col: T) -> ColumnDef { ColumnDef::new(col).timestamp().null().take() } pub fn timestamp_uniq(col: T) -> ColumnDef { timestamp(col).unique_key().take() } pub fn timestamp_with_time_zone(col: T) -> ColumnDef { ColumnDef::new(col) .timestamp_with_time_zone() .not_null() .take() } pub fn timestamp_with_time_zone_null(col: T) -> ColumnDef { ColumnDef::new(col).timestamp_with_time_zone().null().take() } pub fn timestamp_with_time_zone_uniq(col: T) -> ColumnDef { timestamp_with_time_zone(col).unique_key().take() } pub fn time(col: T) -> ColumnDef { ColumnDef::new(col).time().not_null().take() } pub fn time_null(col: T) -> ColumnDef { ColumnDef::new(col).time().null().take() } pub fn time_uniq(col: T) -> ColumnDef { time(col).unique_key().take() } pub fn date(col: T) -> ColumnDef { ColumnDef::new(col).date().not_null().take() } pub fn date_null(col: T) -> ColumnDef { ColumnDef::new(col).date().null().take() } pub fn date_uniq(col: T) -> ColumnDef { date(col).unique_key().take() } pub fn year(col: T) -> ColumnDef { ColumnDef::new(col).year().not_null().take() } pub fn year_null(col: T) -> ColumnDef { ColumnDef::new(col).year().null().take() } pub fn year_uniq(col: T) -> ColumnDef { year(col).unique_key().take() } pub fn binary_len(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).binary_len(length).not_null().take() } pub fn binary_len_null(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).binary_len(length).null().take() } pub fn binary_len_uniq(col: T, length: u32) -> ColumnDef { binary_len(col, length).unique_key().take() } pub fn binary(col: T) -> ColumnDef { ColumnDef::new(col).binary().not_null().take() } pub fn binary_null(col: T) -> ColumnDef { ColumnDef::new(col).binary().null().take() } pub fn binary_uniq(col: T) -> ColumnDef { binary(col).unique_key().take() } pub fn var_binary(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).var_binary(length).not_null().take() } pub fn var_binary_null(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).var_binary(length).null().take() } pub fn var_binary_uniq(col: T, length: u32) -> ColumnDef { var_binary(col, length).unique_key().take() } pub fn bit(col: T, length: Option) -> ColumnDef { ColumnDef::new(col).bit(length).not_null().take() } pub fn bit_null(col: T, length: Option) -> ColumnDef { ColumnDef::new(col).bit(length).null().take() } pub fn bit_uniq(col: T, length: Option) -> ColumnDef { bit(col, length).unique_key().take() } pub fn varbit(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).varbit(length).not_null().take() } pub fn varbit_null(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).varbit(length).null().take() } pub fn varbit_uniq(col: T, length: u32) -> ColumnDef { varbit(col, length).unique_key().take() } pub fn blob(col: T) -> ColumnDef { ColumnDef::new(col).blob().not_null().take() } pub fn blob_null(col: T) -> ColumnDef { ColumnDef::new(col).blob().null().take() } pub fn blob_uniq(col: T) -> ColumnDef { blob(col).unique_key().take() } pub fn boolean(col: T) -> ColumnDef { ColumnDef::new(col).boolean().not_null().take() } pub fn boolean_null(col: T) -> ColumnDef { ColumnDef::new(col).boolean().null().take() } pub fn boolean_uniq(col: T) -> ColumnDef { boolean(col).unique_key().take() } pub fn money_len(col: T, precision: u32, scale: u32) -> ColumnDef { ColumnDef::new(col) .money_len(precision, scale) .not_null() .take() } pub fn money_len_null(col: T, precision: u32, scale: u32) -> ColumnDef { ColumnDef::new(col) .money_len(precision, scale) .null() .take() } pub fn money_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef { money_len(col, precision, scale).unique_key().take() } pub fn money(col: T) -> ColumnDef { ColumnDef::new(col).money().not_null().take() } pub fn money_null(col: T) -> ColumnDef { ColumnDef::new(col).money().null().take() } pub fn money_uniq(col: T) -> ColumnDef { money(col).unique_key().take() } pub fn json(col: T) -> ColumnDef { ColumnDef::new(col).json().not_null().take() } pub fn json_null(col: T) -> ColumnDef { ColumnDef::new(col).json().null().take() } pub fn json_uniq(col: T) -> ColumnDef { json(col).unique_key().take() } pub fn json_binary(col: T) -> ColumnDef { ColumnDef::new(col).json_binary().not_null().take() } pub fn json_binary_null(col: T) -> ColumnDef { ColumnDef::new(col).json_binary().null().take() } pub fn json_binary_uniq(col: T) -> ColumnDef { json_binary(col).unique_key().take() } pub fn uuid(col: T) -> ColumnDef { ColumnDef::new(col).uuid().not_null().take() } pub fn uuid_null(col: T) -> ColumnDef { ColumnDef::new(col).uuid().null().take() } pub fn uuid_uniq(col: T) -> ColumnDef { uuid(col).unique_key().take() } pub fn custom(col: T, name: N) -> ColumnDef { ColumnDef::new(col).custom(name).not_null().take() } pub fn custom_null(col: T, name: N) -> ColumnDef { ColumnDef::new(col).custom(name).null().take() } pub fn enumeration(col: T, name: N, variants: V) -> ColumnDef where T: IntoIden, N: IntoIden, S: IntoIden, V: IntoIterator, { ColumnDef::new(col) .enumeration(name, variants) .not_null() .take() } pub fn enumeration_null(col: T, name: N, variants: V) -> ColumnDef where T: IntoIden, N: IntoIden, S: IntoIden, V: IntoIterator, { ColumnDef::new(col) .enumeration(name, variants) .null() .take() } pub fn enumeration_uniq(col: T, name: N, variants: V) -> ColumnDef where T: IntoIden, N: IntoIden, S: IntoIden, V: IntoIterator, { enumeration(col, name, variants).unique_key().take() } pub fn array(col: T, elem_type: ColumnType) -> ColumnDef { ColumnDef::new(col).array(elem_type).not_null().take() } pub fn array_null(col: T, elem_type: ColumnType) -> ColumnDef { ColumnDef::new(col).array(elem_type).null().take() } pub fn array_uniq(col: T, elem_type: ColumnType) -> ColumnDef { array(col, elem_type).unique_key().take() } /// Add timestamp columns (`CreatedAt` and `UpdatedAt`) to an existing table. pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement { let mut t = t; t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp())) .col(timestamp(GeneralIds::UpdatedAt).default(Expr::current_timestamp())) .take() } /// Create an Alias. pub fn name>(name: T) -> Alias { Alias::new(name) } ================================================ FILE: sea-orm-migration/src/seaql_migrations.rs ================================================ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] // One should override the name of migration table via `MigratorTrait::migration_table_name` method #[sea_orm(table_name = "seaql_migrations")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub version: String, pub applied_at: i64, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ================================================ FILE: sea-orm-migration/src/util.rs ================================================ pub fn get_file_stem(path: &str) -> &str { std::path::Path::new(path) .file_stem() .map(|f| f.to_str().unwrap()) .unwrap() } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_file_stem() { let pair = vec![ ( "m20220101_000001_create_table.rs", "m20220101_000001_create_table", ), ( "src/m20220101_000001_create_table.rs", "m20220101_000001_create_table", ), ( "migration/src/m20220101_000001_create_table.rs", "m20220101_000001_create_table", ), ( "/migration/src/m20220101_000001_create_table.tmp.rs", "m20220101_000001_create_table.tmp", ), ]; for (path, expect) in pair { assert_eq!(get_file_stem(path), expect); } } } ================================================ FILE: sea-orm-migration/tests/common/migration/m20220118_000001_create_cake_table.rs ================================================ use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("cake") .col(pk_auto("id")) .col(string("name")) .to_owned(), ) .await?; manager .create_index( Index::create() .name("cake_name_index") .table("cake") .col("name") .to_owned(), ) .await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("cake").to_owned()) .await?; if std::env::var_os("ABORT_MIGRATION").eq(&Some("YES".into())) { return Err(DbErr::Migration( "Abort migration and rollback changes".into(), )); } Ok(()) } } ================================================ FILE: sea-orm-migration/tests/common/migration/m20220118_000002_create_fruit_table.rs ================================================ use sea_orm_migration::sea_orm::DbBackend; use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("fruit") .col(pk_auto("id")) .col(string("name")) .col(integer("cake_id")) .foreign_key( ForeignKey::create() .name("fk-fruit-cake_id") .from("fruit", "cake_id") .to("cake", "id"), ) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { if manager.get_database_backend() != DbBackend::Sqlite { manager .drop_foreign_key( ForeignKey::drop() .table("fruit") .name("fk-fruit-cake_id") .to_owned(), ) .await?; } manager .drop_table(Table::drop().table("fruit").to_owned()) .await } } ================================================ FILE: sea-orm-migration/tests/common/migration/m20220118_000003_seed_cake_table.rs ================================================ use sea_orm_migration::prelude::*; use sea_orm_migration::sea_orm::{entity::*, query::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); cake::ActiveModel { name: Set("Cheesecake".to_owned()), ..Default::default() } .insert(db) .await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); cake::Entity::delete_many() .filter(cake::Column::Name.eq("Cheesecake")) .exec(db) .await?; Ok(()) } } mod cake { use sea_orm_migration::sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } ================================================ FILE: sea-orm-migration/tests/common/migration/m20220118_000004_create_tea_enum.rs ================================================ use sea_orm_migration::prelude::{sea_query::extension::postgres::Type, *}; use sea_orm_migration::sea_orm::{ConnectionTrait, DbBackend}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); if db.get_database_backend() == DbBackend::Postgres { manager .create_type( Type::create() .as_enum(Tea::Enum) .values([Tea::EverydayTea, Tea::BreakfastTea]) .to_owned(), ) .await?; } Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); if db.get_database_backend() == DbBackend::Postgres { manager .drop_type(Type::drop().name(Tea::Enum).to_owned()) .await?; } Ok(()) } } #[derive(DeriveIden)] pub enum Tea { #[sea_orm(iden = "tea")] Enum, #[sea_orm(iden = "EverydayTea")] EverydayTea, #[sea_orm(iden = "BreakfastTea")] BreakfastTea, } ================================================ FILE: sea-orm-migration/tests/common/migration/m20220923_000001_seed_cake_table.rs ================================================ use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let insert = Query::insert() .into_table("cake") .columns(["name"]) .values_panic(["Tiramisu".into()]) .to_owned(); manager.execute(insert).await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let delete = Query::delete() .from_table("cake") .and_where(Expr::col("name").eq("Tiramisu")) .to_owned(); manager.execute(delete).await?; Ok(()) } } ================================================ FILE: sea-orm-migration/tests/common/migration/m20230109_000001_seed_cake_table.rs ================================================ use sea_orm_migration::prelude::*; use sea_orm_migration::sea_orm::{entity::*, query::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let transaction = db.begin().await?; cake::ActiveModel { name: Set("Cheesecake".to_owned()), ..Default::default() } .insert(&transaction) .await?; if std::env::var_os("ABORT_MIGRATION").eq(&Some("YES".into())) { return Err(DbErr::Migration( "Abort migration and rollback changes".into(), )); } transaction.commit().await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); let transaction = db.begin().await?; cake::Entity::delete_many() .filter(cake::Column::Name.eq("Cheesecake")) .exec(&transaction) .await?; transaction.commit().await?; Ok(()) } } mod cake { use sea_orm_migration::sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } ================================================ FILE: sea-orm-migration/tests/common/migration/m20250101_000001_create_test_table.rs ================================================ use sea_orm_migration::prelude::*; use sea_orm_migration::schema::*; use sea_orm_migration::sea_orm::DbBackend; pub struct Migration { pub use_transaction: Option, pub should_fail: bool, } impl MigrationName for Migration { fn name(&self) -> &str { "m20250101_000001_create_test_table" } } #[async_trait::async_trait] impl MigrationTrait for Migration { fn use_transaction(&self) -> Option { self.use_transaction } async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let expect_txn = self .use_transaction .unwrap_or(manager.get_database_backend() == DbBackend::Postgres); assert_eq!( manager.get_connection().is_transaction(), expect_txn, "up: expected is_transaction() = {expect_txn}" ); manager .create_table( Table::create() .table("test_table") .col(pk_auto("id")) .col(string("name")) .to_owned(), ) .await?; if self.should_fail { return Err(DbErr::Migration("intentional failure".into())); } Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let expect_txn = self .use_transaction .unwrap_or(manager.get_database_backend() == DbBackend::Postgres); assert_eq!( manager.get_connection().is_transaction(), expect_txn, "down: expected is_transaction() = {expect_txn}" ); manager .drop_table(Table::drop().table("test_table").to_owned()) .await } } ================================================ FILE: sea-orm-migration/tests/common/migration/m20250101_000002_manual_transaction.rs ================================================ use sea_orm_migration::prelude::*; use sea_orm_migration::schema::*; pub struct Migration; impl MigrationName for Migration { fn name(&self) -> &str { "m20250101_000002_manual_transaction" } } #[async_trait::async_trait] impl MigrationTrait for Migration { fn use_transaction(&self) -> Option { Some(false) } async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { assert!( !manager.get_connection().is_transaction(), "outer manager should not be in a transaction" ); let m = manager.begin().await?; assert!( m.get_connection().is_transaction(), "inner manager should be in a transaction" ); m.create_table( Table::create() .table("manual_txn_table") .col(pk_auto("id")) .col(string("name")) .to_owned(), ) .await?; m.commit().await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { let m = manager.begin().await?; m.drop_table(Table::drop().table("manual_txn_table").to_owned()) .await?; m.commit().await?; Ok(()) } } ================================================ FILE: sea-orm-migration/tests/common/migration/mod.rs ================================================ pub mod m20220118_000001_create_cake_table; pub mod m20220118_000002_create_fruit_table; pub mod m20220118_000003_seed_cake_table; pub mod m20220118_000004_create_tea_enum; pub mod m20220923_000001_seed_cake_table; pub mod m20230109_000001_seed_cake_table; pub mod m20250101_000001_create_test_table; pub mod m20250101_000002_manual_transaction; ================================================ FILE: sea-orm-migration/tests/common/migrator/default.rs ================================================ use crate::common::migration::*; use sea_orm_migration::prelude::*; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220118_000001_create_cake_table::Migration), Box::new(m20220118_000002_create_fruit_table::Migration), Box::new(m20220118_000003_seed_cake_table::Migration), Box::new(m20220118_000004_create_tea_enum::Migration), Box::new(m20220923_000001_seed_cake_table::Migration), Box::new(m20230109_000001_seed_cake_table::Migration), ] } } ================================================ FILE: sea-orm-migration/tests/common/migrator/mod.rs ================================================ pub mod default; pub mod override_migration_table_name; pub mod transaction_test; pub mod with_self; ================================================ FILE: sea-orm-migration/tests/common/migrator/override_migration_table_name.rs ================================================ use crate::common::migration::*; use sea_orm_migration::prelude::*; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m20220118_000001_create_cake_table::Migration), Box::new(m20220118_000002_create_fruit_table::Migration), Box::new(m20220118_000003_seed_cake_table::Migration), Box::new(m20220118_000004_create_tea_enum::Migration), Box::new(m20220923_000001_seed_cake_table::Migration), Box::new(m20230109_000001_seed_cake_table::Migration), ] } fn migration_table_name() -> sea_orm::DynIden { "override_migration_table_name".into_iden() } } ================================================ FILE: sea-orm-migration/tests/common/migrator/transaction_test.rs ================================================ use crate::common::migration::*; use sea_orm_migration::{MigratorTraitSelf, prelude::*}; pub struct Migrator { pub use_transaction: Option, pub should_fail: bool, } #[async_trait::async_trait] impl MigratorTraitSelf for Migrator { fn migrations(&self) -> Vec> { vec![Box::new(m20250101_000001_create_test_table::Migration { use_transaction: self.use_transaction, should_fail: self.should_fail, })] } } pub struct ManualTxnMigrator; #[async_trait::async_trait] impl MigratorTrait for ManualTxnMigrator { fn migrations() -> Vec> { vec![Box::new(m20250101_000002_manual_transaction::Migration)] } } ================================================ FILE: sea-orm-migration/tests/common/migrator/with_self.rs ================================================ use crate::common::migration::*; use sea_orm_migration::{MigratorTraitSelf, prelude::*}; pub struct Migrator { pub i: i32, } #[async_trait::async_trait] impl MigratorTraitSelf for Migrator { fn migrations(&self) -> Vec> { vec![ Box::new(m20220118_000001_create_cake_table::Migration), Box::new(m20220118_000002_create_fruit_table::Migration), Box::new(m20220118_000003_seed_cake_table::Migration), Box::new(m20220118_000004_create_tea_enum::Migration), Box::new(m20220923_000001_seed_cake_table::Migration), Box::new(m20230109_000001_seed_cake_table::Migration), ] } } ================================================ FILE: sea-orm-migration/tests/common/mod.rs ================================================ pub mod migration; pub mod migrator; ================================================ FILE: sea-orm-migration/tests/main.rs ================================================ mod common; use common::migrator::*; use sea_orm::{ConnectOptions, ConnectionTrait, Database, DbBackend, DbErr, Statement}; use sea_orm_migration::{MigratorTraitSelf, migrator::MigrationStatus, prelude::*}; #[tokio::test] async fn main() -> Result<(), DbErr> { tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .with_test_writer() .init(); let url = &std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set"); run_migration(url, default::Migrator, "sea_orm_migration", "public").await?; run_migration( url, default::Migrator, "sea_orm_migration_schema", "my_schema", ) .await?; run_migration( url, with_self::Migrator { i: 12 }, "sea_orm_migration_self", "public", ) .await?; run_migration( url, override_migration_table_name::Migrator, "sea_orm_migration_table_name", "public", ) .await?; run_migration( url, override_migration_table_name::Migrator, "sea_orm_migration_table_name_schema", "my_schema", ) .await?; run_transaction_test(url, "sea_orm_migration_txn", "public").await?; Ok(()) } async fn create_db( url: &str, db_name: &str, schema: &str, ) -> Result { let db_connect = |url: String| async { let connect_options = ConnectOptions::new(url) .set_schema_search_path(format!("{schema},public")) .to_owned(); Database::connect(connect_options).await }; let db = db_connect(url.to_owned()).await?; match db.get_database_backend() { DbBackend::MySql => { db.execute_raw(Statement::from_string( db.get_database_backend(), format!("CREATE DATABASE IF NOT EXISTS `{db_name}`;"), )) .await?; let url = format!("{url}/{db_name}"); db_connect(url).await } DbBackend::Postgres => { db.execute_raw(Statement::from_string( db.get_database_backend(), format!("DROP DATABASE IF EXISTS \"{db_name}\";"), )) .await?; db.execute_raw(Statement::from_string( db.get_database_backend(), format!("CREATE DATABASE \"{db_name}\";"), )) .await?; let url = format!("{url}/{db_name}"); let db = db_connect(url).await?; db.execute_raw(Statement::from_string( db.get_database_backend(), format!("CREATE SCHEMA IF NOT EXISTS \"{schema}\";"), )) .await?; Ok(db) } DbBackend::Sqlite => Ok(db), db => Err(DbErr::BackendNotSupported { db: db.as_str(), ctx: "create_db", }), } } async fn run_migration(url: &str, migrator: M, db_name: &str, schema: &str) -> Result<(), DbErr> where M: MigratorTraitSelf, { let db = &create_db(url, db_name, schema).await?; let manager = SchemaManager::new(db); println!("\nMigrator::status"); migrator.status(db).await?; println!("\nMigrator::install"); migrator.install(db).await?; let migration_table_name = migrator.migration_table_name().to_string(); let migration_table_name = migration_table_name.as_str(); assert!(manager.has_table(migration_table_name).await?); if migration_table_name != "seaql_migrations" { assert!(!manager.has_table("seaql_migrations").await?); } println!("\nMigrator::reset"); migrator.reset(db).await?; assert!(!manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); println!("\nMigrator::up"); migrator.up(db, Some(0)).await?; assert!(!manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); println!("\nMigrator::up"); migrator.up(db, Some(1)).await?; println!("\nMigrator::get_pending_migrations"); let migrations = migrator.get_pending_migrations(db).await?; assert_eq!(migrations.len(), 5); let migration = migrations.get(0).unwrap(); assert_eq!(migration.name(), "m20220118_000002_create_fruit_table"); assert_eq!(migration.status(), MigrationStatus::Pending); assert!(manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); println!("\nMigrator::down"); migrator.down(db, Some(0)).await?; assert!(manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); println!("\nMigrator::down"); migrator.down(db, Some(1)).await?; assert!(!manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); // Tests rolling back a failing migration on Postgres. // With per-migration transactions, only the failing migration is rolled back; // earlier migrations that committed successfully are preserved. if matches!(db.get_database_backend(), DbBackend::Postgres) { println!("\nRoll back changes when encounter errors"); // Set a flag to throw error inside `m20230109_000001_seed_cake_table.rs` unsafe { std::env::set_var("ABORT_MIGRATION", "YES"); } // Should throw an error println!("\nMigrator::up"); assert_eq!( migrator.up(db, None).await, Err(DbErr::Migration( "Abort migration and rollback changes".into() )) ); println!("\nMigrator::status"); migrator.status(db).await?; // Only the failing migration (m20230109) is rolled back; // earlier migrations (cake, fruit, etc.) committed successfully assert!(manager.has_table("cake").await?); assert!(manager.has_table("fruit").await?); // Unset the flag unsafe { std::env::remove_var("ABORT_MIGRATION"); } } println!("\nMigrator::up"); migrator.up(db, None).await?; println!("\nMigrator::get_applied_migrations"); let migrations = migrator.get_applied_migrations(db).await?; assert_eq!(migrations.len(), 6); assert!(!manager.has_index("cake", "non_existent_index").await?); assert!(manager.has_index("cake", "cake_name_index").await?); let migration = migrations.get(0).unwrap(); assert_eq!(migration.name(), "m20220118_000001_create_cake_table"); assert_eq!(migration.status(), MigrationStatus::Applied); println!("\nMigrator::status"); migrator.status(db).await?; assert!(manager.has_table("cake").await?); assert!(manager.has_table("fruit").await?); assert!(manager.has_column("cake", "name").await?); assert!(manager.has_column("fruit", "cake_id").await?); // Tests rolling back a failing migration-down on Postgres. // With per-migration transactions, rollbacks happen one at a time in reverse. // Migrations 6-2 roll back and commit successfully. Migration 1 (drops cake // then ABORTs) fails, so its DROP is restored. But migration 2's DROP of // the fruit table already committed. if matches!(db.get_database_backend(), DbBackend::Postgres) { println!("\nRoll back changes when encounter errors"); // Set a flag to throw error inside `m20220118_000001_create_cake_table.rs` unsafe { std::env::set_var("ABORT_MIGRATION", "YES"); } // Should throw an error println!("\nMigrator::down"); assert_eq!( migrator.down(db, None).await, Err(DbErr::Migration( "Abort migration and rollback changes".into() )) ); println!("\nMigrator::status"); migrator.status(db).await?; // Only migration 1's down was rolled back (cake table restored). // Migrations 2-6 were rolled back successfully (fruit table dropped). assert!(manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); // Unset the flag unsafe { std::env::remove_var("ABORT_MIGRATION"); } } println!("\nMigrator::down"); migrator.down(db, None).await?; assert!(manager.has_table(migration_table_name).await?); if migration_table_name != "seaql_migrations" { assert!(!manager.has_table("seaql_migrations").await?); } assert!(!manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); println!("\nMigrator::fresh"); migrator.fresh(db).await?; assert!(manager.has_table("cake").await?); assert!(manager.has_table("fruit").await?); println!("\nMigrator::refresh"); migrator.refresh(db).await?; assert!(manager.has_table("cake").await?); assert!(manager.has_table("fruit").await?); println!("\nMigrator::reset"); migrator.reset(db).await?; assert!(!manager.has_table("cake").await?); assert!(!manager.has_table("fruit").await?); println!("\nMigrator::status"); migrator.status(db).await?; Ok(()) } async fn run_transaction_test(url: &str, db_name: &str, schema: &str) -> Result<(), DbErr> { let db = &create_db(url, db_name, schema).await?; let backend = db.get_database_backend(); let manager = SchemaManager::new(db); // use_transaction = None: Postgres wraps by default, others don't. // The assertion happens inside the migration's up()/down() body. println!("\nTransaction test: use_transaction = None"); let m = transaction_test::Migrator { use_transaction: None, should_fail: false, }; m.up(db, None).await?; assert!(manager.has_table("test_table").await?); m.down(db, None).await?; assert!(!manager.has_table("test_table").await?); m.reset(db).await.ok(); // use_transaction = Some(true): forces transaction on every backend. println!("\nTransaction test: use_transaction = Some(true)"); let m = transaction_test::Migrator { use_transaction: Some(true), should_fail: false, }; m.up(db, None).await?; assert!(manager.has_table("test_table").await?); m.down(db, None).await?; assert!(!manager.has_table("test_table").await?); m.reset(db).await.ok(); // use_transaction = Some(false): disables transaction, including on Postgres. println!("\nTransaction test: use_transaction = Some(false)"); let m = transaction_test::Migrator { use_transaction: Some(false), should_fail: false, }; m.up(db, None).await?; assert!(manager.has_table("test_table").await?); m.down(db, None).await?; assert!(!manager.has_table("test_table").await?); m.reset(db).await.ok(); // Failure with transaction: DDL rolled back (except MySQL which auto-commits DDL). println!("\nTransaction test: failure with transaction"); let m = transaction_test::Migrator { use_transaction: Some(true), should_fail: true, }; assert!(m.up(db, None).await.is_err()); if backend != DbBackend::MySql { assert!( !manager.has_table("test_table").await?, "DDL should be rolled back" ); } m.reset(db).await.ok(); // Failure without transaction: DDL persists. println!("\nTransaction test: failure without transaction"); let m = transaction_test::Migrator { use_transaction: Some(false), should_fail: true, }; assert!(m.up(db, None).await.is_err()); assert!(manager.has_table("test_table").await?, "DDL should persist"); db.execute_unprepared("DROP TABLE IF EXISTS test_table") .await?; m.reset(db).await.ok(); // Manual transaction via manager.begin() / commit(). println!("\nTransaction test: manual begin/commit"); let m = transaction_test::ManualTxnMigrator; m.up(db, None).await?; assert!(manager.has_table("manual_txn_table").await?); m.down(db, None).await?; assert!(!manager.has_table("manual_txn_table").await?); m.reset(db).await.ok(); Ok(()) } ================================================ FILE: sea-orm-migration/tests/postgres.rs ================================================ mod common; #[cfg(all(test, feature = "sqlx-postgres"))] mod inner { use crate::common::migrator::default::*; use sea_orm::{ConnectOptions, ConnectionTrait, Database, DbBackend, Statement, error::DbErr}; use sea_orm_migration::prelude::*; #[tokio::test] async fn test_fresh_with_extension() -> Result<(), DbErr> { let url = &std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set"); let db_name = "test_fresh_with_extension"; let db_connect = |url: String| async { let connect_options = ConnectOptions::new(url).to_owned(); Database::connect(connect_options).await }; let db = db_connect(url.to_owned()).await?; if !matches!(db.get_database_backend(), DbBackend::Postgres) { return Ok(()); } db.execute_unprepared(&format!(r#"DROP DATABASE IF EXISTS "{db_name}""#)) .await?; db.execute_unprepared(&format!(r#"CREATE DATABASE "{db_name}""#)) .await?; let url = format!("{url}/{db_name}"); let db = db_connect(url).await?; // Create the extension and a custom type db.execute_unprepared("CREATE EXTENSION IF NOT EXISTS citext") .await?; db.execute_unprepared("CREATE TYPE \"UserFruit\" AS ENUM ('Apple', 'Banana')") .await?; // Run the fresh migration Migrator::fresh(&db).await?; // Check that the custom type was dropped and the extension's type was not let citext_exists: Option = db .query_one_raw(Statement::from_string( DbBackend::Postgres, r#"SELECT 1 as "value" FROM pg_type WHERE typname = 'citext'"#.to_owned(), )) .await? .map(|row| row.try_get("", "value").unwrap()); assert_eq!(citext_exists, Some(1), "the citext type should still exist"); let user_fruit_exists: Option = db .query_one_raw(Statement::from_string( DbBackend::Postgres, r#"SELECT 1 as "value" FROM pg_type WHERE typname = 'UserFruit'"#.to_owned(), )) .await? .map(|row| row.try_get("", "value").unwrap()); assert_eq!( user_fruit_exists, None, "the UserFruit type should have been dropped" ); Ok(()) } } ================================================ FILE: sea-orm-rocket/Cargo.toml ================================================ [workspace] members = ["codegen", "lib"] ================================================ FILE: sea-orm-rocket/README.md ================================================ # SeaORM Rocket support crate. Not all versions of `sea-orm-rocket` are compatible with every version of Rocket due to breaking changes in the Rocket database management API. Please reference the below table to determine which version of `sea-orm-rocket` is appropriate for your use case. | Rocket version | `sea-orm-rocket` version | | :------------- | -----------------------: | | v0.5.0 (+ RCs) | v0.5.5 | | v0.5.1 | v0.6.0 | ================================================ FILE: sea-orm-rocket/codegen/Cargo.toml ================================================ [package] authors = ["Sergio Benitez ", "Jeb Rosen "] description = "Procedural macros for sea_orm_rocket." edition = "2024" keywords = ["rocket", "framework", "database", "pools"] license = "MIT OR Apache-2.0" name = "sea-orm-rocket-codegen" readme = "../README.md" repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools" rust-version = "1.85.0" version = "0.6.0" [lib] proc-macro = true [dependencies] devise = "0.4" quote = "1" [dev-dependencies] rocket = { version = "0.5.1", default-features = false } trybuild = "1" version_check = "0.9" ================================================ FILE: sea-orm-rocket/codegen/src/database.rs ================================================ use proc_macro::TokenStream; use devise::proc_macro2_diagnostics::SpanDiagnosticExt; use devise::syn::{self, spanned::Spanned}; use devise::{DeriveGenerator, FromMeta, MapperBuild, Support, ValidatorBuild}; const ONE_DATABASE_ATTR: &str = "missing `#[database(\"name\")]` attribute"; const ONE_UNNAMED_FIELD: &str = "struct must have exactly one unnamed field"; #[derive(Debug, FromMeta)] struct DatabaseAttribute { #[meta(naked)] name: String, } pub fn derive_database(input: TokenStream) -> TokenStream { DeriveGenerator::build_for(input, quote!(impl rocket_db_pools::Database)) .support(Support::TupleStruct) .validator(ValidatorBuild::new().struct_validate(|_, s| { if s.fields.len() == 1 { Ok(()) } else { Err(s.span().error(ONE_UNNAMED_FIELD)) } })) .outer_mapper(MapperBuild::new().struct_map(|_, s| { let pool_type = match &s.fields { syn::Fields::Unnamed(f) => &f.unnamed[0].ty, _ => unreachable!("Support::TupleStruct"), }; let decorated_type = &s.ident; let db_ty = quote_spanned!(decorated_type.span() => <#decorated_type as rocket_db_pools::Database> ); quote_spanned! { decorated_type.span() => impl From<#pool_type> for #decorated_type { fn from(pool: #pool_type) -> Self { Self(pool) } } impl std::ops::Deref for #decorated_type { type Target = #pool_type; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for #decorated_type { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } #[rocket::async_trait] impl<'r> rocket::request::FromRequest<'r> for &'r #decorated_type { type Error = (); async fn from_request( req: &'r rocket::request::Request<'_> ) -> rocket::request::Outcome { match #db_ty::fetch(req.rocket()) { Some(db) => rocket::outcome::Outcome::Success(db), None => rocket::outcome::Outcome::Error(( rocket::http::Status::InternalServerError, ())) } } } impl rocket::Sentinel for &#decorated_type { fn abort(rocket: &rocket::Rocket) -> bool { #db_ty::fetch(rocket).is_none() } } } })) .outer_mapper(quote!(#[rocket::async_trait])) .inner_mapper(MapperBuild::new().try_struct_map(|_, s| { let db_name = DatabaseAttribute::one_from_attrs("database", &s.attrs)? .map(|attr| attr.name) .ok_or_else(|| s.span().error(ONE_DATABASE_ATTR))?; let fairing_name = format!("'{db_name}' Database Pool"); let pool_type = match &s.fields { syn::Fields::Unnamed(f) => &f.unnamed[0].ty, _ => unreachable!("Support::TupleStruct"), }; Ok(quote_spanned! { pool_type.span() => type Pool = #pool_type; const NAME: &'static str = #db_name; fn init() -> rocket_db_pools::Initializer { rocket_db_pools::Initializer::with_name(#fairing_name) } }) })) .to_tokens() } ================================================ FILE: sea-orm-rocket/codegen/src/lib.rs ================================================ #![recursion_limit = "256"] #![warn(rust_2018_idioms)] //! # `sea_orm_rocket` - Code Generation //! //! Implements the code generation portion of the `sea_orm_rocket` crate. This //! is an implementation detail. This create should never be depended on //! directly. #[macro_use] extern crate quote; mod database; /// Automatic derive for the [`Database`] trait. /// /// The derive generates an implementation of [`Database`] as follows: /// /// * [`Database::NAME`] is set to the value in the `#[database("name")]` /// attribute. /// /// This names the database, providing an anchor to configure the database via /// `Rocket.toml` or any other configuration source. Specifically, the /// configuration in `databases.name` is used to configure the driver. /// /// * [`Database::Pool`] is set to the wrapped type: `PoolType` above. The type /// must implement [`Pool`]. /// /// To meet the required [`Database`] supertrait bounds, this derive also /// generates implementations for: /// /// * `From` /// /// * `Deref` /// /// * `DerefMut` /// /// * `FromRequest<'_> for &Db` /// /// * `Sentinel for &Db` /// /// The `Deref` impls enable accessing the database pool directly from /// references `&Db` or `&mut Db`. To force a dereference to the underlying /// type, use `&db.0` or `&**db` or their `&mut` variants. /// /// [`Database`]: ../sea_orm_rocket/trait.Database.html /// [`Database::NAME`]: ../sea_orm_rocket/trait.Database.html#associatedconstant.NAME /// [`Database::Pool`]: ../sea_orm_rocket/trait.Database.html#associatedtype.Pool /// [`Pool`]: ../sea_orm_rocket/trait.Pool.html #[proc_macro_derive(Database, attributes(database))] pub fn derive_database(input: proc_macro::TokenStream) -> proc_macro::TokenStream { crate::database::derive_database(input) } ================================================ FILE: sea-orm-rocket/lib/Cargo.toml ================================================ [package] authors = ["Sergio Benitez ", "Jeb Rosen "] description = "SeaORM Rocket support crate" edition = "2024" keywords = ["rocket", "framework", "database", "pools"] license = "MIT OR Apache-2.0" name = "sea-orm-rocket" readme = "../README.md" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "0.6.0" [package.metadata.docs.rs] all-features = true [dependencies.rocket] default-features = false version = "0.5.1" [dependencies.sea-orm-rocket-codegen] path = "../codegen" version = "0.6.0" [dependencies.rocket_okapi] default-features = false optional = true version = "0.9" [dev-dependencies.rocket] default-features = false features = ["json"] version = "0.5.1" ================================================ FILE: sea-orm-rocket/lib/src/config.rs ================================================ use rocket::serde::{Deserialize, Serialize}; /// Base configuration for all database drivers. /// /// A dictionary matching this structure is extracted from the active /// [`Figment`](crate::figment::Figment), scoped to `databases.name`, where /// `name` is the name of the database, by the /// [`Initializer`](crate::Initializer) fairing on ignition and used to /// configure the relevant database and database pool. /// /// With the default provider, these parameters are typically configured in a /// `Rocket.toml` file: /// /// ```toml /// [default.databases.db_name] /// url = "/path/to/db.sqlite" /// /// # only `url` is required. `Initializer` provides defaults for the rest. /// min_connections = 64 /// max_connections = 1024 /// connect_timeout = 5 /// idle_timeout = 120 /// ``` /// /// Alternatively, a custom provider can be used. For example, a custom `Figment` /// with a global `databases.name` configuration: /// /// ```rust /// # use rocket::launch; /// #[launch] /// fn rocket() -> _ { /// let figment = rocket::Config::figment().merge(( /// "databases.name", /// sea_orm_rocket::Config { /// url: "db:specific@config&url".into(), /// min_connections: None, /// max_connections: 1024, /// connect_timeout: 3, /// idle_timeout: None, /// sqlx_logging: true, /// }, /// )); /// /// rocket::custom(figment) /// } /// ``` /// /// For general information on configuration in Rocket, see [`rocket::config`]. /// For higher-level details on configuring a database, see the [crate-level /// docs](crate#configuration). #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(crate = "rocket::serde")] pub struct Config { /// Database-specific connection and configuration URL. /// /// The format of the URL is database specific; consult your database's /// documentation. pub url: String, /// Minimum number of connections to maintain in the pool. /// /// **Note:** `deadpool` drivers do not support and thus ignore this value. /// /// _Default:_ `None`. pub min_connections: Option, /// Maximum number of connections to maintain in the pool. /// /// _Default:_ `workers * 4`. pub max_connections: usize, /// Number of seconds to wait for a connection before timing out. /// /// If the timeout elapses before a connection can be made or retrieved from /// a pool, an error is returned. /// /// _Default:_ `5`. pub connect_timeout: u64, /// Maximum number of seconds to keep a connection alive for. /// /// After a connection is established, it is maintained in a pool for /// efficient connection retrieval. When an `idle_timeout` is set, that /// connection will be closed after the timeout elapses. If an /// `idle_timeout` is not specified, the behavior is driver specific but /// typically defaults to keeping a connection active indefinitely. /// /// _Default:_ `None`. pub idle_timeout: Option, /// Enable SQLx statement logging (default true) #[serde(default)] pub sqlx_logging: bool, } ================================================ FILE: sea-orm-rocket/lib/src/database.rs ================================================ use std::marker::PhantomData; use std::ops::DerefMut; use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::http::Status; use rocket::request::{FromRequest, Outcome, Request}; use rocket::{Build, Ignite, Phase, Rocket, Sentinel, error, info_}; use rocket::figment::providers::Serialized; use rocket::yansi::Paint; #[cfg(feature = "rocket_okapi")] use rocket_okapi::{ r#gen::OpenApiGenerator, request::{OpenApiFromRequest, RequestHeaderInput}, }; use crate::Pool; /// Derivable trait which ties a database [`Pool`] with a configuration name. /// /// This trait should rarely, if ever, be implemented manually. Instead, it /// should be derived: /// /// ```ignore /// use sea_orm_rocket::{Database}; /// # use sea_orm_rocket::MockPool as SeaOrmPool; /// /// #[derive(Database, Debug)] /// #[database("sea_orm")] /// struct Db(SeaOrmPool); /// /// #[launch] /// fn rocket() -> _ { /// rocket::build().attach(Db::init()) /// } /// ``` /// /// See the [`Database` derive](derive@crate::Database) for details. pub trait Database: From + DerefMut + Send + Sync + 'static { /// The [`Pool`] type of connections to this database. /// /// When `Database` is derived, this takes the value of the `Inner` type in /// `struct Db(Inner)`. type Pool: Pool; /// The configuration name for this database. /// /// When `Database` is derived, this takes the value `"name"` in the /// `#[database("name")]` attribute. const NAME: &'static str; /// Returns a fairing that initializes the database and its connection pool. /// /// # Example /// /// ```rust /// # mod _inner { /// # use rocket::launch; /// use sea_orm_rocket::Database; /// # use sea_orm_rocket::MockPool as SeaOrmPool; /// /// #[derive(Database)] /// #[database("sea_orm")] /// struct Db(SeaOrmPool); /// /// #[launch] /// fn rocket() -> _ { /// rocket::build().attach(Db::init()) /// } /// # } /// ``` fn init() -> Initializer { Initializer::new() } /// Returns a reference to the initialized database in `rocket`. The /// initializer fairing returned by `init()` must have already executed for /// `Option` to be `Some`. This is guaranteed to be the case if the fairing /// is attached and either: /// /// * Rocket is in the [`Orbit`](rocket::Orbit) phase. That is, the /// application is running. This is always the case in request guards /// and liftoff fairings, /// * _or_ Rocket is in the [`Build`](rocket::Build) or /// [`Ignite`](rocket::Ignite) phase and the `Initializer` fairing has /// already been run. This is the case in all fairing callbacks /// corresponding to fairings attached _after_ the `Initializer` /// fairing. /// /// # Example /// /// Run database migrations in an ignite fairing. It is imperative that the /// migration fairing be registered _after_ the `init()` fairing. /// /// ```rust /// # mod _inner { /// # use rocket::launch; /// use rocket::fairing::{self, AdHoc}; /// use rocket::{Build, Rocket}; /// /// use sea_orm_rocket::Database; /// # use sea_orm_rocket::MockPool as SeaOrmPool; /// /// #[derive(Database)] /// #[database("sea_orm")] /// struct Db(SeaOrmPool); /// /// async fn run_migrations(rocket: Rocket) -> fairing::Result { /// if let Some(db) = Db::fetch(&rocket) { /// // run migrations using `db`. get the inner type with &db.0. /// Ok(rocket) /// } else { /// Err(rocket) /// } /// } /// /// #[launch] /// fn rocket() -> _ { /// rocket::build() /// .attach(Db::init()) /// .attach(AdHoc::try_on_ignite("DB Migrations", run_migrations)) /// } /// # } /// ``` fn fetch(rocket: &Rocket

) -> Option<&Self> { if let Some(db) = rocket.state() { return Some(db); } let dbtype = std::any::type_name::(); let fairing = Paint::new(format!("{dbtype}::init()")).bold(); error!( "Attempted to fetch unattached database `{}`.", Paint::new(dbtype).bold() ); info_!( "`{}` fairing must be attached prior to using this database.", fairing ); None } } /// A [`Fairing`] which initializes a [`Database`] and its connection pool. /// /// A value of this type can be created for any type `D` that implements /// [`Database`] via the [`Database::init()`] method on the type. Normally, a /// value of this type _never_ needs to be constructed directly. This /// documentation exists purely as a reference. /// /// This fairing initializes a database pool. Specifically, it: /// /// 1. Reads the configuration at `database.db_name`, where `db_name` is /// [`Database::NAME`]. /// /// 2. Sets [`Config`](crate::Config) defaults on the configuration figment. /// /// 3. Calls [`Pool::init()`]. /// /// 4. Stores the database instance in managed storage, retrievable via /// [`Database::fetch()`]. /// /// The name of the fairing itself is `Initializer`, with `D` replaced with /// the type name `D` unless a name is explicitly provided via /// [`Self::with_name()`]. pub struct Initializer(Option<&'static str>, PhantomData D>); /// A request guard which retrieves a single connection to a [`Database`]. /// /// For a database type of `Db`, a request guard of `Connection` retrieves a /// single connection to `Db`. /// /// The request guard succeeds if the database was initialized by the /// [`Initializer`] fairing and a connection is available within /// [`connect_timeout`](crate::Config::connect_timeout) seconds. /// * If the `Initializer` fairing was _not_ attached, the guard _fails_ with /// status `InternalServerError`. A [`Sentinel`] guards this condition, and so /// this type of failure is unlikely to occur. A `None` error is returned. /// * If a connection is not available within `connect_timeout` seconds or /// another error occurs, the guard _fails_ with status `ServiceUnavailable` /// and the error is returned in `Some`. pub struct Connection<'a, D: Database>(&'a ::Connection); impl Initializer { /// Returns a database initializer fairing for `D`. /// /// This method should never need to be called manually. See the [crate /// docs](crate) for usage information. #[allow(clippy::new_without_default)] pub fn new() -> Self { Self(None, std::marker::PhantomData) } /// Returns a database initializer fairing for `D` with name `name`. /// /// This method should never need to be called manually. See the [crate /// docs](crate) for usage information. pub fn with_name(name: &'static str) -> Self { Self(Some(name), std::marker::PhantomData) } } impl<'a, D: Database> Connection<'a, D> { /// Returns the internal connection value. See the [`Connection` Deref /// column](crate#supported-drivers) for the expected type of this value. pub fn into_inner(self) -> &'a ::Connection { self.0 } } #[cfg(feature = "rocket_okapi")] impl<'r, D: Database> OpenApiFromRequest<'r> for Connection<'r, D> { fn from_request_input( _gen: &mut OpenApiGenerator, _name: String, _required: bool, ) -> rocket_okapi::Result { Ok(RequestHeaderInput::None) } } #[rocket::async_trait] impl Fairing for Initializer { fn info(&self) -> Info { Info { name: self.0.unwrap_or_else(std::any::type_name::), kind: Kind::Ignite, } } async fn on_ignite(&self, rocket: Rocket) -> fairing::Result { let workers: usize = rocket .figment() .extract_inner(rocket::Config::WORKERS) .unwrap_or_else(|_| rocket::Config::default().workers); let figment = rocket .figment() .focus(&format!("databases.{}", D::NAME)) .merge(Serialized::default("max_connections", workers * 4)) .merge(Serialized::default("connect_timeout", 5)) .merge(Serialized::default("sqlx_logging", true)); match ::init(&figment).await { Ok(pool) => Ok(rocket.manage(D::from(pool))), Err(e) => { error!("failed to initialize database: {}", e); Err(rocket) } } } } #[rocket::async_trait] impl<'r, D: Database> FromRequest<'r> for Connection<'r, D> { type Error = Option<::Error>; async fn from_request(req: &'r Request<'_>) -> Outcome { match D::fetch(req.rocket()) { Some(pool) => Outcome::Success(Connection(pool.borrow())), None => Outcome::Error((Status::InternalServerError, None)), } } } impl Sentinel for Connection<'_, D> { fn abort(rocket: &Rocket) -> bool { D::fetch(rocket).is_none() } } ================================================ FILE: sea-orm-rocket/lib/src/error.rs ================================================ use std::fmt; /// A general error type for use by [`Pool`](crate::Pool#implementing) /// implementors and returned by the [`Connection`](crate::Connection) request /// guard. #[derive(Debug)] pub enum Error { /// An error that occurred during database/pool initialization. Init(A), /// An error that occurred while retrieving a connection from the pool. Get(B), /// A [`Figment`](crate::figment::Figment) configuration error. Config(crate::figment::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Init(e) => write!(f, "failed to initialize database: {e}"), Error::Get(e) => write!(f, "failed to get db connection: {e}"), Error::Config(e) => write!(f, "bad configuration: {e}"), } } } impl std::error::Error for Error where A: fmt::Debug + fmt::Display, B: fmt::Debug + fmt::Display, { } impl From for Error { fn from(e: crate::figment::Error) -> Self { Self::Config(e) } } ================================================ FILE: sea-orm-rocket/lib/src/lib.rs ================================================ //! SeaORM Rocket support crate. #![deny(missing_docs)] /// Re-export of the `figment` crate. #[doc(inline)] pub use rocket::figment; pub use rocket; mod config; mod database; mod error; mod pool; pub use self::config::Config; pub use self::database::{Connection, Database, Initializer}; pub use self::error::Error; pub use self::pool::{MockPool, Pool}; pub use sea_orm_rocket_codegen::*; ================================================ FILE: sea-orm-rocket/lib/src/pool.rs ================================================ use rocket::figment::Figment; /// Generic [`Database`](crate::Database) driver connection pool trait. /// /// This trait provides a generic interface to various database pooling /// implementations in the Rust ecosystem. It can be implemented by anyone. /// /// This is adapted from the original `rocket_db_pools`. But on top we require /// `Connection` itself to be `Sync`. Hence, instead of cloning or allocating /// a new connection per request, here we only borrow a reference to the pool. /// /// In SeaORM, only *when* you are about to execute a SQL statement will a /// connection be acquired from the pool, and returned as soon as the query finishes. /// This helps a bit with concurrency if the lifecycle of a request is long enough. /// ``` #[rocket::async_trait] pub trait Pool: Sized + Send + Sync + 'static { /// The connection type managed by this pool. type Connection; /// The error type returned by [`Self::init()`]. type Error: std::error::Error; /// Constructs a pool from a [Value](rocket::figment::value::Value). /// /// It is up to each implementor of `Pool` to define its accepted /// configuration value(s) via the `Config` associated type. Most /// integrations provided in `sea_orm_rocket` use [`Config`], which /// accepts a (required) `url` and an (optional) `pool_size`. /// /// ## Errors /// /// This method returns an error if the configuration is not compatible, or /// if creating a pool failed due to an unavailable database server, /// insufficient resources, or another database-specific error. async fn init(figment: &Figment) -> Result; /// Borrows a reference to the pool fn borrow(&self) -> &Self::Connection; } #[derive(Debug)] /// A mock object which impl `Pool`, for testing only pub struct MockPool; #[derive(Debug)] pub struct MockPoolErr; impl std::fmt::Display for MockPoolErr { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{self:?}") } } impl std::error::Error for MockPoolErr {} #[rocket::async_trait] impl Pool for MockPool { type Error = MockPoolErr; type Connection = bool; async fn init(_figment: &Figment) -> Result { Ok(MockPool) } fn borrow(&self) -> &Self::Connection { &true } } ================================================ FILE: sea-orm-sync/CLAUDE.md ================================================ # sea-orm-sync Do **not** edit code in `sea-orm-sync/src/` or `sea-orm-sync/tests/` directly. All source and test files are generated from the main `src/` and `tests/` directories by running: ```bash cd /path/to/sea-orm bash build-tools/make-sync.sh ``` The script copies `src/` and `tests/` into `sea-orm-sync/`, then applies sed transforms to strip async/await, remove `#[async_trait]`, replace `futures_util::lock::Mutex` with `std::sync::Mutex`, etc. ## Workflow 1. Make all code changes in the **root** `src/` and `tests/` directories. 2. Edit `sea-orm-sync/Cargo.toml` directly if dependency or feature changes are needed. 3. Regenerate sync code: `bash build-tools/make-sync.sh` 4. Build and test: `cd sea-orm-sync && cargo check --features rusqlite` ## Gotchas - **`make-sync.sh` requires full filesystem access.** It uses `sed -i`, `find`, `cp -r`, and `cargo fmt`. Running it inside a sandboxed environment (e.g. Cursor's default sandbox) will silently produce broken output — the sed substitutions won't apply, leaving `async_trait`, `futures_util`, `.await`, etc. in the generated code. Always run with full permissions. - **The root crate cannot compile with `--features rusqlite`** because it depends on `sea_query_rusqlite`, which is only wired up in `sea-orm-sync/Cargo.toml`. To check rusqlite code, you must regenerate and build from `sea-orm-sync/`. - **`#[sea_orm_macros::test]`** gates tests on `feature = "rusqlite"` in the sync crate (and on `sqlx-*` features in the async crate). When running tests, pass `--features rusqlite` and set `DATABASE_URL`, e.g.: `DATABASE_URL="sqlite::memory:" cargo test --features rusqlite --test transaction_tests` ================================================ FILE: sea-orm-sync/Cargo.toml ================================================ [workspace] members = [".", "examples/quickstart", "examples/pi_spigot"] [package] authors = ["Chris Tsang "] categories = ["database"] description = "🐚 The sync version of SeaORM" documentation = "https://docs.rs/sea-orm" edition = "2024" homepage = "https://www.sea-ql.org/SeaORM" keywords = ["orm", "mysql", "postgres", "sqlite"] license = "MIT OR Apache-2.0" name = "sea-orm-sync" repository = "https://github.com/SeaQL/sea-orm" rust-version = "1.85.0" version = "2.0.0-rc.37" [package.metadata.docs.rs] features = [ "default", "mock", "proxy", "rbac", "schema-sync", "postgres-array", "postgres-vector", "with-arrow", ] rustdoc-args = ["--cfg", "docsrs"] [lib] name = "sea_orm" path = "src/lib.rs" [dependencies] bigdecimal = { version = "0.4", default-features = false, features = [ "std", ], optional = true } chrono = { version = "0.4.30", default-features = false, optional = true } derive_more = { version = "2", features = ["debug"] } inventory = { version = "0.3", optional = true } ipnetwork = { version = "0.20", default-features = false, optional = true } itertools = "0.14.0" log = { version = "0.4", default-features = false } ouroboros = { version = "0.18", default-features = false } pgvector = { version = "~0.4", default-features = false, optional = true } rust_decimal = { version = "1", default-features = false, features = [ "std", ], optional = true } sea-orm-arrow = { version = "2.0.0-rc", path = "../sea-orm-arrow", default-features = false, optional = true } sea-orm-macros = { version = "~2.0.0-rc.20", path = "../sea-orm-macros", default-features = false, features = [ "strum", ] } sea-query = { version = "=1.0.0-rc.31", default-features = false, features = [ "thread-safe", "hashable-value", "backend-mysql", "backend-postgres", "backend-sqlite", "sea-orm", ] } sea-query-rusqlite = { version = "0.8.0-rc.14", optional = true } sea-schema-sync = { version = "0.17.0-rc.15", default-features = false, features = [ "sync", "discovery", "writer", "probe", ], optional = true } serde = { version = "1.0", default-features = false } serde_json = { version = "1.0", default-features = false, optional = true } strum = { version = "0.27", default-features = false } thiserror = { version = "2", default-features = false } time = { version = "0.3.36", default-features = false, optional = true } tracing = { version = "0.1", default-features = false, features = [ "attributes", "log", ] } url = { version = "2.2", default-features = false } uuid = { version = "1", default-features = false, optional = true } [dev-dependencies] dotenv = "0.15" maplit = { version = "1" } pretty_assertions = { version = "0.7" } sea-orm-sync = { path = ".", features = [ "debug-print", "mock", "postgres-array", "tests-cfg", ] } time = { version = "0.3.36", features = ["macros"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } uuid = { version = "1", features = ["v4"] } [features] debug-print = [] default = [ "sync", "macros", "with-json", "with-chrono", "with-rust_decimal", "with-uuid", "with-time", "sqlite-use-returning-for-3_35", ] entity-registry = ["inventory", "sea-orm-macros/entity-registry"] json-array = [ "postgres-array", ] # this does not actually enable postgres, but only a few traits to support array in sea-query macros = ["sea-orm-macros/derive"] mariadb-use-returning = [] mock = [] postgres-array = [ "sea-query/postgres-array", "sea-orm-macros/postgres-array", "sea-query-rusqlite?/postgres-array", ] postgres-vector = [ "pgvector", "sea-query/postgres-vector", "sea-query-rusqlite?/postgres-vector", ] proxy = ["serde_json", "serde/derive"] rbac = ["sea-query/audit", "macros"] rusqlite = ["sea-query-rusqlite/sea-orm", "sea-schema-sync/rusqlite"] schema-sync = ["sea-schema-sync"] sea-orm-internal = [] seaography = ["sea-orm-macros/seaography"] sqlite-no-row-value-before-3_15 = [] sqlite-use-returning-for-3_35 = [] sqlx-dep = [] sqlx-mysql = [] sqlx-postgres = [] sqlx-sqlite = [] sync = [] tests-cfg = ["serde/derive"] tests-features = [ "default", "rbac", "schema-sync", "with-arrow", "with-bigdecimal", ] tracing-spans = [] with-arrow = ["sea-orm-arrow", "sea-orm-macros/with-arrow"] with-bigdecimal = [ "bigdecimal", "sea-query/with-bigdecimal", "sea-query-rusqlite?/with-bigdecimal", "sea-orm-arrow?/with-bigdecimal", ] with-chrono = [ "chrono", "sea-query/with-chrono", "sea-query-rusqlite?/with-chrono", "sea-orm-arrow?/with-chrono", ] with-ipnetwork = [ "ipnetwork", "sea-query/with-ipnetwork", "sea-query-rusqlite?/with-ipnetwork", ] with-json = [ "serde_json", "sea-query/with-json", "sea-orm-macros/with-json", "chrono?/serde", "rust_decimal?/serde", "bigdecimal?/serde", "uuid?/serde", "time?/serde", "pgvector?/serde", "sea-query-rusqlite?/with-json", ] with-rust_decimal = [ "rust_decimal", "sea-query/with-rust_decimal", "sea-query-rusqlite?/with-rust_decimal", "sea-orm-arrow?/with-rust_decimal", ] with-time = [ "time", "sea-query/with-time", "sea-query-rusqlite?/with-time", "sea-orm-arrow?/with-time", ] with-uuid = ["uuid", "sea-query/with-uuid", "sea-query-rusqlite?/with-uuid"] [patch.crates-io] # sea-query = { path = "../sea-query" } ================================================ FILE: sea-orm-sync/README.md ================================================

SeaORM

SeaORM is a powerful ORM for building web services in Rust

[![crate](https://img.shields.io/crates/v/sea-orm.svg)](https://crates.io/crates/sea-orm) [![build status](https://github.com/SeaQL/sea-orm/actions/workflows/rust.yml/badge.svg)](https://github.com/SeaQL/sea-orm/actions/workflows/rust.yml) [![GitHub stars](https://img.shields.io/github/stars/SeaQL/sea-orm.svg?style=social&label=Star&maxAge=1)](https://github.com/SeaQL/sea-orm/stargazers/)
Support us with a ⭐ !
# 🐚 SeaORM [中文文档](https://github.com/SeaQL/sea-orm/blob/master/README-zh.md) ### Advanced Relations Model complex relationships 1-1, 1-N, M-N, and even self-referential in a high-level, conceptual way. ### Familiar Concepts Inspired by popular ORMs in the Ruby, Python, and Node.js ecosystem, SeaORM offers a developer experience that feels instantly recognizable. ### Feature Rich SeaORM is a batteries-included ORM with filters, pagination, and nested queries to accelerate building REST, GraphQL, and gRPC APIs. ### Production Ready With 250k+ weekly downloads, SeaORM is production-ready, trusted by startups and enterprises worldwide. ## Getting Started [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) Join our Discord server to chat with others! + [Documentation](https://www.sea-ql.org/SeaORM) Integration examples: + [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) + [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) + [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example) + [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example) + [Loco Example](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_example) / [Loco REST Starter](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_starter) + [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) + [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) / [Rocket OpenAPI Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_okapi_example) + [Salvo Example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example) + [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example) + [Seaography Example (Bakery)](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) / [Seaography Example (Sakila)](https://github.com/SeaQL/seaography/tree/main/examples/sqlite) If you want a simple, clean example that fits in a single file that demonstrates the best of SeaORM, you can try: + [Quickstart](https://github.com/SeaQL/sea-orm/blob/master/examples/quickstart/src/main.rs) Let's have a quick walk through of the unique features of SeaORM. ## Expressive Entity format You don't have to write this by hand! Entity files can be generated from an existing database using `sea-orm-cli`, following is generated with `--entity-format dense` *(new in 2.0)*. ```rust mod user { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(unique)] pub email: String, #[sea_orm(has_one)] pub profile: HasOne, #[sea_orm(has_many)] pub posts: HasMany, } } mod post { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: i32, pub title: String, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub author: HasOne, #[sea_orm(has_many, via = "post_tag")] // M-N relation with junction pub tags: HasMany, } } ``` ## Smart Entity Loader The Entity Loader intelligently uses join for 1-1 and data loader for 1-N relations, eliminating the N+1 problem even when performing nested queries. ```rust // join paths: // user -> profile // user -> post // post -> post_tag -> tag let smart_user = user::Entity::load() .filter_by_id(42) // shorthand for .filter(user::COLUMN.id.eq(42)) .with(profile::Entity) // 1-1 uses join .with((post::Entity, tag::Entity)) // 1-N uses data loader .one(db) ? .unwrap(); // 3 queries are executed under the hood: // 1. SELECT FROM user JOIN profile WHERE id = $ // 2. SELECT FROM post WHERE user_id IN (..) // 3. SELECT FROM tag JOIN post_tag WHERE post_id IN (..) smart_user == user::ModelEx { id: 42, name: "Bob".into(), email: "bob@sea-ql.org".into(), profile: HasOne::Loaded( profile::ModelEx { picture: "image.jpg".into(), } .into(), ), posts: HasMany::Loaded(vec![post::ModelEx { title: "Nice weather".into(), tags: HasMany::Loaded(vec![tag::ModelEx { tag: "sunny".into(), }]), }]), }; ``` ## ActiveModel: nested persistence made simple Persist an entire object graph: user, profile (1-1), posts (1-N), and tags (M-N) in a single operation using a fluent builder API. SeaORM automatically determines the dependencies and inserts or deletes objects in the correct order. ```rust // this creates the nested object as shown above: let user = user::ActiveModel::builder() .set_name("Bob") .set_email("bob@sea-ql.org") .set_profile(profile::ActiveModel::builder().set_picture("image.jpg")) .add_post( post::ActiveModel::builder() .set_title("Nice weather") .add_tag(tag::ActiveModel::builder().set_tag("sunny")), ) .save(db) ?; ``` ## Schema first or Entity first? Your choice SeaORM provides a powerful migration system that lets you create tables, modify schemas, and seed data with ease. With SeaORM 2.0, you also get a first-class [Entity First Workflow](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/): simply define new entities or add columns to existing ones, and SeaORM will automatically detect the changes and create the new tables, columns, unique keys, and foreign keys. ```rust // SeaORM resolves foreign key dependencies and creates the tables in topological order. // Requires the `entity-registry` and `schema-sync` feature flags. db.get_schema_registry("my_crate::entity::*").sync(db); ``` ## Ergonomic Raw SQL Let SeaORM handle 95% of your transactional queries. For the remaining cases that are too complex to express, SeaORM still offers convenient support for writing raw SQL. ```rust let user = Item { name: "Bob" }; // nested parameter access let ids = [2, 3, 4]; // expanded by the `..` operator let user: Option = user::Entity::find() .from_raw_sql(raw_sql!( Sqlite, r#"SELECT "id", "name" FROM "user" WHERE "name" LIKE {user.name} AND "id" in ({..ids}) "# )) .one(db) ?; ``` ## Synchronous Support [`sea-orm-sync`](https://crates.io/crates/sea-orm-sync) provides the full SeaORM API without requiring an runtime, making it ideal for lightweight CLI programs with SQLite. See the [quickstart example](https://github.com/SeaQL/sea-orm/blob/master/sea-orm-sync/examples/quickstart/src/main.rs) for usage. ## Basics ### Select SeaORM models 1-N and M-N relationships at the Entity level, letting you traverse many-to-many links through a junction table in a single call. ```rust // find all models let cakes: Vec = Cake::find().all(db)?; // find and filter let chocolate: Vec = Cake::find() .filter(Cake::COLUMN.name.contains("chocolate")) .all(db) ?; // find one model let cheese: Option = Cake::find_by_id(1).one(db)?; let cheese: cake::Model = cheese.unwrap(); // find related models (lazy) let fruit: Option = cheese.find_related(Fruit).one(db)?; // find related models (eager): for 1-1 relations let cake_with_fruit: Vec<(cake::Model, Option)> = Cake::find().find_also_related(Fruit).all(db)?; // find related models (eager): works for both 1-N and M-N relations let cake_with_fillings: Vec<(cake::Model, Vec)> = Cake::find() .find_with_related(Filling) // for M-N relations, two joins are performed .all(db) // rows are automatically consolidated by left entity ?; ``` ### Nested Select Partial models prevent overfetching by letting you querying only the fields you need; it also makes writing deeply nested relational queries simple. ```rust use sea_orm::DerivePartialModel; #[derive(DerivePartialModel)] #[sea_orm(entity = "cake::Entity")] struct CakeWithFruit { id: i32, name: String, #[sea_orm(nested)] fruit: Option, // this can be a regular or another partial model } let cakes: Vec = Cake::find() .left_join(fruit::Entity) // no need to specify join condition .into_partial_model() // only the columns in the partial model will be selected .all(db) ?; ``` ### Insert SeaORM's ActiveModel lets you work directly with Rust data structures and persist them through a simple API. It's easy to insert large batches of rows from different data sources. ```rust let apple = fruit::ActiveModel { name: Set("Apple".to_owned()), ..Default::default() // no need to set primary key }; let pear = fruit::ActiveModel { name: Set("Pear".to_owned()), ..Default::default() }; // insert one: Active Record style let apple = apple.insert(db)?; apple.id == 1; // insert one: repository style let result = Fruit::insert(apple).exec(db)?; result.last_insert_id == 1; // insert many returning last insert id let result = Fruit::insert_many([apple, pear]).exec(db)?; result.last_insert_id == Some(2); ``` ### Insert (advanced) You can take advantage of database specific features to perform upsert and idempotent insert. ```rust // insert many with returning (if supported by database) let models: Vec = Fruit::insert_many([apple, pear]) .exec_with_returning(db) ?; models[0] == fruit::Model { id: 1, // database assigned value name: "Apple".to_owned(), cake_id: None, }; // insert with ON CONFLICT on primary key do nothing, with MySQL specific polyfill let result = Fruit::insert_many([apple, pear]) .on_conflict_do_nothing() .exec(db) ?; matches!(result, TryInsertResult::Conflicted); ``` ### Update ActiveModel avoids race conditions by updating only the fields you've changed, never overwriting untouched columns. You can also craft complex bulk update queries with a fluent query building API. ```rust use sea_orm::sea_query::{Expr, Value}; let pear: Option = Fruit::find_by_id(1).one(db)?; let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); // update value of a single field // update one: only changed columns will be updated let pear: fruit::Model = pear.update(db)?; // update many: UPDATE "fruit" SET "cake_id" = "cake_id" + 2 // WHERE "fruit"."name" LIKE '%Apple%' Fruit::update_many() .col_expr(fruit::COLUMN.cake_id, fruit::COLUMN.cake_id.add(2)) .filter(fruit::COLUMN.name.contains("Apple")) .exec(db) ?; ``` ### Save You can perform "insert or update" operation with ActiveModel, making it easy to compose transactional operations. ```rust let banana = fruit::ActiveModel { id: NotSet, name: Set("Banana".to_owned()), ..Default::default() }; // create, because primary key `id` is `NotSet` let mut banana = banana.save(db)?; banana.id == Unchanged(2); banana.name = Set("Banana Mongo".to_owned()); // update, because primary key `id` is present let banana = banana.save(db)?; ``` ### Delete The same ActiveModel API consistent with insert and update. ```rust // delete one: Active Record style let orange: Option = Fruit::find_by_id(1).one(db)?; let orange: fruit::Model = orange.unwrap(); orange.delete(db)?; // delete one: repository style let orange = fruit::ActiveModel { id: Set(2), ..Default::default() }; fruit::Entity::delete(orange).exec(db)?; // delete many: DELETE FROM "fruit" WHERE "fruit"."name" LIKE '%Orange%' fruit::Entity::delete_many() .filter(fruit::COLUMN.name.contains("Orange")) .exec(db) ?; ``` ### Raw SQL Query The `raw_sql!` macro is like the `format!` macro but without the risk of SQL injection. It supports nested parameter interpolation, array and tuple expansion, and even repeating group, offering great flexibility in crafting complex queries. ```rust #[derive(FromQueryResult)] struct CakeWithBakery { name: String, #[sea_orm(nested)] bakery: Option, } #[derive(FromQueryResult)] struct Bakery { #[sea_orm(alias = "bakery_name")] name: String, } let cake_ids = [2, 3, 4]; // expanded by the `..` operator // can use many APIs with raw SQL, including nested select let cake: Option = CakeWithBakery::find_by_statement(raw_sql!( Sqlite, r#"SELECT "cake"."name", "bakery"."name" AS "bakery_name" FROM "cake" LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id" WHERE "cake"."id" IN ({..cake_ids})"# )) .one(db) ?; ``` ## 🧭 Seaography: instant GraphQL API [Seaography](https://github.com/SeaQL/seaography) is a GraphQL framework built for SeaORM. Seaography allows you to build GraphQL resolvers quickly. With just a few commands, you can launch a fullly-featured GraphQL server from SeaORM entities, complete with filter, pagination, relational queries and mutations! Look at the [Seaography Example](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) to learn more. ## 🖥️ SeaORM Pro: Professional Admin Panel [SeaORM Pro](https://github.com/SeaQL/sea-orm-pro/) is an admin panel solution allowing you to quickly and easily launch an admin panel for your application - frontend development skills not required, but certainly nice to have! SeaORM Pro has been updated to support the latest features in SeaORM 2.0. Features: + Full CRUD + Built on React + GraphQL + Built-in GraphQL resolver + Customize the UI with TOML config + Role Based Access Control *(new in 2.0)* Read the [Getting Started](https://www.sea-ql.org/sea-orm-pro/docs/install-and-config/getting-started/) guide to learn more. ![](https://raw.githubusercontent.com/SeaQL/sea-orm/refs/heads/master/docs/sea-orm-pro-dark.png#gh-dark-mode-only) ![](https://raw.githubusercontent.com/SeaQL/sea-orm/refs/heads/master/docs/sea-orm-pro-light.png#gh-light-mode-only) ## SQL Server Support [SQL Server for SeaORM](https://www.sea-ql.org/SeaORM-X/) offers the same SeaORM API for MSSQL. We ported all test cases and examples, complemented by MSSQL specific documentation. If you are building enterprise software, you can [request commercial access](https://forms.office.com/r/1MuRPJmYBR). It is currently based on SeaORM 1.0, but we will offer free upgrade to existing users when SeaORM 2.0 is finalized. ## Releases SeaORM 2.0 has reached its release candidate phase. We'd love for you to try it out and help shape the final release by [sharing your feedback](https://github.com/SeaQL/sea-orm/discussions/). + [Change Log](https://github.com/SeaQL/sea-orm/tree/master/CHANGELOG.md) SeaORM 2.0 is shaping up to be our most significant release yet - with a few breaking changes, plenty of enhancements, and a clear focus on developer experience. + [A Sneak Peek at SeaORM 2.0](https://www.sea-ql.org/blog/2025-09-16-sea-orm-2.0/) + [SeaORM 2.0: A closer look](https://www.sea-ql.org/blog/2025-09-24-sea-orm-2.0/) + [Role Based Access Control in SeaORM 2.0](https://www.sea-ql.org/blog/2025-09-30-sea-orm-rbac/) + [Seaography 2.0: A Powerful and Extensible GraphQL Framework](https://www.sea-ql.org/blog/2025-10-08-seaography/) + [SeaORM 2.0: New Entity Format](https://www.sea-ql.org/blog/2025-10-20-sea-orm-2.0/) + [SeaORM 2.0: Entity First Workflow](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/) + [SeaORM 2.0: Strongly-Typed Column](https://www.sea-ql.org/blog/2025-11-11-sea-orm-2.0/) + [What's new in SeaORM Pro 2.0](https://www.sea-ql.org/blog/2025-11-21-whats-new-in-seaormpro-2.0/) + [SeaORM 2.0: Nested ActiveModel](https://www.sea-ql.org/blog/2025-11-25-sea-orm-2.0/) + [A walk-through of SeaORM 2.0](https://www.sea-ql.org/blog/2025-12-05-sea-orm-2.0/) + [How we made SeaORM synchronous](https://www.sea-ql.org/blog/2025-12-12-sea-orm-2.0/) + [SeaORM 2.0 Migration Guide](https://www.sea-ql.org/blog/2026-01-12-sea-orm-2.0/) If you make extensive use of SeaQuery, we recommend checking out our blog post on SeaQuery 1.0 release: + [The road to SeaQuery 1.0](https://www.sea-ql.org/blog/2025-08-30-sea-query-1.0/) ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. We invite you to participate, contribute and together help build Rust's future. A big shout out to our contributors! [![Contributors](https://opencollective.com/sea-orm/contributors.svg?width=1000&button=false)](https://github.com/SeaQL/sea-orm/graphs/contributors) ## Who's using SeaORM? Here is a short list of awesome open source software built with SeaORM. Feel free to [submit yours](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#built-with-seaorm)! | Project | GitHub | Tagline | |---------|--------|---------| | [Zed](https://github.com/zed-industries/zed) | ![GitHub stars](https://img.shields.io/github/stars/zed-industries/zed.svg?style=social) | A high-performance, multiplayer code editor | | [OpenObserve](https://github.com/openobserve/openobserve) | ![GitHub stars](https://img.shields.io/github/stars/openobserve/openobserve.svg?style=social) | Open-source observability platform | | [RisingWave](https://github.com/risingwavelabs/risingwave) | ![GitHub stars](https://img.shields.io/github/stars/risingwavelabs/risingwave.svg?style=social) | Stream processing and management platform | | [LLDAP](https://github.com/nitnelave/lldap) | ![GitHub stars](https://img.shields.io/github/stars/nitnelave/lldap.svg?style=social) | A light LDAP server for user management | | [Warpgate](https://github.com/warp-tech/warpgate) | ![GitHub stars](https://img.shields.io/github/stars/warp-tech/warpgate.svg?style=social) | Smart SSH bastion that works with any SSH client | | [Svix](https://github.com/svix/svix-webhooks) | ![GitHub stars](https://img.shields.io/github/stars/svix/svix-webhooks.svg?style=social) | The enterprise ready webhooks service | | [Ryot](https://github.com/IgnisDa/ryot) | ![GitHub stars](https://img.shields.io/github/stars/ignisda/ryot.svg?style=social) | The only self hosted tracker you will ever need | | [Lapdev](https://github.com/lapce/lapdev) | ![GitHub stars](https://img.shields.io/github/stars/lapce/lapdev.svg?style=social) | Self-hosted remote development enviroment | | [System Initiative](https://github.com/systeminit/si) | ![GitHub stars](https://img.shields.io/github/stars/systeminit/si.svg?style=social) | DevOps Automation Platform | | [OctoBase](https://github.com/toeverything/OctoBase) | ![GitHub stars](https://img.shields.io/github/stars/toeverything/OctoBase.svg?style=social) | A light-weight, scalable, offline collaborative data backend | ## Sponsorship [SeaQL.org](https://www.sea-ql.org/) is an independent open-source organization run by passionate developers. If you feel generous, a small donation via [GitHub Sponsor](https://github.com/sponsors/SeaQL) will be greatly appreciated, and goes a long way towards sustaining the organization. ### Gold Sponsors
[QDX](https://qdx.co/) pioneers quantum dynamics-powered drug discovery, leveraging AI and supercomputing to accelerate molecular modeling. We're immensely grateful to QDX for sponsoring the development of SeaORM, the SQL toolkit that powers their data intensive applications. ### Silver Sponsors We're grateful to our silver sponsors: Digital Ocean, for sponsoring our servers. And JetBrains, for sponsoring our IDE.
## Mascot A friend of Ferris, Terres the hermit crab is the official mascot of SeaORM. His hobby is collecting shells. Terres ## 🦀 Rustacean Sticker Pack The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Sticker Pack Contents: + Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography + Mascots: Ferris the Crab x 3, Terres the Hermit Crab + The Rustacean wordmark [Support SeaQL and get a Sticker Pack!](https://www.sea-ql.org/sticker-pack/) All proceeds contributes directly to the ongoing development of SeaQL projects.
Rustacean Sticker Pack by SeaQL ================================================ FILE: sea-orm-sync/examples/parquet_example/Cargo.toml ================================================ [workspace] # A separate workspace [package] edition = "2024" name = "sea-orm-parquet-example" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] env_logger = { version = "0.11" } fastrand = "2" log = { version = "0.4" } parquet = { version = "58", default-features = false, features = ["arrow"] } [dependencies.sea-orm-sync] features = [ "rusqlite", "with-arrow", "with-chrono", "with-rust_decimal", ] path = "../../" ================================================ FILE: sea-orm-sync/examples/parquet_example/src/main.rs ================================================ use sea_orm::{ ArrowSchema, entity::*, prelude::{ChronoUtc, Decimal}, sea_query::prelude::chrono::Timelike, }; use log::info; mod measurement { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "measurement", arrow_schema)] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub recorded_at: ChronoDateTimeUtc, pub sensor_id: i32, pub temperature: f64, #[sea_orm(column_type = "Decimal(Some((10, 4)))")] pub voltage: Decimal, } impl ActiveModelBehavior for ActiveModel {} } fn main() -> Result<(), Box> { let env = env_logger::Env::default().filter_or("RUST_LOG", "info,sea_orm=info,sqlx=warn"); env_logger::Builder::from_env(env).init(); // ----------------------------------------------------------------------- // Step 1: Generate 100 random rows // ----------------------------------------------------------------------- let base_ts = ChronoUtc::now(); let base_ts = base_ts .with_nanosecond((base_ts.nanosecond() / 1000) * 1000) .unwrap(); // truncate to microsecond let mut rng = fastrand::Rng::new(); let models: Vec = (1..=100) .map(|i| { let offset = std::time::Duration::from_secs(rng.u64(0..86_400)); let ts = base_ts + offset; let sensor_id = rng.i32(100..110); let temperature = -10.0 + rng.f64() * 50.0; // -10 .. 40 °C let voltage_raw = 30000 + rng.i64(0..5000); // 3.0000 .. 3.5000 measurement::ActiveModel { id: Set(i), recorded_at: Set(ts), sensor_id: Set(sensor_id), temperature: Set(temperature), voltage: Set(Decimal::new(voltage_raw, 4)), } }) .collect(); let schema = measurement::Entity::arrow_schema(); info!("Arrow schema: {schema:?}"); // ----------------------------------------------------------------------- // Step 2: Convert to Arrow RecordBatch and write to Parquet // ----------------------------------------------------------------------- let batch = measurement::ActiveModel::to_arrow(&models, &schema)?; info!( "RecordBatch: {} rows, {} columns", batch.num_rows(), batch.num_columns() ); let parquet_path = "measurements.parquet"; { let file = std::fs::File::create(parquet_path)?; let mut writer = parquet::arrow::ArrowWriter::try_new(file, schema.into(), None)?; writer.write(&batch)?; writer.close()?; } info!("Wrote Parquet file: {parquet_path}"); // ----------------------------------------------------------------------- // Step 3: Read the Parquet file back into a RecordBatch // ----------------------------------------------------------------------- let file = std::fs::File::open(parquet_path)?; let reader = parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder::try_new(file)?.build()?; let batches: Vec<_> = reader.collect::>()?; info!("Read {} batch(es) from Parquet", batches.len()); let read_batch = &batches[0]; assert_eq!(read_batch.num_rows(), 100); // Convert back to ActiveModels let restored = measurement::ActiveModel::from_arrow(read_batch)?; info!("Restored {} ActiveModels from Parquet", restored.len()); for (original, restored) in models.iter().zip(restored.iter()) { assert_eq!(original, restored, "Roundtrip mismatch"); } info!("Parquet roundtrip verified: all rows match."); // ----------------------------------------------------------------------- // Step 4: Dump into SQLite // ----------------------------------------------------------------------- match std::fs::remove_file("measurements.sqlite") { Ok(_) => (), Err(e) if e.kind() == std::io::ErrorKind::NotFound => (), Err(e) => panic!("Failed to remove file: {e}"), } let db = &sea_orm::Database::connect("sqlite://measurements.sqlite")?; db.get_schema_builder() .register(measurement::Entity) .apply(db)?; info!("SQLite schema created."); measurement::Entity::insert_many(restored).exec(db)?; info!("Inserted all rows into SQLite."); info!("Done!"); Ok(()) } ================================================ FILE: sea-orm-sync/examples/pi_spigot/Cargo.toml ================================================ [package] edition = "2024" name = "sea-orm-pi-spigot" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] clap = { version = "4", features = ["derive"] } serde = { version = "1", features = ["derive"] } serde_json = { version = "1" } [dependencies.sea-orm-sync] features = ["rusqlite", "with-json", "schema-sync"] path = "../../" ================================================ FILE: sea-orm-sync/examples/pi_spigot/README.md ================================================ # Pi Spigot Algorithm Resumable Program Example This example shows how easy it is to add SQLite-backed checkpointing to a long-running computation using SeaORM. The idea applies to any stateful program: batch jobs, data pipelines, simulations: anything you want to pause and resume. We use the Rabinowitz-Wagon pi spigot algorithm as the example workload. It streams decimal digits of pi one at a time, making it a perfect fit for demonstrating incremental persistence. ## The Pattern: State Machine + Serialization Any computation that can be modeled as a state machine can be made resumable. The recipe has four parts: ``` new() → initialize fresh state step() → advance one iteration, mutating &mut self finalize() → flush any buffered output to_state() → serialize self into a database row from_state() → deserialize a database row back into self ``` ### Step 1: Define your state as a SeaORM entity Map every mutable field to a column: ```rust pub mod state { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "state")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub digits: u32, // identifies this computation pub boxes: JsonVec, // algorithm working memory pub i: u32, // current iteration pub nines: u32, // buffered 9s pub predigit: u8, // held digit pub have_predigit: bool, pub count: u32, // digits emitted so far #[sea_orm(column_type = "Text")] pub result: String, // emitted digits } } ``` Complex types like `Vec` are stored as JSON columns via `FromJsonQueryResult`. ### Step 2: Write `to_state` / `from_state` These convert between your in-memory struct and the entity model: ```rust fn to_state(&self, i: u32) -> state::Model { state::Model { digits: self.digits, boxes: state::JsonVec(self.boxes.clone()), i, nines: self.nines, // ... every field } } fn from_state(s: state::Model) -> Self { Self { digits: s.digits, boxes: s.boxes.0, nines: s.nines, // ... every field } } ``` ### Step 3: Checkpoint with transactions Inside your main loop, periodically save state. Use a transaction so the checkpoint is atomic: either everything is saved or nothing is: ```rust if self.count % checkpoint_interval == 0 { let txn = db.begin()?; state::Entity::delete_by_id(self.digits).exec(&txn)?; self.to_state(i + 1).into_active_model().insert(&txn)?; txn.commit()?; } ``` ### Step 4: Resume on startup Check for an existing checkpoint. If found, reconstruct from it; otherwise start fresh: ```rust pub fn resume(db: &DatabaseConnection, digits: u32) -> Result { db.get_schema_builder() .register(state::Entity) .sync(db)?; // creates table if it doesn't exist match state::Entity::find_by_id(digits).one(db)? { Some(s) => Ok(Self::from_state(s)), None => Ok(Self::new(digits)), } } ``` Note that `get_schema_builder().sync()` creates the table from the entity definition automatically: no migrations needed. ## Running the example ```sh # Compute 10000 digits of pi (checkpoints every 100 digits to pi.sqlite) cargo run -- --digits 10000 # Press Ctrl-C at any time, then run again: it resumes from the last checkpoint cargo run -- --digits 10000 # Use in-memory SQLite (no persistence, useful for testing) cargo run -- --digits 1000 --db "sqlite::memory:" ``` ## Running the tests ```sh cargo test ``` The tests verify correctness against a known 1000-digit reference, including a three-phase checkpoint/resume test: checkpoint at iteration 100, resume and checkpoint again at 500, resume and finish at 1000. ================================================ FILE: sea-orm-sync/examples/pi_spigot/bdigits.html ================================================
3.1415926535 8979323846 2643383279 5028841971 6939937510
  5820974944 5923078164 0628620899 8628034825 3421170679
  8214808651 3282306647 0938446095 5058223172 5359408128
  4811174502 8410270193 8521105559 6446229489 5493038196
  4428810975 6659334461 2847564823 3786783165 2712019091
  4564856692 3460348610 4543266482 1339360726 0249141273
  7245870066 0631558817 4881520920 9628292540 9171536436
  7892590360 0113305305 4882046652 1384146951 9415116094
  3305727036 5759591953 0921861173 8193261179 3105118548
  0744623799 6274956735 1885752724 8912279381 8301194912

  9833673362 4406566430 8602139494 6395224737 1907021798
  6094370277 0539217176 2931767523 8467481846 7669405132
  0005681271 4526356082 7785771342 7577896091 7363717872
  1468440901 2249534301 4654958537 1050792279 6892589235
  4201995611 2129021960 8640344181 5981362977 4771309960
  5187072113 4999999837 2978049951 0597317328 1609631859
  5024459455 3469083026 4252230825 3344685035 2619311881
  7101000313 7838752886 5875332083 8142061717 7669147303
  5982534904 2875546873 1159562863 8823537875 9375195778
  1857780532 1712268066 1300192787 6611195909 2164201989

  3809525720 1065485863 2788659361 5338182796 8230301952
  0353018529 6899577362 2599413891 2497217752 8347913151
  5574857242 4541506959 5082953311 6861727855 8890750983
  8175463746 4939319255 0604009277 0167113900 9848824012
  8583616035 6370766010 4710181942 9555961989 4676783744
  9448255379 7747268471 0404753464 6208046684 2590694912
  9331367702 8989152104 7521620569 6602405803 8150193511
  2533824300 3558764024 7496473263 9141992726 0426992279
  6782354781 6360093417 2164121992 4586315030 2861829745
  5570674983 8505494588 5869269956 9092721079 7509302955

  3211653449 8720275596 0236480665 4991198818 3479775356
  6369807426 5425278625 5181841757 4672890977 7727938000
  8164706001 6145249192 1732172147 7235014144 1973568548
  1613611573 5255213347 5741849468 4385233239 0739414333
  4547762416 8625189835 6948556209 9219222184 2725502542
  5688767179 0494601653 4668049886 2723279178 6085784383
  8279679766 8145410095 3883786360 9506800642 2512520511
  7392984896 0841284886 2694560424 1965285022 2106611863
  0674427862 2039194945 0471237137 8696095636 4371917287
  4677646575 7396241389 0865832645 9958133904 7802759009

  9465764078 9512694683 9835259570 9825822620 5224894077
  2671947826 8482601476 9909026401 3639443745 5305068203
  4962524517 4939965143 1429809190 6592509372 2169646151
  5709858387 4105978859 5977297549 8930161753 9284681382
  6868386894 2774155991 8559252459 5395943104 9972524680
  8459872736 4469584865 3836736222 6260991246 0805124388
  4390451244 1365497627 8079771569 1435997700 1296160894
  4169486855 5848406353 4220722258 2848864815 8456028506
  0168427394 5226746767 8895252138 5225499546 6672782398
  6456596116 3548862305 7745649803 5593634568 1743241125

  1507606947 9451096596 0940252288 7971089314 5669136867
  2287489405 6010150330 8617928680 9208747609 1782493858
  9009714909 6759852613 6554978189 3129784821 6829989487
  2265880485 7564014270 4775551323 7964145152 3746234364
  5428584447 9526586782 1051141354 7357395231 1342716610
  2135969536 2314429524 8493718711 0145765403 5902799344
  0374200731 0578539062 1983874478 0847848968 3321445713
  8687519435 0643021845 3191048481 0053706146 8067491927
  8191197939 9520614196 6342875444 0643745123 7181921799
  9839101591 9561814675 1426912397 4894090718 6494231961

  5679452080 9514655022 5231603881 9301420937 6213785595
  6638937787 0830390697 9207734672 2182562599 6615014215
  0306803844 7734549202 6054146659 2520149744 2850732518
  6660021324 3408819071 0486331734 6496514539 0579626856
  1005508106 6587969981 6357473638 4052571459 1028970641
  4011097120 6280439039 7595156771 5770042033 7869936007
  2305587631 7635942187 3125147120 5329281918 2618612586
  7321579198 4148488291 6447060957 5270695722 0917567116
  7229109816 9091528017 3506712748 5832228718 3520935396
  5725121083 5791513698 8209144421 0067510334 6711031412

  6711136990 8658516398 3150197016 5151168517 1437657618
  3515565088 4909989859 9823873455 2833163550 7647918535
  8932261854 8963213293 3089857064 2046752590 7091548141
  6549859461 6371802709 8199430992 4488957571 2828905923
  2332609729 9712084433 5732654893 8239119325 9746366730
  5836041428 1388303203 8249037589 8524374417 0291327656
  1809377344 4030707469 2112019130 2033038019 7621101100
  4492932151 6084244485 9637669838 9522868478 3123552658
  2131449576 8572624334 4189303968 6426243410 7732269780
  2807318915 4411010446 8232527162 0105265227 2111660396

  6655730925 4711055785 3763466820 6531098965 2691862056
  4769312570 5863566201 8558100729 3606598764 8611791045
  3348850346 1136576867 5324944166 8039626579 7877185560
  8455296541 2665408530 6143444318 5867697514 5661406800
  7002378776 5913440171 2749470420 5622305389 9456131407
  1127000407 8547332699 3908145466 4645880797 2708266830
  6343285878 5698305235 8089330657 5740679545 7163775254
  2021149557 6158140025 0126228594 1302164715 5097925923
  0990796547 3761255176 5675135751 7829666454 7791745011
  2996148903 0463994713 2962107340 4375189573 5961458901

  9389713111 7904297828 5647503203 1986915140 2870808599
  0480109412 1472213179 4764777262 2414254854 5403321571
  8530614228 8137585043 0633217518 2979866223 7172159160
  7716692547 4873898665 4949450114 6540628433 6639379003
  9769265672 1463853067 3609657120 9180763832 7166416274
  8888007869 2560290228 4721040317 2118608204 1900042296
  6171196377 9213375751 1495950156 6049631862 9472654736
  4252308177 0367515906 7350235072 8354056704 0386743513
  6222247715 8915049530 9844489333 0963408780 7693259939
  7805419341 4473774418 4263129860 8099888687 4132604721

  5695162396 5864573021 6315981931 9516735381 2974167729
  4786724229 2465436680 0980676928 2382806899 6400482435
  4037014163 1496589794 0924323789 6907069779 4223625082
  2168895738 3798623001 5937764716 5122893578 6015881617
  5578297352 3344604281 5126272037 3431465319 7777416031
  9906655418 7639792933 4419521541 3418994854 4473456738
  3162499341 9131814809 2777710386 3877343177 2075456545
  3220777092 1201905166 0962804909 2636019759 8828161332
  3166636528 6193266863 3606273567 6303544776 2803504507
  7723554710 5859548702 7908143562 4014517180 6246436267

  9456127531 8134078330 3362542327 8394497538 2437205835
  3114771199 2606381334 6776879695 9703098339 1307710987
  0408591337 4641442822 7726346594 7047458784 7787201927
  7152807317 6790770715 7213444730 6057007334 9243693113
  8350493163 1284042512 1925651798 0694113528 0131470130
  4781643788 5185290928 5452011658 3934196562 1349143415
  9562586586 5570552690 4965209858 0338507224 2648293972
  8584783163 0577775606 8887644624 8246857926 0395352773
  4803048029 0058760758 2510474709 1643961362 6760449256
  2742042083 2085661190 6254543372 1315359584 5068772460

  2901618766 7952406163 4252257719 5429162991 9306455377
  9914037340 4328752628 8896399587 9475729174 6426357455
  2540790914 5135711136 9410911939 3251910760 2082520261
  8798531887 7058429725 9167781314 9699009019 2116971737
  2784768472 6860849003 3770242429 1651300500 5168323364
  3503895170 2989392233 4517220138 1280696501 1784408745
  1960121228 5993716231 3017114448 4640903890 6449544400
  6198690754 8516026327 5052983491 8740786680 8818338510
  2283345085 0486082503 9302133219 7155184306 3545500766
  8282949304 1377655279 3975175461 3953984683 3936383047

  4611996653 8581538420 5685338621 8672523340 2830871123
  2827892125 0771262946 3229563989 8989358211 6745627010
  2183564622 0134967151 8819097303 8119800497 3407239610
  3685406643 1939509790 1906996395 5245300545 0580685501
  9567302292 1913933918 5680344903 9820595510 0226353536
  1920419947 4553859381 0234395544 9597783779 0237421617
  2711172364 3435439478 2218185286 2408514006 6604433258
  8856986705 4315470696 5747458550 3323233421 0730154594
  0516553790 6866273337 9958511562 5784322988 2737231989
  8757141595 7811196358 3300594087 3068121602 8764962867

  4460477464 9159950549 7374256269 0104903778 1986835938
  1465741268 0492564879 8556145372 3478673303 9046883834
  3634655379 4986419270 5638729317 4872332083 7601123029
  9113679386 2708943879 9362016295 1541337142 4892830722
  0126901475 4668476535 7616477379 4675200490 7571555278
  1965362132 3926406160 1363581559 0742202020 3187277605
  2772190055 6148425551 8792530343 5139844253 2234157623
  3610642506 3904975008 6562710953 5919465897 5141310348
  2276930624 7435363256 9160781547 8181152843 6679570611
  0861533150 4452127473 9245449454 2368288606 1340841486

  3776700961 2071512491 4043027253 8607648236 3414334623
  5189757664 5216413767 9690314950 1910857598 4423919862
  9164219399 4907236234 6468441173 9403265918 4044378051
  3338945257 4239950829 6591228508 5558215725 0310712570
  1266830240 2929525220 1187267675 6220415420 5161841634
  8475651699 9811614101 0029960783 8690929160 3028840026
  9104140792 8862150784 2451670908 7000699282 1206604183
  7180653556 7252532567 5328612910 4248776182 5829765157
  9598470356 2226293486 0034158722 9805349896 5022629174
  8788202734 2092222453 3985626476 6914905562 8425039127

  5771028402 7998066365 8254889264 8802545661 0172967026
  6407655904 2909945681 5065265305 3718294127 0336931378
  5178609040 7086671149 6558343434 7693385781 7113864558
  7367812301 4587687126 6034891390 9562009939 3610310291
  6161528813 8437909904 2317473363 9480457593 1493140529
  7634757481 1935670911 0137751721 0080315590 2485309066
  9203767192 2033229094 3346768514 2214477379 3937517034
  4366199104 0337511173 5471918550 4644902636 5512816228
  8244625759 1633303910 7225383742 1821408835 0865739177
  1509682887 4782656995 9957449066 1758344137 5223970968

  3408005355 9849175417 3818839994 4697486762 6551658276
  5848358845 3142775687 9002909517 0283529716 3445621296
  4043523117 6006651012 4120065975 5851276178 5838292041
  9748442360 8007193045 7618932349 2292796501 9875187212
  7267507981 2554709589 0455635792 1221033346 6974992356
  3025494780 2490114195 2123828153 0911407907 3860251522
  7429958180 7247162591 6685451333 1239480494 7079119153
  2673430282 4418604142 6363954800 0448002670 4962482017
  9289647669 7583183271 3142517029 6923488962 7668440323
  2609275249 6035799646 9256504936 8183609003 2380929345

  9588970695 3653494060 3402166544 3755890045 6328822505
  4525564056 4482465151 8754711962 1844396582 5337543885
  6909411303 1509526179 3780029741 2076651479 3942590298
  9695946995 5657612186 5619673378 6236256125 2163208628
  6922210327 4889218654 3648022967 8070576561 5144632046
  9279068212 0738837781 4233562823 6089632080 6822246801
  2248261177 1858963814 0918390367 3672220888 3215137556
  0037279839 4004152970 0287830766 7094447456 0134556417
  2543709069 7939612257 1429894671 5435784687 8861444581
  2314593571 9849225284 7160504922 1242470141 2147805734

  5510500801 9086996033 0276347870 8108175450 1193071412
  2339086639 3833952942 5786905076 4310063835 1983438934
  1596131854 3475464955 6978103829 3097164651 4384070070
  7360411237 3599843452 2516105070 2705623526 6012764848
  3084076118 3013052793 2054274628 6540360367 4532865105
  7065874882 2569815793 6789766974 2205750596 8344086973
  5020141020 6723585020 0724522563 2651341055 9240190274
  2162484391 4035998953 5394590944 0704691209 1409387001
  2645600162 3742880210 9276457931 0657922955 2498872758
  4610126483 6999892256 9596881592 0560010165 5256375679
================================================ FILE: sea-orm-sync/examples/pi_spigot/src/lib.rs ================================================ use sea_orm::{ ActiveModelTrait, DatabaseConnection, EntityTrait, IntoActiveModel, NotSet, Set, TransactionTrait, }; pub mod state { use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "state")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub digits: u32, pub boxes: JsonVec, pub i: u32, pub nines: u32, pub predigit: u8, pub have_predigit: bool, pub count: u32, #[sea_orm(column_type = "Text")] pub result: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] pub struct JsonVec(pub Vec); impl ActiveModelBehavior for ActiveModel {} } pub mod run_log { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "run_log")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub digits: u32, #[sea_orm(column_type = "Text")] pub pi_digits: String, } impl ActiveModelBehavior for ActiveModel {} } /// Tracks the mutable computation state of the spigot algorithm. pub struct PiSpigot { digits: u32, boxes: Vec, nines: u32, predigit: u8, have_predigit: bool, count: u32, result: String, start_i: u32, } impl PiSpigot { /// Create a new computation for the given number of decimal digits (after "3."). pub fn new(digits: u32) -> Self { let len = digits as usize * 10 / 3 + 1; Self { digits, boxes: vec![2u32; len], nines: 0, predigit: 0, have_predigit: false, count: 0, result: String::new(), start_i: 0, } } /// Load from a checkpoint if one exists, otherwise create fresh. pub fn resume(db: &DatabaseConnection, digits: u32) -> Result { db.get_schema_builder() .register(state::Entity) .register(run_log::Entity) .sync(db)?; match state::Entity::find_by_id(digits).one(db)? { Some(s) => { eprintln!( "Resuming from checkpoint (iteration {}, {} digits result)", s.i, s.count ); Ok(Self::from_state(s)) } None => Ok(Self::new(digits)), } } /// Compute all digits without database persistence. Returns the decimal /// digits of pi after "3." (e.g. "14159265..." for 8 digits). pub fn compute(mut self) -> String { for _ in 0..=self.digits { self.step(); } self.finalize(); self.result } /// Compute with database persistence, checkpointing every `checkpoint_interval` iterations. /// Returns the fractional digits of pi (after "3."). pub fn compute_with_db( mut self, db: &DatabaseConnection, checkpoint_interval: u32, ) -> Result { db.get_schema_builder() .register(state::Entity) .register(run_log::Entity) .sync(db)?; let mut run_log = run_log::ActiveModel { id: NotSet, digits: Set(self.digits), pi_digits: Set(String::new()), } .save(db)?; for i in self.start_i..=self.digits { self.step(); if checkpoint_interval > 0 && self.count > 0 && self.count % checkpoint_interval == 0 { let txn = db.begin()?; state::Entity::delete_by_id(self.digits).exec(&txn)?; self.to_state(i + 1).into_active_model().insert(&txn)?; run_log.pi_digits = Set(self.result.clone()); run_log = run_log.save(&txn)?; txn.commit()?; let start = self .result .len() .saturating_sub(checkpoint_interval as usize); eprintln!("[{}] {}", self.count, &self.result[start..]); } } self.finalize(); state::Entity::delete_by_id(self.digits).exec(db)?; run_log.pi_digits = Set(self.result.clone()); run_log.save(db)?; Ok(self.result) } /// Resume from a persisted state, recovering previously result digits. fn from_state(s: state::Model) -> Self { Self { digits: s.digits, boxes: s.boxes.0, nines: s.nines, predigit: s.predigit, have_predigit: s.have_predigit, count: s.count, result: s.result, start_i: s.i, } } /// Build a state model for checkpoint persistence. fn to_state(&self, i: u32) -> state::Model { state::Model { digits: self.digits, boxes: state::JsonVec(self.boxes.clone()), i, nines: self.nines, predigit: self.predigit, have_predigit: self.have_predigit, count: self.count, result: self.result.clone(), } } fn push_digit(&mut self, digit: u8) { self.result.push((b'0' + digit) as char); self.count += 1; } /// Run one iteration of the spigot algorithm, producing zero or more digits. fn step(&mut self) { let len = self.boxes.len(); let mut carry: u32 = 0; for j in (1..len).rev() { let j_u = j as u32; let x = self.boxes[j] * 10 + carry; self.boxes[j] = x % (2 * j_u + 1); carry = (x / (2 * j_u + 1)) * j_u; } let x = self.boxes[0] * 10 + carry; let q = (x / 10) as u8; self.boxes[0] = x % 10; if q == 9 { self.nines += 1; } else if q == 10 { if self.have_predigit { self.push_digit(self.predigit + 1); } else { self.push_digit(1); self.have_predigit = true; } for _ in 0..self.nines { self.push_digit(0); } self.predigit = 0; self.nines = 0; } else { if self.have_predigit { self.push_digit(self.predigit); } else { self.have_predigit = true; } self.predigit = q; for _ in 0..self.nines { self.push_digit(9); } self.nines = 0; } } /// Flush the final predigit and strip the leading "3" so the result /// contains only fractional digits (e.g. "14159..." not "314159..."). fn finalize(&mut self) { self.push_digit(self.predigit); for _ in 0..self.nines { self.push_digit(9); } self.nines = 0; // The spigot algorithm produces digits starting with 3; strip it. if self.result.starts_with('3') { self.result.remove(0); } } } #[cfg(test)] mod tests { use super::*; const PI_1000: &str = "\ 14159265358979323846264338327950288419716939937510\ 58209749445923078164062862089986280348253421170679\ 82148086513282306647093844609550582231725359408128\ 48111745028410270193852110555964462294895493038196\ 44288109756659334461284756482337867831652712019091\ 45648566923460348610454326648213393607260249141273\ 72458700660631558817488152092096282925409171536436\ 78925903600113305305488204665213841469519415116094\ 33057270365759591953092186117381932611793105118548\ 07446237996274956735188575272489122793818301194912\ 98336733624406566430860213949463952247371907021798\ 60943702770539217176293176752384674818467669405132\ 00056812714526356082778577134275778960917363717872\ 14684409012249534301465495853710507922796892589235\ 42019956112129021960864034418159813629774771309960\ 51870721134999999837297804995105973173281609631859\ 50244594553469083026425223082533446850352619311881\ 71010003137838752886587533208381420617177669147303\ 59825349042875546873115956286388235378759375195778\ 18577805321712268066130019278766111959092164201989"; #[test] fn test_compute_10_digits() { let result = PiSpigot::new(10).compute(); assert_eq!(&result[..10], &PI_1000[..10]); } #[test] fn test_compute_100_digits() { let result = PiSpigot::new(100).compute(); assert_eq!(&result[..100], &PI_1000[..100]); } #[test] fn test_compute_1000_digits() { let result = PiSpigot::new(1000).compute(); assert_eq!(&result[..1000], PI_1000); } #[test] fn test_compute_with_db() { let db = sea_orm::Database::connect("sqlite::memory:").unwrap(); let spigot = PiSpigot::new(100); let result = spigot.compute_with_db(&db, 10).unwrap(); assert_eq!(&result[..100], &PI_1000[..100]); } #[test] fn test_checkpoint_resume() { let db = sea_orm::Database::connect("sqlite::memory:").unwrap(); db.get_schema_builder() .register(state::Entity) .register(run_log::Entity) .sync(&db) .unwrap(); // Phase 1: step 0..100, checkpoint let mut spigot = PiSpigot::new(1000); for _ in 0..100 { spigot.step(); } spigot .to_state(100) .into_active_model() .insert(&db) .unwrap(); // Phase 2: resume, step 100..500, checkpoint again let mut spigot = PiSpigot::resume(&db, 1000).unwrap(); assert_eq!(spigot.start_i, 100); for _ in 100..500 { spigot.step(); } state::Entity::delete_by_id(1000u32).exec(&db).unwrap(); spigot .to_state(500) .into_active_model() .insert(&db) .unwrap(); // Phase 3: resume from second checkpoint, finish let resumed = PiSpigot::resume(&db, 1000).unwrap(); assert_eq!(resumed.start_i, 500); let result = resumed.compute_with_db(&db, 0).unwrap(); assert_eq!(result, PI_1000); } } ================================================ FILE: sea-orm-sync/examples/pi_spigot/src/main.rs ================================================ use clap::Parser; use sea_orm::Database; use sea_orm_pi_spigot::PiSpigot; #[derive(Parser)] #[command(name = "pi-spigot", about = "Compute digits of pi with checkpointing")] struct Cli { /// Number of decimal digits to compute (after "3.") #[arg(short, long, default_value_t = 100)] digits: u32, /// Checkpoint every N digits (0 to disable) #[arg(short, long, default_value_t = 100)] checkpoint: u32, /// SQLite database path for persistence #[arg(long, default_value = "sqlite://pi.sqlite")] db: String, } fn main() { let cli = Cli::parse(); let db = Database::connect(&cli.db).expect("Failed to connect to database"); let spigot = PiSpigot::resume(&db, cli.digits).expect("Failed to initialize"); println!("Computing {} decimal digits of pi...", cli.digits); let result = spigot .compute_with_db(&db, cli.checkpoint) .expect("Computation failed"); println!("Finished computing {} digits of pi.", cli.digits); println!("3.{result}"); } ================================================ FILE: sea-orm-sync/examples/quickstart/Cargo.toml ================================================ [package] edition = "2024" name = "sea-orm-quickstart" publish = false rust-version = "1.85.0" version = "0.1.0" [dependencies] env_logger = { version = "0.11" } log = { version = "0.4" } [dependencies.sea-orm-sync] features = [ "rusqlite", "debug-print", "entity-registry", "schema-sync", ] path = "../../" ================================================ FILE: sea-orm-sync/examples/quickstart/README.md ================================================ # SeaORM Quick Start This is a single file app that strives to minimize dependency and code noise. It uses an in-memory SQLite database so there is no setup. Just do `cargo run`! ================================================ FILE: sea-orm-sync/examples/quickstart/src/main.rs ================================================ use log::info; mod user { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(unique)] pub email: String, #[sea_orm(has_one)] pub profile: HasOne, #[sea_orm(has_many)] pub posts: HasMany, #[sea_orm(self_ref, via = "user_follower", from = "User", to = "Follower")] pub followers: HasMany, #[sea_orm(self_ref, via = "user_follower", reverse)] pub following: HasMany, } impl ActiveModelBehavior for ActiveModel {} } mod profile { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "profile")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub picture: String, #[sea_orm(unique)] pub user_id: i32, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub user: HasOne, } impl ActiveModelBehavior for ActiveModel {} } mod post { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub user_id: i32, pub title: String, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub author: HasOne, #[sea_orm(has_many)] pub comments: HasMany, #[sea_orm(has_many, via = "post_tag")] pub tags: HasMany, } impl ActiveModelBehavior for ActiveModel {} } mod comment { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "comment")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub comment: String, pub user_id: i32, pub post_id: i32, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub user: HasOne, #[sea_orm(belongs_to, from = "post_id", to = "id")] pub post: HasOne, } impl ActiveModelBehavior for ActiveModel {} } mod tag { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "tag")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, #[sea_orm(unique)] pub tag: String, #[sea_orm(has_many, via = "post_tag")] pub posts: HasMany, } impl ActiveModelBehavior for ActiveModel {} } mod post_tag { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "post_tag")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub post_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub tag_id: i32, #[sea_orm(belongs_to, from = "post_id", to = "id")] pub post: Option, #[sea_orm(belongs_to, from = "tag_id", to = "id")] pub tag: Option, } impl ActiveModelBehavior for ActiveModel {} } mod user_follower { use sea_orm::entity::prelude::*; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "user_follower")] pub struct Model { #[sea_orm(primary_key)] pub user_id: i32, #[sea_orm(primary_key)] pub follower_id: i32, #[sea_orm(belongs_to, from = "user_id", to = "id")] pub user: Option, #[sea_orm( belongs_to, relation_enum = "Follower", from = "follower_id", to = "id" )] pub follower: Option, } impl ActiveModelBehavior for ActiveModel {} } fn main() -> Result<(), sea_orm::DbErr> { ///// Part 0: Setup Environment ///// // This disables sqlx's logging and enables sea-orm's logging with parameter injection, // which is easier to debug. let env = env_logger::Env::default().filter_or("RUST_LOG", "info,sea_orm=debug,sqlx=warn"); env_logger::Builder::from_env(env).init(); use sea_orm::{entity::*, query::*}; // Use a SQLite in memory database so no setup needed. // SeaORM supports MySQL, Postgres, SQL Server as well. let db = &sea_orm::Database::connect("sqlite::memory:")?; // Populate this fresh database with tables. // // All entities defined in this crate are automatically registered // into the schema registry, regardless of which module they live in. // // The registry may also include entities from upstream crates, // so here we restrict it to entities defined in this crate only. // // The order of entity definitions does not matter. // SeaORM resolves foreign key dependencies automatically // and creates the tables in the correct order with their keys. db.get_schema_registry("sea_orm_quickstart::*").sync(db)?; info!("Schema created."); ///// Part 1: CRUD with nested 1-1 and 1-N relations ///// info!("Create user Bob with a profile:"); let bob = user::ActiveModel::builder() .set_name("Bob") .set_email("bob@sea-ql.org") .set_profile(profile::ActiveModel::builder().set_picture("Tennis")) .insert(db)?; info!("Find Bob by email:"); assert_eq!( bob, // this method is generated by #[sea_orm::model] on unique keys user::Entity::find_by_email("bob@sea-ql.org") .one(db)? .unwrap() ); info!("Query user with profile in a single query:"); let mut bob = user::Entity::load() .filter_by_id(bob.id) .with(profile::Entity) .one(db)? .expect("Not found"); assert_eq!(bob.name, "Bob"); assert_eq!(bob.profile.as_ref().unwrap().picture, "Tennis"); // Here we take ownership of the nested model, modify in place and save it info!("Update Bob's profile:"); bob.profile .take() .unwrap() .into_active_model() .set_picture("Landscape") .save(db)?; info!("Confirmed that it's been updated:"); assert_eq!( profile::Entity::find_by_user_id(bob.id).all(db)?[0].picture, "Landscape" ); // we don't have to set the `user_id` of the posts, they're automatically set to Bob info!("Bob wrote some posts:"); let mut bob = bob.into_active_model(); bob.posts .push( post::ActiveModel::builder() .set_title("Lorem ipsum dolor sit amet, consectetur adipiscing elit"), ) .push( post::ActiveModel::builder() .set_title("Ut enim ad minim veniam, quis nostrud exercitation"), ); bob.save(db)?; info!("Find Bob's profile and his posts:"); let bob = user::Entity::load() .filter(user::COLUMN.name.eq("Bob")) .with(profile::Entity) .with(post::Entity) .one(db)? .unwrap(); assert_eq!(bob.name, "Bob"); assert_eq!(bob.profile.as_ref().unwrap().picture, "Landscape"); assert!(bob.posts[0].title.starts_with("Lorem ipsum")); assert!(bob.posts[1].title.starts_with("Ut enim ad")); // It's actually fine to create user + profile the other way round. // SeaORM figures out the dependency and creates the user first. info!("Create a new user Alice:"); let alice = profile::ActiveModel::builder() .set_user( user::ActiveModel::builder() .set_name("Alice") .set_email("alice@rust-lang.org"), ) .set_picture("Park") .insert(db)? .user .unwrap(); // Not only can we insert new posts via the bob active model, // we can also add new comments to the posts. // SeaORM walks the document tree and figures out what's changed, // and perform the operation in one transaction. let mut bob = bob.into_active_model(); info!("Alice commented on Bob's post:"); bob.posts[0].comments.push( comment::ActiveModel::builder() .set_comment("nice post!") .set_user_id(alice.id), ); bob.posts[1].comments.push( comment::ActiveModel::builder() .set_comment("interesting!") .set_user_id(alice.id), ); let bob = bob.save(db)?; info!("Find all posts with author along with comments and who commented:"); let posts = post::Entity::load() .with(user::Entity) .with((comment::Entity, user::Entity)) .all(db)?; assert!(posts[0].title.starts_with("Lorem ipsum")); assert_eq!(posts[0].author.as_ref().unwrap().name, "Bob"); assert_eq!(posts[0].comments.len(), 1); assert_eq!(posts[0].comments[0].comment, "nice post!"); assert_eq!(posts[0].comments[0].user.as_ref().unwrap().name, "Alice"); assert!(posts[1].title.starts_with("Ut enim ad")); assert_eq!(posts[1].author.as_ref().unwrap().name, "Bob"); assert_eq!(posts[1].comments.len(), 1); assert_eq!(posts[1].comments[0].comment, "interesting!"); // Again, we can apply multiple changes in one operation, // the queries are executed inside a transaction. info!("Update post title and comment on first post:"); let mut post = posts[0].clone().into_active_model(); post.title = Set("Lorem ipsum dolor sit amet".into()); // shorten it post.comments[0].comment = Set("nice post! I learnt a lot".into()); post.save(db)?; info!("Confirm the post and comment is updated"); let post = post::Entity::load() .filter_by_id(posts[0].id) .with(comment::Entity) .one(db)? .unwrap(); assert_eq!(post.title, "Lorem ipsum dolor sit amet"); assert_eq!(post.comments[0].comment, "nice post! I learnt a lot"); // Comments belongs to post. They will be deleted first, otherwise the foreign key // would prevent the operation. info!("Delete the post along with all comments"); post.delete(db)?; assert!(post::Entity::find_by_id(posts[0].id).one(db)?.is_none()); ///// Part 2: managing M-N relations ///// // A unique feature of SeaORM is modelling many-to-many relations in a high level way info!("Insert one tag for later use"); let sunny = tag::ActiveModel::builder().set_tag("sunny").save(db)?; info!("Insert a new post with 2 tags"); let mut post = post::ActiveModel::builder() .set_title("A perfect day out") .set_user_id(alice.id) .add_tag(sunny.clone()) // an existing tag .add_tag(tag::ActiveModel::builder().set_tag("foodie")) // a new tag .save(db) // new tag will be created and associcated to the new post ?; let post_id = post.id.clone().unwrap(); { info!("get back the post and tags"); let post = post::Entity::load() .filter_by_id(post_id) .with(tag::Entity) .one(db)? .unwrap(); assert_eq!(post.title, "A perfect day out"); assert_eq!(post.tags.len(), 2); assert_eq!(post.tags[0].tag, "sunny"); assert_eq!(post.tags[1].tag, "foodie"); } info!("Add new tag to post"); post.tags .push(tag::ActiveModel::builder().set_tag("downtown")); let mut post = post.save(db)?; { info!("get back the post and tags"); let post = post::Entity::load() .filter_by_id(post_id) .with(tag::Entity) .one(db)? .unwrap(); assert_eq!(post.tags.len(), 3); assert_eq!(post.tags[0].tag, "sunny"); assert_eq!(post.tags[1].tag, "foodie"); assert_eq!(post.tags[2].tag, "downtown"); } info!("Update post title and remove a tag"); let mut tags = post.tags.take(); tags.as_mut_vec().remove(0); // it actually rained post.title = Set("Almost a perfect day out".into()); post.tags.replace_all(tags); // converting the field from append to replace would delete associations not in this list let post = post.save(db)?; { info!("get back the post and tags"); let post = post::Entity::load() .filter_by_id(post_id) .with(tag::Entity) .one(db)? .unwrap(); assert_eq!(post.tags.len(), 2); assert_eq!(post.title, "Almost a perfect day out"); assert_eq!(post.tags[0].tag, "foodie"); assert_eq!(post.tags[1].tag, "downtown"); } // only the association between post and tag is removed, // but the tag itself is not deleted info!("check that the tag sunny still exists"); assert!(tag::Entity::find_by_tag("sunny").one(db)?.is_some()); info!("cascade delete post, remove tag associations"); post.delete(db)?; ///// Part 3: self-referencing relations ///// info!("save a new user with a new profile"); let sam = user::ActiveModel::builder() .set_name("Sam") .set_email("sam@rustacean.net") .set_profile(profile::ActiveModel::builder().set_picture("Crab.jpg")) .save(db)?; // we can do it from left to right: user <- follower info!("Add follower to Alice"); let alice = alice.into_active_model().add_follower(bob).save(db)?; // we can also do it in reverse: user -> following info!("Sam starts following Alice"); sam.add_following(alice).save(db)?; info!("Query Alice's profile and followers"); let alice = user::Entity::load() .filter_by_email("alice@rust-lang.org") .with(profile::Entity) .with(user_follower::Entity) .with(user_follower::Entity::REVERSE) .one(db)? .unwrap(); assert_eq!(alice.name, "Alice"); assert_eq!(alice.profile.as_ref().unwrap().picture, "Park"); assert_eq!(alice.followers.len(), 2); assert_eq!(alice.followers[0].name, "Bob"); assert_eq!(alice.followers[1].name, "Sam"); Ok(()) } ================================================ FILE: sea-orm-sync/src/database/connection.rs ================================================ use crate::{ DbBackend, DbErr, ExecResult, QueryResult, Statement, StatementBuilder, TransactionError, }; /// The generic API for a database connection that can perform query or execute statements. /// It abstracts database connection and transaction pub trait ConnectionTrait { /// Get the database backend for the connection. This depends on feature flags enabled. fn get_database_backend(&self) -> DbBackend; /// Execute a [Statement] fn execute_raw(&self, stmt: Statement) -> Result; /// Execute a [QueryStatement] fn execute(&self, stmt: &S) -> Result { let db_backend = self.get_database_backend(); let stmt = db_backend.build(stmt); self.execute_raw(stmt) } /// Execute a unprepared [Statement] fn execute_unprepared(&self, sql: &str) -> Result; /// Execute a [Statement] and return a single row of `QueryResult` fn query_one_raw(&self, stmt: Statement) -> Result, DbErr>; /// Execute a [QueryStatement] and return a single row of `QueryResult` fn query_one(&self, stmt: &S) -> Result, DbErr> { let db_backend = self.get_database_backend(); let stmt = db_backend.build(stmt); self.query_one_raw(stmt) } /// Execute a [Statement] and return a vector of `QueryResult` fn query_all_raw(&self, stmt: Statement) -> Result, DbErr>; /// Execute a [QueryStatement] and return a vector of `QueryResult` fn query_all(&self, stmt: &S) -> Result, DbErr> { let db_backend = self.get_database_backend(); let stmt = db_backend.build(stmt); self.query_all_raw(stmt) } /// Check if the connection supports `RETURNING` syntax on insert and update fn support_returning(&self) -> bool { let db_backend = self.get_database_backend(); db_backend.support_returning() } /// Check if the connection is a test connection for the Mock database fn is_mock_connection(&self) -> bool { false } } /// Stream query results pub trait StreamTrait { /// Create a stream for the [QueryResult] type Stream<'a>: Iterator> where Self: 'a; /// Get the database backend for the connection. This depends on feature flags enabled. fn get_database_backend(&self) -> DbBackend; /// Execute a [Statement] and return a stream of results fn stream_raw<'a>(&'a self, stmt: Statement) -> Result, DbErr>; /// Execute a [QueryStatement] and return a stream of results fn stream<'a, S: StatementBuilder>(&'a self, stmt: &S) -> Result, DbErr> { let db_backend = self.get_database_backend(); let stmt = db_backend.build(stmt); self.stream_raw(stmt) } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Isolation level pub enum IsolationLevel { /// Consistent reads within the same transaction read the snapshot established by the first read. RepeatableRead, /// Each consistent read, even within the same transaction, sets and reads its own fresh snapshot. ReadCommitted, /// SELECT statements are performed in a nonlocking fashion, but a possible earlier version of a row might be used. ReadUncommitted, /// All statements of the current transaction can only see rows committed before the first query or data-modification statement was executed in this transaction. Serializable, } impl std::fmt::Display for IsolationLevel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IsolationLevel::RepeatableRead => write!(f, "REPEATABLE READ"), IsolationLevel::ReadCommitted => write!(f, "READ COMMITTED"), IsolationLevel::ReadUncommitted => write!(f, "READ UNCOMMITTED"), IsolationLevel::Serializable => write!(f, "SERIALIZABLE"), } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Access mode pub enum AccessMode { /// Data can't be modified in this transaction ReadOnly, /// Data can be modified in this transaction (default) ReadWrite, } impl std::fmt::Display for AccessMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AccessMode::ReadOnly => write!(f, "READ ONLY"), AccessMode::ReadWrite => write!(f, "READ WRITE"), } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Which kind of transaction to start. Only supported by SQLite. /// pub enum SqliteTransactionMode { /// The default. Transaction starts when the next statement is executed, and /// will be a read or write transaction depending on that statement. Deferred, /// Start a write transaction as soon as the BEGIN statement is received. Immediate, /// Start a write transaction as soon as the BEGIN statement is received. /// When in non-WAL mode, also block all other transactions from reading the /// database. Exclusive, } impl SqliteTransactionMode { /// The keyword used to start a transaction in this mode (the word coming after "BEGIN"). pub fn sqlite_keyword(&self) -> &'static str { match self { SqliteTransactionMode::Deferred => "DEFERRED", SqliteTransactionMode::Immediate => "IMMEDIATE", SqliteTransactionMode::Exclusive => "EXCLUSIVE", } } } /// Configuration for starting a transaction #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] pub struct TransactionOptions { /// Isolation level for the new transaction pub isolation_level: Option, /// Access mode for the new transaction pub access_mode: Option, /// Transaction mode (deferred, immediate, exclusive) for the new transaction. Supported only by SQLite. pub sqlite_transaction_mode: Option, } /// Spawn database transaction pub trait TransactionTrait { /// The concrete type for the transaction type Transaction: ConnectionTrait + TransactionTrait + TransactionSession; /// Execute SQL `BEGIN` transaction. /// Returns a Transaction that can be committed or rolled back fn begin(&self) -> Result; /// Execute SQL `BEGIN` transaction with isolation level and/or access mode. /// Returns a Transaction that can be committed or rolled back fn begin_with_config( &self, isolation_level: Option, access_mode: Option, ) -> Result; /// Execute SQL `BEGIN` transaction with isolation level and/or access mode. /// Returns a Transaction that can be committed or rolled back fn begin_with_options(&self, options: TransactionOptions) -> Result; /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. fn transaction(&self, callback: F) -> Result> where F: for<'c> FnOnce(&'c Self::Transaction) -> Result, E: std::fmt::Display + std::fmt::Debug; /// Execute the function inside a transaction with isolation level and/or access mode. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. fn transaction_with_config( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'c> FnOnce(&'c Self::Transaction) -> Result, E: std::fmt::Display + std::fmt::Debug; } /// Represents an open transaction pub trait TransactionSession { /// Commit a transaction fn commit(self) -> Result<(), DbErr>; /// Rolls back a transaction explicitly fn rollback(self) -> Result<(), DbErr>; } ================================================ FILE: sea-orm-sync/src/database/db_connection.rs ================================================ use crate::{ AccessMode, ConnectionTrait, DatabaseTransaction, ExecResult, IsolationLevel, QueryResult, Schema, SchemaBuilder, Statement, StatementBuilder, StreamTrait, TransactionError, TransactionOptions, TransactionTrait, error::*, }; use std::fmt::Debug; use tracing::instrument; use url::Url; #[cfg(feature = "sqlx-dep")] use sqlx::pool::PoolConnection; #[cfg(feature = "rusqlite")] use crate::driver::rusqlite::{RusqliteInnerConnection, RusqliteSharedConnection}; #[cfg(any(feature = "mock", feature = "proxy"))] use std::sync::Arc; /// Handle a database connection depending on the backend enabled by the feature /// flags. This creates a connection pool internally (for SQLx connections), /// and so is cheap to clone. #[derive(Debug, Clone)] #[non_exhaustive] pub struct DatabaseConnection { /// `DatabaseConnection` used to be a enum. Now it's moved into inner, /// because we have to attach other contexts. pub inner: DatabaseConnectionType, #[cfg(feature = "rbac")] pub(crate) rbac: crate::RbacEngineMount, } /// The underlying database connection type. #[derive(Clone)] pub enum DatabaseConnectionType { /// MySql database connection pool #[cfg(feature = "sqlx-mysql")] SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection), /// PostgreSQL database connection pool #[cfg(feature = "sqlx-postgres")] SqlxPostgresPoolConnection(crate::SqlxPostgresPoolConnection), /// SQLite database connection pool #[cfg(feature = "sqlx-sqlite")] SqlxSqlitePoolConnection(crate::SqlxSqlitePoolConnection), /// SQLite database connection sharable across threads #[cfg(feature = "rusqlite")] RusqliteSharedConnection(RusqliteSharedConnection), /// Mock database connection useful for testing #[cfg(feature = "mock")] MockDatabaseConnection(Arc), /// Proxy database connection #[cfg(feature = "proxy")] ProxyDatabaseConnection(Arc), /// The connection has never been established Disconnected, } /// The same as a [DatabaseConnection] pub type DbConn = DatabaseConnection; impl Default for DatabaseConnection { fn default() -> Self { DatabaseConnectionType::Disconnected.into() } } impl From for DatabaseConnection { fn from(inner: DatabaseConnectionType) -> Self { Self { inner, #[cfg(feature = "rbac")] rbac: Default::default(), } } } /// The type of database backend for real world databases. /// This is enabled by feature flags as specified in the crate documentation #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum DatabaseBackend { /// A MySQL backend MySql, /// A PostgreSQL backend Postgres, /// A SQLite backend Sqlite, } /// A shorthand for [DatabaseBackend]. pub type DbBackend = DatabaseBackend; #[derive(Debug)] pub(crate) enum InnerConnection { #[cfg(feature = "sqlx-mysql")] MySql(PoolConnection), #[cfg(feature = "sqlx-postgres")] Postgres(PoolConnection), #[cfg(feature = "sqlx-sqlite")] Sqlite(PoolConnection), #[cfg(feature = "rusqlite")] Rusqlite(RusqliteInnerConnection), #[cfg(feature = "mock")] Mock(Arc), #[cfg(feature = "proxy")] Proxy(Arc), } impl Debug for DatabaseConnectionType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", match self { #[cfg(feature = "sqlx-mysql")] Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection", #[cfg(feature = "sqlx-postgres")] Self::SqlxPostgresPoolConnection(_) => "SqlxPostgresPoolConnection", #[cfg(feature = "sqlx-sqlite")] Self::SqlxSqlitePoolConnection(_) => "SqlxSqlitePoolConnection", #[cfg(feature = "rusqlite")] Self::RusqliteSharedConnection(_) => "RusqliteSharedConnection", #[cfg(feature = "mock")] Self::MockDatabaseConnection(_) => "MockDatabaseConnection", #[cfg(feature = "proxy")] Self::ProxyDatabaseConnection(_) => "ProxyDatabaseConnection", Self::Disconnected => "Disconnected", } ) } } impl ConnectionTrait for DatabaseConnection { fn get_database_backend(&self) -> DbBackend { self.get_database_backend() } #[instrument(level = "trace")] #[allow(unused_variables)] fn execute_raw(&self, stmt: Statement) -> Result { super::tracing_spans::with_db_span!( "sea_orm.execute", self.get_database_backend(), stmt.sql.as_str(), record_stmt = true, { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => conn.execute(stmt), #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => conn.execute(stmt), #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.execute(stmt), #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.execute(stmt), #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => conn.execute(stmt), #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => conn.execute(stmt), DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } ) } #[instrument(level = "trace")] #[allow(unused_variables)] fn execute_unprepared(&self, sql: &str) -> Result { super::tracing_spans::with_db_span!( "sea_orm.execute_unprepared", self.get_database_backend(), sql, record_stmt = false, { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => { conn.execute_unprepared(sql) } #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.execute_unprepared(sql) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => { conn.execute_unprepared(sql) } #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => { conn.execute_unprepared(sql) } #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => { let db_backend = conn.get_database_backend(); let stmt = Statement::from_string(db_backend, sql); conn.execute(stmt) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => { let db_backend = conn.get_database_backend(); let stmt = Statement::from_string(db_backend, sql); conn.execute(stmt) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } ) } #[instrument(level = "trace")] #[allow(unused_variables)] fn query_one_raw(&self, stmt: Statement) -> Result, DbErr> { super::tracing_spans::with_db_span!( "sea_orm.query_one", self.get_database_backend(), stmt.sql.as_str(), record_stmt = true, { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt), #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.query_one(stmt) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt), #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.query_one(stmt), #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => conn.query_one(stmt), #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => conn.query_one(stmt), DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } ) } #[instrument(level = "trace")] #[allow(unused_variables)] fn query_all_raw(&self, stmt: Statement) -> Result, DbErr> { super::tracing_spans::with_db_span!( "sea_orm.query_all", self.get_database_backend(), stmt.sql.as_str(), record_stmt = true, { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt), #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.query_all(stmt) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt), #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.query_all(stmt), #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => conn.query_all(stmt), #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => conn.query_all(stmt), DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } ) } #[cfg(feature = "mock")] fn is_mock_connection(&self) -> bool { matches!( self, DatabaseConnection { inner: DatabaseConnectionType::MockDatabaseConnection(_), .. } ) } } impl StreamTrait for DatabaseConnection { type Stream<'a> = crate::QueryStream; fn get_database_backend(&self) -> DbBackend { self.get_database_backend() } #[instrument(level = "trace")] #[allow(unused_variables)] fn stream_raw<'a>(&'a self, stmt: Statement) -> Result, DbErr> { ({ match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => conn.stream(stmt), #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => conn.stream(stmt), #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.stream(stmt), #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.stream(stmt), #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => { Ok(crate::QueryStream::from((Arc::clone(conn), stmt, None))) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => { Ok(crate::QueryStream::from((Arc::clone(conn), stmt, None))) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } }) } } impl TransactionTrait for DatabaseConnection { type Transaction = DatabaseTransaction; #[instrument(level = "trace")] fn begin(&self) -> Result { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => conn.begin(None, None), #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => conn.begin(None, None), #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.begin(None, None, None), #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.begin(None, None, None), #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => { DatabaseTransaction::new_mock(Arc::clone(conn), None) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => { DatabaseTransaction::new_proxy(conn.clone(), None) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } #[instrument(level = "trace")] fn begin_with_config( &self, _isolation_level: Option, _access_mode: Option, ) -> Result { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => { conn.begin(_isolation_level, _access_mode) } #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.begin(_isolation_level, _access_mode) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => { conn.begin(_isolation_level, _access_mode, None) } #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => { conn.begin(_isolation_level, _access_mode, None) } #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => { DatabaseTransaction::new_mock(Arc::clone(conn), None) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => { DatabaseTransaction::new_proxy(conn.clone(), None) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } #[instrument(level = "trace")] fn begin_with_options( &self, TransactionOptions { isolation_level: _isolation_level, access_mode: _access_mode, sqlite_transaction_mode: _sqlite_transaction_mode, }: TransactionOptions, ) -> Result { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => { conn.begin(_isolation_level, _access_mode) } #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.begin(_isolation_level, _access_mode) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => { conn.begin(_isolation_level, _access_mode, _sqlite_transaction_mode) } #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => { conn.begin(_isolation_level, _access_mode, _sqlite_transaction_mode) } #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => { DatabaseTransaction::new_mock(Arc::clone(conn), None) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => { DatabaseTransaction::new_proxy(conn.clone(), None) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. #[instrument(level = "trace", skip(_callback))] fn transaction(&self, _callback: F) -> Result> where F: for<'c> FnOnce(&'c DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => { conn.transaction(_callback, None, None) } #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.transaction(_callback, None, None) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => { conn.transaction(_callback, None, None) } #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => { conn.transaction(_callback, None, None) } #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => { let transaction = DatabaseTransaction::new_mock(Arc::clone(conn), None) .map_err(TransactionError::Connection)?; transaction.run(_callback) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => { let transaction = DatabaseTransaction::new_proxy(conn.clone(), None) .map_err(TransactionError::Connection)?; transaction.run(_callback) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected").into()), } } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. #[instrument(level = "trace", skip(_callback))] fn transaction_with_config( &self, _callback: F, _isolation_level: Option, _access_mode: Option, ) -> Result> where F: for<'c> FnOnce(&'c DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => { conn.transaction(_callback, _isolation_level, _access_mode) } #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.transaction(_callback, _isolation_level, _access_mode) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => { conn.transaction(_callback, _isolation_level, _access_mode) } #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => { conn.transaction(_callback, _isolation_level, _access_mode) } #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => { let transaction = DatabaseTransaction::new_mock(Arc::clone(conn), None) .map_err(TransactionError::Connection)?; transaction.run(_callback) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => { let transaction = DatabaseTransaction::new_proxy(conn.clone(), None) .map_err(TransactionError::Connection)?; transaction.run(_callback) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected").into()), } } } #[cfg(feature = "mock")] impl DatabaseConnection { /// Generate a database connection for testing the Mock database /// /// # Panics /// /// Panics if [DbConn] is not a mock connection. pub fn as_mock_connection(&self) -> &crate::MockDatabaseConnection { match &self.inner { DatabaseConnectionType::MockDatabaseConnection(mock_conn) => mock_conn, _ => panic!("Not mock connection"), } } /// Get the transaction log as a collection Vec<[crate::Transaction]> /// /// # Panics /// /// Panics if the mocker mutex is being held by another thread. pub fn into_transaction_log(self) -> Vec { let mut mocker = self .as_mock_connection() .get_mocker_mutex() .lock() .expect("Fail to acquire mocker"); mocker.drain_transaction_log() } } #[cfg(feature = "proxy")] impl DatabaseConnection { /// Generate a database connection for testing the Proxy database /// /// # Panics /// /// Panics if [DbConn] is not a proxy connection. pub fn as_proxy_connection(&self) -> &crate::ProxyDatabaseConnection { match &self.inner { DatabaseConnectionType::ProxyDatabaseConnection(proxy_conn) => proxy_conn, _ => panic!("Not proxy connection"), } } } #[cfg(feature = "rbac")] impl DatabaseConnection { /// Load RBAC data from the same database as this connection and setup RBAC engine. /// If the RBAC engine already exists, it will be replaced. pub fn load_rbac(&self) -> Result<(), DbErr> { self.load_rbac_from(self) } /// Load RBAC data from the given database connection and setup RBAC engine. /// This could be from another database. pub fn load_rbac_from(&self, db: &DbConn) -> Result<(), DbErr> { let engine = crate::rbac::RbacEngine::load_from(db)?; self.rbac.replace(engine); Ok(()) } /// Replace the internal RBAC engine. pub fn replace_rbac(&self, engine: crate::rbac::RbacEngine) { self.rbac.replace(engine); } /// Create a restricted connection with access control specific for the user. pub fn restricted_for( &self, user_id: crate::rbac::RbacUserId, ) -> Result { if self.rbac.is_some() { Ok(crate::RestrictedConnection { user_id, conn: self.clone(), }) } else { Err(DbErr::RbacError("engine not set up".into())) } } } impl DatabaseConnection { /// Get the database backend for this connection /// /// # Panics /// /// Panics if [DatabaseConnection] is `Disconnected`. pub fn get_database_backend(&self) -> DbBackend { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(_) => DbBackend::MySql, #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(_) => DbBackend::Postgres, #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(_) => DbBackend::Sqlite, #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(_) => DbBackend::Sqlite, #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => conn.get_database_backend(), #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => conn.get_database_backend(), DatabaseConnectionType::Disconnected => panic!("Disconnected"), } } /// Creates a [`SchemaBuilder`] for this backend pub fn get_schema_builder(&self) -> SchemaBuilder { Schema::new(self.get_database_backend()).builder() } #[cfg(feature = "entity-registry")] #[cfg_attr(docsrs, doc(cfg(feature = "entity-registry")))] /// Builds a schema for all the entites in the given module pub fn get_schema_registry(&self, prefix: &str) -> SchemaBuilder { let schema = Schema::new(self.get_database_backend()); crate::EntityRegistry::build_schema(schema, prefix) } /// Sets a callback to metric this connection pub fn set_metric_callback(&mut self, _callback: F) where F: Fn(&crate::metric::Info<'_>) + 'static, { match &mut self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => { conn.set_metric_callback(_callback) } #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => { conn.set_metric_callback(_callback) } #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => { conn.set_metric_callback(_callback) } #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => { conn.set_metric_callback(_callback) } _ => {} } } /// Checks if a connection to the database is still valid. pub fn ping(&self) -> Result<(), DbErr> { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => conn.ping(), #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => conn.ping(), #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.ping(), #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.ping(), #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(conn) => conn.ping(), #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(conn) => conn.ping(), DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } /// Explicitly close the database connection. /// See [`Self::close_by_ref`] for usage with references. pub fn close(self) -> Result<(), DbErr> { self.close_by_ref() } /// Explicitly close the database connection pub fn close_by_ref(&self) -> Result<(), DbErr> { match &self.inner { #[cfg(feature = "sqlx-mysql")] DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => conn.close_by_ref(), #[cfg(feature = "sqlx-postgres")] DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => conn.close_by_ref(), #[cfg(feature = "sqlx-sqlite")] DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => conn.close_by_ref(), #[cfg(feature = "rusqlite")] DatabaseConnectionType::RusqliteSharedConnection(conn) => conn.close_by_ref(), #[cfg(feature = "mock")] DatabaseConnectionType::MockDatabaseConnection(_) => { // Nothing to cleanup, we just consume the `DatabaseConnection` Ok(()) } #[cfg(feature = "proxy")] DatabaseConnectionType::ProxyDatabaseConnection(_) => { // Nothing to cleanup, we just consume the `DatabaseConnection` Ok(()) } DatabaseConnectionType::Disconnected => Err(conn_err("Disconnected")), } } } impl DatabaseConnection { /// Get [sqlx::MySqlPool] /// /// # Panics /// /// Panics if [DbConn] is not a MySQL connection. #[cfg(feature = "sqlx-mysql")] pub fn get_mysql_connection_pool(&self) -> &sqlx::MySqlPool { match &self.inner { DatabaseConnectionType::SqlxMySqlPoolConnection(conn) => &conn.pool, _ => panic!("Not MySQL Connection"), } } /// Get [sqlx::PgPool] /// /// # Panics /// /// Panics if [DbConn] is not a Postgres connection. #[cfg(feature = "sqlx-postgres")] pub fn get_postgres_connection_pool(&self) -> &sqlx::PgPool { match &self.inner { DatabaseConnectionType::SqlxPostgresPoolConnection(conn) => &conn.pool, _ => panic!("Not Postgres Connection"), } } /// Get [sqlx::SqlitePool] /// /// # Panics /// /// Panics if [DbConn] is not a SQLite connection. #[cfg(feature = "sqlx-sqlite")] pub fn get_sqlite_connection_pool(&self) -> &sqlx::SqlitePool { match &self.inner { DatabaseConnectionType::SqlxSqlitePoolConnection(conn) => &conn.pool, _ => panic!("Not SQLite Connection"), } } } impl DbBackend { /// Check if the URI is the same as the specified database backend. /// Returns true if they match. /// /// # Panics /// /// Panics if `base_url` cannot be parsed as `Url`. pub fn is_prefix_of(self, base_url: &str) -> bool { let base_url_parsed = Url::parse(base_url).expect("Fail to parse database URL"); match self { Self::Postgres => { base_url_parsed.scheme() == "postgres" || base_url_parsed.scheme() == "postgresql" } Self::MySql => base_url_parsed.scheme() == "mysql", Self::Sqlite => base_url_parsed.scheme() == "sqlite", } } /// Build an SQL [Statement] pub fn build(&self, statement: &S) -> Statement where S: StatementBuilder, { statement.build(self) } /// Check if the database supports `RETURNING` syntax on insert and update pub fn support_returning(&self) -> bool { match self { Self::Postgres => true, Self::Sqlite if cfg!(feature = "sqlite-use-returning-for-3_35") => true, Self::MySql if cfg!(feature = "mariadb-use-returning") => true, _ => false, } } /// A getter for database dependent boolean value pub fn boolean_value(&self, boolean: bool) -> sea_query::Value { match self { Self::MySql | Self::Postgres | Self::Sqlite => boolean.into(), } } /// Get the display string for this enum pub fn as_str(&self) -> &'static str { match self { DatabaseBackend::MySql => "MySql", DatabaseBackend::Postgres => "Postgres", DatabaseBackend::Sqlite => "Sqlite", } } } #[cfg(test)] mod tests { use crate::DatabaseConnection; #[cfg(not(feature = "sync"))] #[test] fn assert_database_connection_traits() { fn assert_send_sync() {} assert_send_sync::(); } } ================================================ FILE: sea-orm-sync/src/database/executor.rs ================================================ use crate::{ AccessMode, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbBackend, DbErr, ExecResult, IsolationLevel, QueryResult, Statement, TransactionError, TransactionOptions, TransactionTrait, }; use crate::{Schema, SchemaBuilder}; use std::future::Future; use std::pin::Pin; /// A wrapper that holds either a reference to a [`DatabaseConnection`] or [`DatabaseTransaction`], /// or an owned [`DatabaseTransaction`]. #[derive(Debug)] pub enum DatabaseExecutor<'c> { /// A reference to a database connection Connection(&'c DatabaseConnection), /// A reference to a database transaction Transaction(&'c DatabaseTransaction), /// An owned database transaction (used by migration's `SchemaManager::begin()`) OwnedTransaction(DatabaseTransaction), } impl<'c> From<&'c DatabaseConnection> for DatabaseExecutor<'c> { fn from(conn: &'c DatabaseConnection) -> Self { Self::Connection(conn) } } impl<'c> From<&'c DatabaseTransaction> for DatabaseExecutor<'c> { fn from(trans: &'c DatabaseTransaction) -> Self { Self::Transaction(trans) } } impl ConnectionTrait for DatabaseExecutor<'_> { fn get_database_backend(&self) -> DbBackend { match self { DatabaseExecutor::Connection(conn) => conn.get_database_backend(), DatabaseExecutor::Transaction(trans) => trans.get_database_backend(), DatabaseExecutor::OwnedTransaction(trans) => trans.get_database_backend(), } } fn execute_raw(&self, stmt: Statement) -> Result { match self { DatabaseExecutor::Connection(conn) => conn.execute_raw(stmt), DatabaseExecutor::Transaction(trans) => trans.execute_raw(stmt), DatabaseExecutor::OwnedTransaction(trans) => trans.execute_raw(stmt), } } fn execute_unprepared(&self, sql: &str) -> Result { match self { DatabaseExecutor::Connection(conn) => conn.execute_unprepared(sql), DatabaseExecutor::Transaction(trans) => trans.execute_unprepared(sql), DatabaseExecutor::OwnedTransaction(trans) => trans.execute_unprepared(sql), } } fn query_one_raw(&self, stmt: Statement) -> Result, DbErr> { match self { DatabaseExecutor::Connection(conn) => conn.query_one_raw(stmt), DatabaseExecutor::Transaction(trans) => trans.query_one_raw(stmt), DatabaseExecutor::OwnedTransaction(trans) => trans.query_one_raw(stmt), } } fn query_all_raw(&self, stmt: Statement) -> Result, DbErr> { match self { DatabaseExecutor::Connection(conn) => conn.query_all_raw(stmt), DatabaseExecutor::Transaction(trans) => trans.query_all_raw(stmt), DatabaseExecutor::OwnedTransaction(trans) => trans.query_all_raw(stmt), } } } impl TransactionTrait for DatabaseExecutor<'_> { type Transaction = DatabaseTransaction; fn begin(&self) -> Result { match self { DatabaseExecutor::Connection(conn) => conn.begin(), DatabaseExecutor::Transaction(trans) => trans.begin(), DatabaseExecutor::OwnedTransaction(trans) => trans.begin(), } } fn begin_with_config( &self, isolation_level: Option, access_mode: Option, ) -> Result { match self { DatabaseExecutor::Connection(conn) => { conn.begin_with_config(isolation_level, access_mode) } DatabaseExecutor::Transaction(trans) => { trans.begin_with_config(isolation_level, access_mode) } DatabaseExecutor::OwnedTransaction(trans) => { trans.begin_with_config(isolation_level, access_mode) } } } fn begin_with_options( &self, options: TransactionOptions, ) -> Result { match self { DatabaseExecutor::Connection(conn) => conn.begin_with_options(options), DatabaseExecutor::Transaction(trans) => trans.begin_with_options(options), DatabaseExecutor::OwnedTransaction(trans) => trans.begin_with_options(options), } } fn transaction(&self, callback: F) -> Result> where F: for<'c> FnOnce(&'c DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { match self { DatabaseExecutor::Connection(conn) => conn.transaction(callback), DatabaseExecutor::Transaction(trans) => trans.transaction(callback), DatabaseExecutor::OwnedTransaction(trans) => trans.transaction(callback), } } fn transaction_with_config( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'c> FnOnce(&'c DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { match self { DatabaseExecutor::Connection(conn) => { conn.transaction_with_config(callback, isolation_level, access_mode) } DatabaseExecutor::Transaction(trans) => { trans.transaction_with_config(callback, isolation_level, access_mode) } DatabaseExecutor::OwnedTransaction(trans) => { trans.transaction_with_config(callback, isolation_level, access_mode) } } } } /// A trait for converting into [`DatabaseExecutor`] pub trait IntoDatabaseExecutor<'c> where Self: 'c, { /// Convert into a [`DatabaseExecutor`] fn into_database_executor(self) -> DatabaseExecutor<'c>; } impl<'c> IntoDatabaseExecutor<'c> for DatabaseExecutor<'c> { fn into_database_executor(self) -> DatabaseExecutor<'c> { self } } impl<'c> IntoDatabaseExecutor<'c> for &'c DatabaseConnection { fn into_database_executor(self) -> DatabaseExecutor<'c> { DatabaseExecutor::Connection(self) } } impl<'c> IntoDatabaseExecutor<'c> for &'c DatabaseTransaction { fn into_database_executor(self) -> DatabaseExecutor<'c> { DatabaseExecutor::Transaction(self) } } impl IntoDatabaseExecutor<'static> for DatabaseTransaction { fn into_database_executor(self) -> DatabaseExecutor<'static> { DatabaseExecutor::OwnedTransaction(self) } } impl DatabaseExecutor<'_> { /// Returns `true` if this executor is backed by a transaction (borrowed or owned). pub fn is_transaction(&self) -> bool { matches!( self, DatabaseExecutor::Transaction(_) | DatabaseExecutor::OwnedTransaction(_) ) } /// Creates a [`SchemaBuilder`] for this backend pub fn get_schema_builder(&self) -> SchemaBuilder { Schema::new(self.get_database_backend()).builder() } #[cfg(feature = "entity-registry")] #[cfg_attr(docsrs, doc(cfg(feature = "entity-registry")))] /// Builds a schema for all the entities in the given module pub fn get_schema_registry(&self, prefix: &str) -> SchemaBuilder { let schema = Schema::new(self.get_database_backend()); crate::EntityRegistry::build_schema(schema, prefix) } } ================================================ FILE: sea-orm-sync/src/database/mock.rs ================================================ use crate::{ DatabaseConnection, DatabaseConnectionType, DbBackend, EntityTrait, ExecResult, ExecResultHolder, Iden, IdenStatic, Iterable, MockDatabaseConnection, MockDatabaseTrait, ModelTrait, QueryResult, QueryResultRow, SelectA, SelectB, Statement, error::*, }; use sea_query::{Value, ValueType, Values}; use std::{collections::BTreeMap, sync::Arc}; use tracing::instrument; /// Defines a Mock database suitable for testing #[derive(Debug)] pub struct MockDatabase { db_backend: DbBackend, transaction: Option, transaction_log: Vec, exec_results: Vec>, query_results: Vec, DbErr>>, } /// Defines the results obtained from a [MockDatabase] #[derive(Clone, Debug, Default)] pub struct MockExecResult { /// The last inserted id on auto-increment pub last_insert_id: u64, /// The number of rows affected by the database operation pub rows_affected: u64, } /// Defines the structure of a test Row for the [MockDatabase] /// which is just a [BTreeMap]<[String], [Value]> #[derive(Clone, Debug)] pub struct MockRow { /// The values of the single row pub(crate) values: BTreeMap, } /// A trait to get a [MockRow] from a type useful for testing in the [MockDatabase] pub trait IntoMockRow { /// The method to perform this operation fn into_mock_row(self) -> MockRow; } /// Defines a transaction that is has not been committed #[derive(Debug)] pub struct OpenTransaction { stmts: Vec, transaction_depth: usize, } /// Defines a database transaction as it holds a Vec<[Statement]> #[derive(Debug, Clone, PartialEq)] pub struct Transaction { stmts: Vec, } impl MockDatabase { /// Instantiate a mock database with a [DbBackend] to simulate real /// world SQL databases pub fn new(db_backend: DbBackend) -> Self { Self { db_backend, transaction: None, transaction_log: Vec::new(), exec_results: Vec::new(), query_results: Vec::new(), } } /// Create a database connection pub fn into_connection(self) -> DatabaseConnection { DatabaseConnectionType::MockDatabaseConnection(Arc::new(MockDatabaseConnection::new(self))) .into() } /// Add some [MockExecResult]s to `exec_results` pub fn append_exec_results(mut self, vec: I) -> Self where I: IntoIterator, { self.exec_results.extend(vec.into_iter().map(Result::Ok)); self } /// Add some Values to `query_results` pub fn append_query_results(mut self, vec: II) -> Self where T: IntoMockRow, I: IntoIterator, II: IntoIterator, { for row in vec.into_iter() { let row = row.into_iter().map(|vec| Ok(vec.into_mock_row())).collect(); self.query_results.push(row); } self } /// Add some [DbErr]s to `exec_results` pub fn append_exec_errors(mut self, vec: I) -> Self where I: IntoIterator, { self.exec_results.extend(vec.into_iter().map(Result::Err)); self } /// Add some [DbErr]s to `query_results` pub fn append_query_errors(mut self, vec: I) -> Self where I: IntoIterator, { self.query_results.extend(vec.into_iter().map(Result::Err)); self } } impl MockDatabaseTrait for MockDatabase { #[instrument(level = "trace")] fn execute(&mut self, counter: usize, statement: Statement) -> Result { if let Some(transaction) = &mut self.transaction { transaction.push(statement); } else { self.transaction_log.push(Transaction::one(statement)); } if counter < self.exec_results.len() { match std::mem::replace( &mut self.exec_results[counter], Err(exec_err("this value has been consumed already")), ) { Ok(result) => Ok(ExecResult { result: ExecResultHolder::Mock(result), }), Err(err) => Err(err), } } else { Err(exec_err("`exec_results` buffer is empty")) } } #[instrument(level = "trace")] fn query(&mut self, counter: usize, statement: Statement) -> Result, DbErr> { if let Some(transaction) = &mut self.transaction { transaction.push(statement); } else { self.transaction_log.push(Transaction::one(statement)); } if counter < self.query_results.len() { match std::mem::replace( &mut self.query_results[counter], Err(query_err("this value has been consumed already")), ) { Ok(result) => Ok(result .into_iter() .map(|row| QueryResult { row: QueryResultRow::Mock(row), }) .collect()), Err(err) => Err(err), } } else { Err(query_err("`query_results` buffer is empty.")) } } #[instrument(level = "trace")] fn begin(&mut self) { match self.transaction.as_mut() { Some(transaction) => transaction.begin_nested(self.db_backend), None => self.transaction = Some(OpenTransaction::init()), } } #[instrument(level = "trace")] fn commit(&mut self) { match self.transaction.as_mut() { Some(transaction) => { if transaction.commit(self.db_backend) { if let Some(transaction) = self.transaction.take() { self.transaction_log.push(transaction.into_transaction()); } } } None => panic!("There is no open transaction to commit"), } } #[instrument(level = "trace")] fn rollback(&mut self) { match self.transaction.as_mut() { Some(transaction) => { if transaction.rollback(self.db_backend) { if let Some(transaction) = self.transaction.take() { self.transaction_log.push(transaction.into_transaction()); } } } None => panic!("There is no open transaction to rollback"), } } fn drain_transaction_log(&mut self) -> Vec { std::mem::take(&mut self.transaction_log) } fn get_database_backend(&self) -> DbBackend { self.db_backend } fn ping(&self) -> Result<(), DbErr> { Ok(()) } } impl MockRow { /// Get a value from the [MockRow] pub fn try_get(&self, index: I) -> Result where T: ValueType, { if let Some(index) = index.as_str() { T::try_from( self.values .get(index) .ok_or_else(|| query_err(format!("No column for ColIdx {index:?}")))? .clone(), ) .map_err(type_err) } else if let Some(index) = index.as_usize() { let (_, value) = self .values .iter() .nth(*index) .ok_or_else(|| query_err(format!("Column at index {index} not found")))?; T::try_from(value.clone()).map_err(type_err) } else { unreachable!("Missing ColIdx implementation for MockRow"); } } /// An iterator over the keys and values of a mock row pub fn into_column_value_tuples(self) -> impl Iterator { self.values.into_iter() } } impl IntoMockRow for MockRow { fn into_mock_row(self) -> MockRow { self } } impl IntoMockRow for M where M: ModelTrait, { fn into_mock_row(self) -> MockRow { let mut values = BTreeMap::new(); for col in <::Column>::iter() { values.insert(col.to_string(), self.get(col)); } MockRow { values } } } impl IntoMockRow for (M, N) where M: ModelTrait, N: ModelTrait, { fn into_mock_row(self) -> MockRow { let mut mapped_join = BTreeMap::new(); for column in <::Entity as EntityTrait>::Column::iter() { mapped_join.insert( format!("{}{}", SelectA.as_str(), column.as_str()), self.0.get(column), ); } for column in <::Entity as EntityTrait>::Column::iter() { mapped_join.insert( format!("{}{}", SelectB.as_str(), column.as_str()), self.1.get(column), ); } mapped_join.into_mock_row() } } impl IntoMockRow for (M, Option) where M: ModelTrait, N: ModelTrait, { fn into_mock_row(self) -> MockRow { let mut mapped_join = BTreeMap::new(); for column in <::Entity as EntityTrait>::Column::iter() { mapped_join.insert( format!("{}{}", SelectA.as_str(), column.as_str()), self.0.get(column), ); } if let Some(b_entity) = self.1 { for column in <::Entity as EntityTrait>::Column::iter() { mapped_join.insert( format!("{}{}", SelectB.as_str(), column.as_str()), b_entity.get(column), ); } } mapped_join.into_mock_row() } } impl IntoMockRow for BTreeMap where T: Into, { fn into_mock_row(self) -> MockRow { MockRow { values: self.into_iter().map(|(k, v)| (k.into(), v)).collect(), } } } impl Transaction { /// Get the [Value]s from s raw SQL statement depending on the [DatabaseBackend](crate::DatabaseBackend) pub fn from_sql_and_values(db_backend: DbBackend, sql: T, values: I) -> Self where I: IntoIterator, T: Into, { Self::one(Statement::from_string_values_tuple( db_backend, (sql, Values(values.into_iter().collect())), )) } /// Create a Transaction with one statement pub fn one(stmt: Statement) -> Self { Self { stmts: vec![stmt] } } /// Create a Transaction with many statements pub fn many(stmts: I) -> Self where I: IntoIterator, { Self { stmts: stmts.into_iter().collect(), } } /// Wrap each Statement as a single-statement Transaction pub fn wrap(stmts: I) -> Vec where I: IntoIterator, { stmts.into_iter().map(Self::one).collect() } /// Get the list of statements pub fn statements(&self) -> &[Statement] { &self.stmts } } impl OpenTransaction { fn init() -> Self { Self { stmts: vec![Statement::from_string(DbBackend::Postgres, "BEGIN")], transaction_depth: 0, } } fn begin_nested(&mut self, db_backend: DbBackend) { self.transaction_depth += 1; self.push(Statement::from_string( db_backend, format!("SAVEPOINT savepoint_{}", self.transaction_depth), )); } fn commit(&mut self, db_backend: DbBackend) -> bool { if self.transaction_depth == 0 { self.push(Statement::from_string(db_backend, "COMMIT")); true } else { self.push(Statement::from_string( db_backend, format!("RELEASE SAVEPOINT savepoint_{}", self.transaction_depth), )); self.transaction_depth -= 1; false } } fn rollback(&mut self, db_backend: DbBackend) -> bool { if self.transaction_depth == 0 { self.push(Statement::from_string(db_backend, "ROLLBACK")); true } else { self.push(Statement::from_string( db_backend, format!("ROLLBACK TO SAVEPOINT savepoint_{}", self.transaction_depth), )); self.transaction_depth -= 1; false } } fn push(&mut self, stmt: Statement) { self.stmts.push(stmt); } fn into_transaction(self) -> Transaction { match self.transaction_depth { 0 => Transaction { stmts: self.stmts }, _ => panic!("There is uncommitted nested transaction"), } } } #[cfg(test)] #[cfg(feature = "mock")] mod tests { #[cfg(feature = "sync")] use crate::util::StreamShim; use crate::{ DbBackend, DbErr, IntoMockRow, MockDatabase, Statement, Transaction, TransactionError, TransactionTrait, entity::*, error::*, tests_cfg::*, }; use pretty_assertions::assert_eq; #[derive(Debug, PartialEq, Eq)] pub struct MyErr(String); impl std::error::Error for MyErr {} impl std::fmt::Display for MyErr { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.0.as_str()) } } #[test] fn test_transaction_1() { let db = MockDatabase::new(DbBackend::Postgres).into_connection(); db.transaction::<_, (), DbErr>(|txn| { ({ let _1 = cake::Entity::find().one(txn); let _2 = fruit::Entity::find().all(txn); Ok(()) }) }) .unwrap(); let _ = cake::Entity::find().all(&db); assert_eq!( db.into_transaction_log(), [ Transaction::many([ Statement::from_string(DbBackend::Postgres, "BEGIN"), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, [1u64.into()] ), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#, [] ), Statement::from_string(DbBackend::Postgres, "COMMIT"), ]), Transaction::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, [] ), ] ); } #[test] fn test_transaction_2() { let db = MockDatabase::new(DbBackend::Postgres).into_connection(); let result = db.transaction::<_, (), MyErr>(|txn| { ({ let _ = cake::Entity::find().one(txn); Err(MyErr("test".to_owned())) }) }); match result { Err(TransactionError::Transaction(err)) => { assert_eq!(err, MyErr("test".to_owned())) } _ => unreachable!(), } assert_eq!( db.into_transaction_log(), [Transaction::many([ Statement::from_string(DbBackend::Postgres, "BEGIN"), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, [1u64.into()] ), Statement::from_string(DbBackend::Postgres, "ROLLBACK"), ])] ); } #[test] fn test_nested_transaction_1() { let db = MockDatabase::new(DbBackend::Postgres).into_connection(); db.transaction::<_, (), DbErr>(|txn| { ({ let _ = cake::Entity::find().one(txn); txn.transaction::<_, (), DbErr>(|txn| { ({ let _ = fruit::Entity::find().all(txn); Ok(()) }) }) .unwrap(); Ok(()) }) }) .unwrap(); assert_eq!( db.into_transaction_log(), [Transaction::many([ Statement::from_string(DbBackend::Postgres, "BEGIN"), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, [1u64.into()] ), Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_1"), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#, [] ), Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_1"), Statement::from_string(DbBackend::Postgres, "COMMIT"), ]),] ); } #[test] fn test_nested_transaction_2() { let db = MockDatabase::new(DbBackend::Postgres).into_connection(); db.transaction::<_, (), DbErr>(|txn| { ({ let _ = cake::Entity::find().one(txn); txn.transaction::<_, (), DbErr>(|txn| { ({ let _ = fruit::Entity::find().all(txn); txn.transaction::<_, (), DbErr>(|txn| { ({ let _ = cake::Entity::find().all(txn); Ok(()) }) }) .unwrap(); Ok(()) }) }) .unwrap(); Ok(()) }) }) .unwrap(); assert_eq!( db.into_transaction_log(), [Transaction::many([ Statement::from_string(DbBackend::Postgres, "BEGIN"), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, [1u64.into()] ), Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_1"), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#, [] ), Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_2"), Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, [] ), Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_2"), Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_1"), Statement::from_string(DbBackend::Postgres, "COMMIT"), ]),] ); } #[test] fn test_stream_1() -> Result<(), DbErr> { let apple = fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), }; let orange = fruit::Model { id: 2, name: "orange".to_owned(), cake_id: None, }; let db = MockDatabase::new(DbBackend::Postgres) .append_query_results([[apple.clone(), orange.clone()]]) .into_connection(); let mut stream = fruit::Entity::find().stream(&db)?; assert_eq!(stream.try_next()?, Some(apple)); assert_eq!(stream.try_next()?, Some(orange)); assert_eq!(stream.try_next()?, None); Ok(()) } #[test] fn test_stream_2() -> Result<(), DbErr> { use fruit::Entity as Fruit; let db = MockDatabase::new(DbBackend::Postgres) .append_query_results([Vec::::new()]) .into_connection(); let mut stream = Fruit::find().stream(&db)?; while let Some(item) = stream.try_next()? { let _item: fruit::ActiveModel = item.into(); } Ok(()) } #[test] fn test_stream_in_transaction() -> Result<(), DbErr> { let apple = fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), }; let orange = fruit::Model { id: 2, name: "orange".to_owned(), cake_id: None, }; let db = MockDatabase::new(DbBackend::Postgres) .append_query_results([[apple.clone(), orange.clone()]]) .into_connection(); let txn = db.begin()?; if let Ok(mut stream) = fruit::Entity::find().stream(&txn) { assert_eq!(stream.try_next()?, Some(apple)); assert_eq!(stream.try_next()?, Some(orange)); assert_eq!(stream.try_next()?, None); // stream will be dropped end of scope } txn.commit()?; Ok(()) } #[test] fn test_mocked_join() { let row = ( cake::Model { id: 1, name: "Apple Cake".to_owned(), }, fruit::Model { id: 2, name: "Apple".to_owned(), cake_id: Some(1), }, ); let mocked_row = row.into_mock_row(); let a_id = mocked_row.try_get::("A_id"); assert!(a_id.is_ok()); assert_eq!(1, a_id.unwrap()); let b_id = mocked_row.try_get::("B_id"); assert!(b_id.is_ok()); assert_eq!(2, b_id.unwrap()); } #[test] fn test_find_also_related_1() -> Result<(), DbErr> { let db = MockDatabase::new(DbBackend::Postgres) .append_query_results([[( cake::Model { id: 1, name: "Apple Cake".to_owned(), }, fruit::Model { id: 2, name: "Apple".to_owned(), cake_id: Some(1), }, )]]) .into_connection(); assert_eq!( cake::Entity::find() .find_also_related(fruit::Entity) .all(&db)?, [( cake::Model { id: 1, name: "Apple Cake".to_owned(), }, Some(fruit::Model { id: 2, name: "Apple".to_owned(), cake_id: Some(1), }) )] ); assert_eq!( db.into_transaction_log(), [Transaction::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name", "fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id" FROM "cake" LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#, [] ),] ); Ok(()) } #[cfg(feature = "postgres-array")] #[test] fn test_postgres_array_1() -> Result<(), DbErr> { mod collection { use crate as sea_orm; use crate::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "collection")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub integers: Vec, pub integers_opt: Option>, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } let db = MockDatabase::new(DbBackend::Postgres) .append_query_results([[ collection::Model { id: 1, integers: vec![1, 2, 3], integers_opt: Some(vec![1, 2, 3]), }, collection::Model { id: 2, integers: vec![], integers_opt: Some(vec![]), }, collection::Model { id: 3, integers: vec![3, 1, 4], integers_opt: None, }, ]]) .into_connection(); assert_eq!( collection::Entity::find().all(&db)?, [ collection::Model { id: 1, integers: vec![1, 2, 3], integers_opt: Some(vec![1, 2, 3]), }, collection::Model { id: 2, integers: vec![], integers_opt: Some(vec![]), }, collection::Model { id: 3, integers: vec![3, 1, 4], integers_opt: None, }, ] ); assert_eq!( db.into_transaction_log(), [Transaction::from_sql_and_values( DbBackend::Postgres, r#"SELECT "collection"."id", "collection"."integers", "collection"."integers_opt" FROM "collection""#, [] ),] ); Ok(()) } #[test] fn test_query_err() { let db = MockDatabase::new(DbBackend::MySql) .append_query_errors([query_err("this is a mock query error")]) .into_connection(); assert_eq!( cake::Entity::find().all(&db), Err(query_err("this is a mock query error")) ); } #[test] fn test_exec_err() { let db = MockDatabase::new(DbBackend::MySql) .append_exec_errors([exec_err("this is a mock exec error")]) .into_connection(); let model = cake::ActiveModel::new(); assert_eq!(model.save(&db), Err(exec_err("this is a mock exec error"))); } } ================================================ FILE: sea-orm-sync/src/database/mod.rs ================================================ use std::{sync::Arc, time::Duration}; #[cfg(feature = "sqlx-mysql")] use sqlx::mysql::MySqlConnectOptions; #[cfg(feature = "sqlx-postgres")] use sqlx::postgres::PgConnectOptions; #[cfg(feature = "sqlx-sqlite")] use sqlx::sqlite::SqliteConnectOptions; mod connection; mod db_connection; mod executor; #[cfg(feature = "mock")] #[cfg_attr(docsrs, doc(cfg(feature = "mock")))] mod mock; #[cfg(feature = "proxy")] #[cfg_attr(docsrs, doc(cfg(feature = "proxy")))] mod proxy; #[cfg(feature = "rbac")] mod restricted_connection; #[cfg(all(feature = "schema-sync", feature = "rusqlite"))] mod sea_schema_rusqlite; #[cfg(all(feature = "schema-sync", feature = "sqlx-dep"))] mod sea_schema_shim; mod statement; mod stream; mod tracing_spans; mod transaction; pub use connection::*; pub use db_connection::*; pub use executor::*; #[cfg(feature = "mock")] #[cfg_attr(docsrs, doc(cfg(feature = "mock")))] pub use mock::*; #[cfg(feature = "proxy")] #[cfg_attr(docsrs, doc(cfg(feature = "proxy")))] pub use proxy::*; #[cfg(feature = "rbac")] pub use restricted_connection::*; pub use statement::*; use std::borrow::Cow; pub use stream::*; use tracing::instrument; pub use transaction::*; use crate::error::*; /// Defines a database #[derive(Debug, Default)] pub struct Database; #[cfg(feature = "sync")] type BoxFuture<'a, T> = T; type AfterConnectCallback = Option BoxFuture<'static, Result<(), DbErr>> + 'static>>; /// Defines the configuration options of a database #[derive(derive_more::Debug, Clone)] pub struct ConnectOptions { /// The URI of the database pub(crate) url: String, /// Maximum number of connections for a pool pub(crate) max_connections: Option, /// Minimum number of connections for a pool pub(crate) min_connections: Option, /// The connection timeout for a packet connection pub(crate) connect_timeout: Option, /// Maximum idle time for a particular connection to prevent /// network resource exhaustion pub(crate) idle_timeout: Option>, /// Set the maximum amount of time to spend waiting for acquiring a connection pub(crate) acquire_timeout: Option, /// Set the maximum lifetime of individual connections pub(crate) max_lifetime: Option>, /// Enable SQLx statement logging pub(crate) sqlx_logging: bool, /// SQLx statement logging level (ignored if `sqlx_logging` is false) pub(crate) sqlx_logging_level: log::LevelFilter, /// SQLx slow statements logging level (ignored if `sqlx_logging` is false) pub(crate) sqlx_slow_statements_logging_level: log::LevelFilter, /// SQLx slow statements duration threshold (ignored if `sqlx_logging` is false) pub(crate) sqlx_slow_statements_logging_threshold: Duration, /// set sqlcipher key pub(crate) sqlcipher_key: Option>, /// Schema search path (PostgreSQL only) pub(crate) schema_search_path: Option, /// Application name (PostgreSQL only) pub(crate) application_name: Option, /// Statement timeout (PostgreSQL only) pub(crate) statement_timeout: Option, pub(crate) test_before_acquire: bool, /// Only establish connections to the DB as needed. If set to `true`, the db connection will /// be created using SQLx's [connect_lazy](https://docs.rs/sqlx/latest/sqlx/struct.Pool.html#method.connect_lazy) /// method. pub(crate) connect_lazy: bool, #[debug(skip)] pub(crate) after_connect: AfterConnectCallback, #[cfg(feature = "sqlx-mysql")] #[debug(skip)] pub(crate) mysql_opts_fn: Option MySqlConnectOptions>>, #[cfg(feature = "sqlx-postgres")] #[debug(skip)] pub(crate) pg_opts_fn: Option PgConnectOptions>>, #[cfg(feature = "sqlx-sqlite")] #[debug(skip)] pub(crate) sqlite_opts_fn: Option SqliteConnectOptions>>, } impl Database { /// Method to create a [DatabaseConnection] on a database. This method will return an error /// if the database is not available. #[instrument(level = "trace", skip(opt))] pub fn connect(opt: C) -> Result where C: Into, { let opt: ConnectOptions = opt.into(); if url::Url::parse(&opt.url).is_err() { return Err(conn_err(format!( "The connection string '{}' cannot be parsed.", opt.url ))); } #[cfg(feature = "sqlx-mysql")] if DbBackend::MySql.is_prefix_of(&opt.url) { return crate::SqlxMySqlConnector::connect(opt); } #[cfg(feature = "sqlx-postgres")] if DbBackend::Postgres.is_prefix_of(&opt.url) { return crate::SqlxPostgresConnector::connect(opt); } #[cfg(feature = "sqlx-sqlite")] if DbBackend::Sqlite.is_prefix_of(&opt.url) { return crate::SqlxSqliteConnector::connect(opt); } #[cfg(feature = "rusqlite")] if DbBackend::Sqlite.is_prefix_of(&opt.url) { return crate::driver::rusqlite::RusqliteConnector::connect(opt); } #[cfg(feature = "mock")] if crate::MockDatabaseConnector::accepts(&opt.url) { return crate::MockDatabaseConnector::connect(&opt.url); } Err(conn_err(format!( "The connection string '{}' has no supporting driver.", opt.url ))) } /// Method to create a [DatabaseConnection] on a proxy database #[cfg(feature = "proxy")] #[instrument(level = "trace", skip(proxy_func_arc))] pub fn connect_proxy( db_type: DbBackend, proxy_func_arc: std::sync::Arc>, ) -> Result { match db_type { DbBackend::MySql => { return crate::ProxyDatabaseConnector::connect( DbBackend::MySql, proxy_func_arc.to_owned(), ); } DbBackend::Postgres => { return crate::ProxyDatabaseConnector::connect( DbBackend::Postgres, proxy_func_arc.to_owned(), ); } DbBackend::Sqlite => { return crate::ProxyDatabaseConnector::connect( DbBackend::Sqlite, proxy_func_arc.to_owned(), ); } } } } impl From for ConnectOptions where T: Into, { fn from(s: T) -> ConnectOptions { ConnectOptions::new(s.into()) } } impl ConnectOptions { /// Create new [ConnectOptions] for a [Database] by passing in a URI string pub fn new(url: T) -> Self where T: Into, { Self { url: url.into(), max_connections: None, min_connections: None, connect_timeout: None, idle_timeout: None, acquire_timeout: None, max_lifetime: None, sqlx_logging: true, sqlx_logging_level: log::LevelFilter::Info, sqlx_slow_statements_logging_level: log::LevelFilter::Off, sqlx_slow_statements_logging_threshold: Duration::from_secs(1), sqlcipher_key: None, schema_search_path: None, application_name: None, statement_timeout: None, test_before_acquire: true, connect_lazy: false, after_connect: None, #[cfg(feature = "sqlx-mysql")] mysql_opts_fn: None, #[cfg(feature = "sqlx-postgres")] pg_opts_fn: None, #[cfg(feature = "sqlx-sqlite")] sqlite_opts_fn: None, } } /// Get the database URL of the pool pub fn get_url(&self) -> &str { &self.url } /// Set the maximum number of connections of the pool pub fn max_connections(&mut self, value: u32) -> &mut Self { self.max_connections = Some(value); self } /// Get the maximum number of connections of the pool, if set pub fn get_max_connections(&self) -> Option { self.max_connections } /// Set the minimum number of connections of the pool pub fn min_connections(&mut self, value: u32) -> &mut Self { self.min_connections = Some(value); self } /// Get the minimum number of connections of the pool, if set pub fn get_min_connections(&self) -> Option { self.min_connections } /// Set the timeout duration when acquiring a connection pub fn connect_timeout(&mut self, value: Duration) -> &mut Self { self.connect_timeout = Some(value); self } /// Get the timeout duration when acquiring a connection, if set pub fn get_connect_timeout(&self) -> Option { self.connect_timeout } /// Set the idle duration before closing a connection. pub fn idle_timeout(&mut self, value: T) -> &mut Self where T: Into>, { self.idle_timeout = Some(value.into()); self } /// Get the idle duration before closing a connection, if set pub fn get_idle_timeout(&self) -> Option> { self.idle_timeout } /// Set the maximum amount of time to spend waiting for acquiring a connection pub fn acquire_timeout(&mut self, value: Duration) -> &mut Self { self.acquire_timeout = Some(value); self } /// Get the maximum amount of time to spend waiting for acquiring a connection pub fn get_acquire_timeout(&self) -> Option { self.acquire_timeout } /// Set the maximum lifetime of individual connections. pub fn max_lifetime(&mut self, lifetime: T) -> &mut Self where T: Into>, { self.max_lifetime = Some(lifetime.into()); self } /// Get the maximum lifetime of individual connections, if set pub fn get_max_lifetime(&self) -> Option> { self.max_lifetime } /// Enable SQLx statement logging (default true) pub fn sqlx_logging(&mut self, value: bool) -> &mut Self { self.sqlx_logging = value; self } /// Get whether SQLx statement logging is enabled pub fn get_sqlx_logging(&self) -> bool { self.sqlx_logging } /// Set SQLx statement logging level (default INFO). /// (ignored if `sqlx_logging` is `false`) pub fn sqlx_logging_level(&mut self, level: log::LevelFilter) -> &mut Self { self.sqlx_logging_level = level; self } /// Set SQLx slow statements logging level and duration threshold (default `LevelFilter::Off`). /// (ignored if `sqlx_logging` is `false`) pub fn sqlx_slow_statements_logging_settings( &mut self, level: log::LevelFilter, duration: Duration, ) -> &mut Self { self.sqlx_slow_statements_logging_level = level; self.sqlx_slow_statements_logging_threshold = duration; self } /// Get the level of SQLx statement logging pub fn get_sqlx_logging_level(&self) -> log::LevelFilter { self.sqlx_logging_level } /// Get the SQLx slow statements logging settings pub fn get_sqlx_slow_statements_logging_settings(&self) -> (log::LevelFilter, Duration) { ( self.sqlx_slow_statements_logging_level, self.sqlx_slow_statements_logging_threshold, ) } /// set key for sqlcipher pub fn sqlcipher_key(&mut self, value: T) -> &mut Self where T: Into>, { self.sqlcipher_key = Some(value.into()); self } /// Set schema search path (PostgreSQL only) pub fn set_schema_search_path(&mut self, schema_search_path: T) -> &mut Self where T: Into, { self.schema_search_path = Some(schema_search_path.into()); self } /// Set application name (PostgreSQL only) pub fn set_application_name(&mut self, application_name: T) -> &mut Self where T: Into, { self.application_name = Some(application_name.into()); self } /// Set the statement timeout (PostgreSQL only). /// /// This sets the PostgreSQL `statement_timeout` parameter via the connection options, /// causing the server to abort any statement that exceeds the specified duration. /// The timeout is applied at connection time and does not require an extra roundtrip. /// /// Has no effect on MySQL or SQLite connections. pub fn statement_timeout(&mut self, value: Duration) -> &mut Self { self.statement_timeout = Some(value); self } /// Get the statement timeout, if set pub fn get_statement_timeout(&self) -> Option { self.statement_timeout } /// If true, the connection will be pinged upon acquiring from the pool (default true). pub fn test_before_acquire(&mut self, value: bool) -> &mut Self { self.test_before_acquire = value; self } /// If set to `true`, the db connection pool will be created using SQLx's /// [connect_lazy](https://docs.rs/sqlx/latest/sqlx/struct.Pool.html#method.connect_lazy) method. pub fn connect_lazy(&mut self, value: bool) -> &mut Self { self.connect_lazy = value; self } /// Get whether DB connections will be established when the pool is created or only as needed. pub fn get_connect_lazy(&self) -> bool { self.connect_lazy } /// Set a callback function that will be called after a new connection is established. pub fn after_connect(&mut self, f: F) -> &mut Self where F: Fn(DatabaseConnection) -> BoxFuture<'static, Result<(), DbErr>> + 'static, { self.after_connect = Some(Arc::new(f)); self } #[cfg(feature = "sqlx-mysql")] #[cfg_attr(docsrs, doc(cfg(feature = "sqlx-mysql")))] /// Apply a function to modify the underlying [`MySqlConnectOptions`] before /// creating the connection pool. pub fn map_sqlx_mysql_opts(&mut self, f: F) -> &mut Self where F: Fn(MySqlConnectOptions) -> MySqlConnectOptions + 'static, { self.mysql_opts_fn = Some(Arc::new(f)); self } #[cfg(feature = "sqlx-postgres")] #[cfg_attr(docsrs, doc(cfg(feature = "sqlx-postgres")))] /// Apply a function to modify the underlying [`PgConnectOptions`] before /// creating the connection pool. pub fn map_sqlx_postgres_opts(&mut self, f: F) -> &mut Self where F: Fn(PgConnectOptions) -> PgConnectOptions + 'static, { self.pg_opts_fn = Some(Arc::new(f)); self } #[cfg(feature = "sqlx-sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "sqlx-sqlite")))] /// Apply a function to modify the underlying [`SqliteConnectOptions`] before /// creating the connection pool. pub fn map_sqlx_sqlite_opts(&mut self, f: F) -> &mut Self where F: Fn(SqliteConnectOptions) -> SqliteConnectOptions + 'static, { self.sqlite_opts_fn = Some(Arc::new(f)); self } } ================================================ FILE: sea-orm-sync/src/database/proxy.rs ================================================ use crate::{ExecResult, ExecResultHolder, QueryResult, QueryResultRow, Statement, error::*}; use sea_query::{Value, ValueType}; use std::{collections::BTreeMap, fmt::Debug}; /// Defines the [ProxyDatabaseTrait] to save the functions pub trait ProxyDatabaseTrait: Debug { /// Execute a query in the [ProxyDatabase], and return the query results fn query(&self, statement: Statement) -> Result, DbErr>; /// Execute a command in the [ProxyDatabase], and report the number of rows affected fn execute(&self, statement: Statement) -> Result; /// Begin a transaction in the [ProxyDatabase] fn begin(&self) {} /// Commit a transaction in the [ProxyDatabase] fn commit(&self) {} /// Rollback a transaction in the [ProxyDatabase] fn rollback(&self) {} /// Start rollback a transaction in the [ProxyDatabase] fn start_rollback(&self) {} /// Ping the [ProxyDatabase], it should return an error if the database is not available fn ping(&self) -> Result<(), DbErr> { Ok(()) } } /// Defines the results obtained from a [ProxyDatabase] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ProxyExecResult { /// The last inserted id on auto-increment pub last_insert_id: u64, /// The number of rows affected by the database operation pub rows_affected: u64, } impl ProxyExecResult { /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected pub fn new(last_insert_id: u64, rows_affected: u64) -> Self { Self { last_insert_id, rows_affected, } } } impl Default for ExecResultHolder { fn default() -> Self { Self::Proxy(ProxyExecResult::default()) } } impl From for ExecResult { fn from(result: ProxyExecResult) -> Self { Self { result: ExecResultHolder::Proxy(result), } } } impl From for ProxyExecResult { fn from(result: ExecResult) -> Self { match result.result { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => Self { last_insert_id: result.last_insert_id(), rows_affected: result.rows_affected(), }, #[cfg(feature = "sqlx-postgres")] ExecResultHolder::SqlxPostgres(result) => Self { last_insert_id: 0, rows_affected: result.rows_affected(), }, #[cfg(feature = "sqlx-sqlite")] ExecResultHolder::SqlxSqlite(result) => Self { last_insert_id: result.last_insert_rowid() as u64, rows_affected: result.rows_affected(), }, #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => Self { last_insert_id: result.last_insert_id, rows_affected: result.rows_affected, }, ExecResultHolder::Proxy(result) => result, } } } /// Defines the structure of a Row for the [ProxyDatabase] /// which is just a [BTreeMap]<[String], [Value]> #[derive(Clone, Debug, Default)] pub struct ProxyRow { /// The values of the single row pub values: BTreeMap, } impl ProxyRow { /// Create a new [ProxyRow] from a [BTreeMap]<[String], [Value]> pub fn new(values: BTreeMap) -> Self { Self { values } } } impl From> for ProxyRow { fn from(values: BTreeMap) -> Self { Self { values } } } impl From for BTreeMap { fn from(row: ProxyRow) -> Self { row.values } } impl From for Vec<(String, Value)> { fn from(row: ProxyRow) -> Self { row.values.into_iter().collect() } } impl From for QueryResult { fn from(row: ProxyRow) -> Self { QueryResult { row: QueryResultRow::Proxy(row), } } } #[cfg(feature = "with-json")] impl From for serde_json::Value { fn from(val: ProxyRow) -> serde_json::Value { val.values .into_iter() .map(|(k, v)| (k, sea_query::sea_value_to_json_value(&v))) .collect() } } /// Convert [QueryResult] to [ProxyRow] pub fn from_query_result_to_proxy_row(result: &QueryResult) -> ProxyRow { match &result.row { #[cfg(feature = "sqlx-mysql")] QueryResultRow::SqlxMySql(row) => crate::from_sqlx_mysql_row_to_proxy_row(row), #[cfg(feature = "sqlx-postgres")] QueryResultRow::SqlxPostgres(row) => crate::from_sqlx_postgres_row_to_proxy_row(row), #[cfg(feature = "sqlx-sqlite")] QueryResultRow::SqlxSqlite(row) => crate::from_sqlx_sqlite_row_to_proxy_row(row), #[cfg(feature = "mock")] QueryResultRow::Mock(row) => ProxyRow { values: row.values.clone(), }, QueryResultRow::Proxy(row) => row.to_owned(), } } impl ProxyRow { /// Get a value from the [ProxyRow] pub fn try_get(&self, index: I) -> Result where T: ValueType, { if let Some(index) = index.as_str() { T::try_from( self.values .get(index) .ok_or_else(|| query_err(format!("No column for ColIdx {index:?}")))? .clone(), ) .map_err(type_err) } else if let Some(index) = index.as_usize() { let (_, value) = self .values .iter() .nth(*index) .ok_or_else(|| query_err(format!("Column at index {index} not found")))?; T::try_from(value.clone()).map_err(type_err) } else { unreachable!("Missing ColIdx implementation for ProxyRow"); } } /// An iterator over the keys and values of a proxy row pub fn into_column_value_tuples(self) -> impl Iterator { self.values.into_iter() } } #[cfg(test)] mod tests { use crate::{ Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, entity::*, tests_cfg::*, }; use std::sync::Arc; #[derive(Debug)] struct ProxyDb {} impl ProxyDatabaseTrait for ProxyDb { fn query(&self, statement: Statement) -> Result, DbErr> { println!("SQL query: {}", statement.sql); Ok(vec![].into()) } fn execute(&self, statement: Statement) -> Result { println!("SQL execute: {}", statement.sql); Ok(ProxyExecResult { last_insert_id: 1, rows_affected: 1, }) } } #[test] fn create_proxy_conn() { let _db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {}))).unwrap(); } #[test] fn select_rows() { let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {}))).unwrap(); let _ = cake::Entity::find().all(&db); } #[test] fn insert_one_row() { let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {}))).unwrap(); let item = cake::ActiveModel { id: NotSet, name: Set("Alice".to_string()), }; cake::Entity::insert(item).exec(&db).unwrap(); } } ================================================ FILE: sea-orm-sync/src/database/restricted_connection.rs ================================================ use crate::{ AccessMode, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbBackend, DbErr, ExecResult, IsolationLevel, QueryResult, Statement, StatementBuilder, TransactionError, TransactionSession, TransactionTrait, }; use crate::{ TransactionOptions, rbac::{ PermissionRequest, RbacEngine, RbacError, RbacPermissionsByResources, RbacResourcesAndPermissions, RbacRoleHierarchyList, RbacRolesAndRanks, RbacUserRolePermissions, ResourceRequest, entity::{role::RoleId, user::UserId}, }, }; use std::{ pin::Pin, sync::{Arc, RwLock}, }; use tracing::instrument; /// Wrapper of [`DatabaseConnection`] that performs authorization on all executed /// queries for the current user. Note that raw SQL [`Statement`] is not allowed /// currently. #[derive(Debug, Clone)] #[cfg_attr(docsrs, doc(cfg(feature = "rbac")))] pub struct RestrictedConnection { pub(crate) user_id: UserId, pub(crate) conn: DatabaseConnection, } /// Wrapper of [`DatabaseTransaction`] that performs authorization on all executed /// queries for the current user. Note that raw SQL [`Statement`] is not allowed /// currently. #[derive(Debug)] pub struct RestrictedTransaction { user_id: UserId, conn: DatabaseTransaction, rbac: RbacEngineMount, } #[derive(Debug, Default, Clone)] pub(crate) struct RbacEngineMount { inner: Arc>>, } impl ConnectionTrait for RestrictedConnection { fn get_database_backend(&self) -> DbBackend { self.conn.get_database_backend() } fn execute_raw(&self, stmt: Statement) -> Result { Err(DbErr::RbacError(format!( "Raw query is not supported: {stmt}" ))) } fn execute(&self, stmt: &S) -> Result { self.user_can_run(stmt)?; self.conn.execute(stmt) } fn execute_unprepared(&self, sql: &str) -> Result { Err(DbErr::RbacError(format!( "Raw query is not supported: {sql}" ))) } fn query_one_raw(&self, stmt: Statement) -> Result, DbErr> { Err(DbErr::RbacError(format!( "Raw query is not supported: {stmt}" ))) } fn query_one(&self, stmt: &S) -> Result, DbErr> { self.user_can_run(stmt)?; self.conn.query_one(stmt) } fn query_all_raw(&self, stmt: Statement) -> Result, DbErr> { Err(DbErr::RbacError(format!( "Raw query is not supported: {stmt}" ))) } fn query_all(&self, stmt: &S) -> Result, DbErr> { self.user_can_run(stmt)?; self.conn.query_all(stmt) } } impl ConnectionTrait for RestrictedTransaction { fn get_database_backend(&self) -> DbBackend { self.conn.get_database_backend() } fn execute_raw(&self, stmt: Statement) -> Result { Err(DbErr::RbacError(format!( "Raw query is not supported: {stmt}" ))) } fn execute(&self, stmt: &S) -> Result { self.user_can_run(stmt)?; self.conn.execute(stmt) } fn execute_unprepared(&self, sql: &str) -> Result { Err(DbErr::RbacError(format!( "Raw query is not supported: {sql}" ))) } fn query_one_raw(&self, stmt: Statement) -> Result, DbErr> { Err(DbErr::RbacError(format!( "Raw query is not supported: {stmt}" ))) } fn query_one(&self, stmt: &S) -> Result, DbErr> { self.user_can_run(stmt)?; self.conn.query_one(stmt) } fn query_all_raw(&self, stmt: Statement) -> Result, DbErr> { Err(DbErr::RbacError(format!( "Raw query is not supported: {stmt}" ))) } fn query_all(&self, stmt: &S) -> Result, DbErr> { self.user_can_run(stmt)?; self.conn.query_all(stmt) } } impl RestrictedConnection { /// Get the [`RbacUserId`] bounded to this connection. pub fn user_id(&self) -> UserId { self.user_id } /// Returns `()` if the current user can execute / query the given SQL statement. /// Returns `DbErr::AccessDenied` otherwise. pub fn user_can_run(&self, stmt: &S) -> Result<(), DbErr> { self.conn.rbac.user_can_run(self.user_id, stmt) } /// Returns true if the current user can perform action on resource pub fn user_can(&self, permission: P, resource: R) -> Result where P: Into, R: Into, { self.conn.rbac.user_can(self.user_id, permission, resource) } /// Get current user's role and associated permissions. /// This includes permissions "inherited" from child roles. pub fn current_user_role_permissions(&self) -> Result { self.conn.rbac.user_role_permissions(self.user_id) } /// Get a list of all roles and their ranks. /// Rank is defined as (1 + number of child roles). pub fn roles_and_ranks(&self) -> Result { self.conn.rbac.roles_and_ranks() } /// Get two lists of all resources and permissions, excluding wildcards. pub fn resources_and_permissions(&self) -> Result { self.conn.rbac.resources_and_permissions() } /// Get a list of edges walking the role hierarchy tree pub fn role_hierarchy_edges(&self, role_id: RoleId) -> Result { self.conn.rbac.role_hierarchy_edges(role_id) } /// Get a list of permissions for the specific role, grouped by resources. /// This does not include permissions of child roles. pub fn role_permissions_by_resources( &self, role_id: RoleId, ) -> Result { self.conn.rbac.role_permissions_by_resources(role_id) } } impl RestrictedTransaction { /// Get the [`RbacUserId`] bounded to this connection. pub fn user_id(&self) -> UserId { self.user_id } /// Returns `()` if the current user can execute / query the given SQL statement. /// Returns `DbErr::AccessDenied` otherwise. pub fn user_can_run(&self, stmt: &S) -> Result<(), DbErr> { self.rbac.user_can_run(self.user_id, stmt) } /// Returns true if the current user can perform action on resource pub fn user_can(&self, permission: P, resource: R) -> Result where P: Into, R: Into, { self.rbac.user_can(self.user_id, permission, resource) } } impl TransactionTrait for RestrictedConnection { type Transaction = RestrictedTransaction; #[instrument(level = "trace")] fn begin(&self) -> Result { Ok(RestrictedTransaction { user_id: self.user_id, conn: self.conn.begin()?, rbac: self.conn.rbac.clone(), }) } #[instrument(level = "trace")] fn begin_with_config( &self, isolation_level: Option, access_mode: Option, ) -> Result { Ok(RestrictedTransaction { user_id: self.user_id, conn: self.conn.begin_with_config(isolation_level, access_mode)?, rbac: self.conn.rbac.clone(), }) } #[instrument(level = "trace")] fn begin_with_options( &self, options: TransactionOptions, ) -> Result { Ok(RestrictedTransaction { user_id: self.user_id, conn: self.conn.begin_with_options(options)?, rbac: self.conn.rbac.clone(), }) } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. #[instrument(level = "trace", skip(callback))] fn transaction(&self, callback: F) -> Result> where F: for<'c> FnOnce(&'c RestrictedTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let transaction = self.begin().map_err(TransactionError::Connection)?; transaction.run(callback) } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. #[instrument(level = "trace", skip(callback))] fn transaction_with_config( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'c> FnOnce(&'c RestrictedTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let transaction = self .begin_with_config(isolation_level, access_mode) .map_err(TransactionError::Connection)?; transaction.run(callback) } } impl TransactionTrait for RestrictedTransaction { type Transaction = RestrictedTransaction; #[instrument(level = "trace")] fn begin(&self) -> Result { Ok(RestrictedTransaction { user_id: self.user_id, conn: self.conn.begin()?, rbac: self.rbac.clone(), }) } #[instrument(level = "trace")] fn begin_with_config( &self, isolation_level: Option, access_mode: Option, ) -> Result { Ok(RestrictedTransaction { user_id: self.user_id, conn: self.conn.begin_with_config(isolation_level, access_mode)?, rbac: self.rbac.clone(), }) } #[instrument(level = "trace")] fn begin_with_options( &self, options: TransactionOptions, ) -> Result { Ok(RestrictedTransaction { user_id: self.user_id, conn: self.conn.begin_with_options(options)?, rbac: self.rbac.clone(), }) } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. #[instrument(level = "trace", skip(callback))] fn transaction(&self, callback: F) -> Result> where F: for<'c> FnOnce(&'c RestrictedTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let transaction = self.begin().map_err(TransactionError::Connection)?; transaction.run(callback) } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. If it does not return an error, the transaction will be committed. #[instrument(level = "trace", skip(callback))] fn transaction_with_config( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'c> FnOnce(&'c RestrictedTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let transaction = self .begin_with_config(isolation_level, access_mode) .map_err(TransactionError::Connection)?; transaction.run(callback) } } impl TransactionSession for RestrictedTransaction { fn commit(self) -> Result<(), DbErr> { self.commit() } fn rollback(self) -> Result<(), DbErr> { self.rollback() } } impl RestrictedTransaction { /// Runs a transaction to completion passing through the result. /// Rolling back the transaction on encountering an error. #[instrument(level = "trace", skip(callback))] fn run(self, callback: F) -> Result> where F: for<'b> FnOnce(&'b RestrictedTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let res = callback(&self).map_err(TransactionError::Transaction); if res.is_ok() { self.commit().map_err(TransactionError::Connection)?; } else { self.rollback().map_err(TransactionError::Connection)?; } res } /// Commit a transaction #[instrument(level = "trace")] pub fn commit(self) -> Result<(), DbErr> { self.conn.commit() } /// Rolls back a transaction explicitly #[instrument(level = "trace")] pub fn rollback(self) -> Result<(), DbErr> { self.conn.rollback() } } impl RbacEngineMount { pub fn is_some(&self) -> bool { let engine = self.inner.read().expect("RBAC Engine died"); engine.is_some() } pub fn replace(&self, engine: RbacEngine) { let mut inner = self.inner.write().expect("RBAC Engine died"); *inner = Some(engine); } pub fn user_can(&self, user_id: UserId, permission: P, resource: R) -> Result where P: Into, R: Into, { let permission = permission.into(); let resource = resource.into(); // There is nothing we can do if RwLock is poisoned. let holder = self.inner.read().expect("RBAC Engine died"); // Constructor of this struct should ensure engine is not None. let engine = holder.as_ref().expect("RBAC Engine not set"); engine .user_can(user_id, permission, resource) .map_err(map_err) } pub fn user_can_run( &self, user_id: UserId, stmt: &S, ) -> Result<(), DbErr> { let audit = match stmt.audit() { Ok(audit) => audit, Err(err) => return Err(DbErr::RbacError(err.to_string())), }; // There is nothing we can do if RwLock is poisoned. let holder = self.inner.read().expect("RBAC Engine died"); // Constructor of this struct should ensure engine is not None. let engine = holder.as_ref().expect("RBAC Engine not set"); for request in audit.requests { let permission = || PermissionRequest { action: request.access_type.as_str().to_owned(), }; let resource = || ResourceRequest { schema: request.schema_table.0.as_ref().map(|s| s.1.to_string()), table: request.schema_table.1.to_string(), }; if !engine .user_can(user_id, permission(), resource()) .map_err(map_err)? { return Err(DbErr::AccessDenied { permission: permission().action.to_owned(), resource: resource().to_string(), }); } } Ok(()) } pub fn user_role_permissions(&self, user_id: UserId) -> Result { let holder = self.inner.read().expect("RBAC Engine died"); let engine = holder.as_ref().expect("RBAC Engine not set"); engine .get_user_role_permissions(user_id) .map_err(|err| DbErr::RbacError(err.to_string())) } pub fn roles_and_ranks(&self) -> Result { let holder = self.inner.read().expect("RBAC Engine died"); let engine = holder.as_ref().expect("RBAC Engine not set"); engine .get_roles_and_ranks() .map_err(|err| DbErr::RbacError(err.to_string())) } pub fn resources_and_permissions(&self) -> Result { let holder = self.inner.read().expect("RBAC Engine died"); let engine = holder.as_ref().expect("RBAC Engine not set"); Ok(engine.list_resources_and_permissions()) } pub fn role_hierarchy_edges(&self, role_id: RoleId) -> Result { let holder = self.inner.read().expect("RBAC Engine died"); let engine = holder.as_ref().expect("RBAC Engine not set"); Ok(engine.list_role_hierarchy_edges(role_id)) } pub fn role_permissions_by_resources( &self, role_id: RoleId, ) -> Result { let holder = self.inner.read().expect("RBAC Engine died"); let engine = holder.as_ref().expect("RBAC Engine not set"); engine .list_role_permissions_by_resources(role_id) .map_err(|err| DbErr::RbacError(err.to_string())) } } fn map_err(err: RbacError) -> DbErr { DbErr::RbacError(err.to_string()) } ================================================ FILE: sea-orm-sync/src/database/sea_schema_rusqlite.rs ================================================ use crate::{ ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr, QueryResult, QueryResultRow, RuntimeErr, Statement, driver::rusqlite::RusqliteRow as OurRusqliteRow, }; use sea_query::SelectStatement; use sea_schema::rusqlite_types::{RusqliteError, RusqliteRow}; use std::sync::Arc; impl sea_schema::Connection for DatabaseConnection { fn query_all(&self, select: SelectStatement) -> Result, RusqliteError> { map_result(ConnectionTrait::query_all(self, &select)) } fn query_all_raw(&self, sql: String) -> Result, RusqliteError> { map_result(ConnectionTrait::query_all_raw( self, Statement::from_string(self.get_database_backend(), sql), )) } } impl sea_schema::Connection for DatabaseTransaction { fn query_all(&self, select: SelectStatement) -> Result, RusqliteError> { map_result(ConnectionTrait::query_all(self, &select)) } fn query_all_raw(&self, sql: String) -> Result, RusqliteError> { map_result(ConnectionTrait::query_all_raw( self, Statement::from_string(self.get_database_backend(), sql), )) } } fn map_result(result: Result, DbErr>) -> Result, RusqliteError> { match result { Ok(rows) => Ok(rows .into_iter() .filter_map(|r| match r.row { #[cfg(feature = "rusqlite")] QueryResultRow::Rusqlite(OurRusqliteRow { values, .. }) => { Some(RusqliteRow { values }) } #[allow(unreachable_patterns)] _ => None, }) .collect()), Err(err) => Err(match err { DbErr::Conn(RuntimeErr::Rusqlite(err)) => { Arc::into_inner(err).expect("Should only have one owner") } DbErr::Exec(RuntimeErr::Rusqlite(err)) => { Arc::into_inner(err).expect("Should only have one owner") } DbErr::Query(RuntimeErr::Rusqlite(err)) => { Arc::into_inner(err).expect("Should only have one owner") } _ => RusqliteError::InvalidParameterName(err.to_string()), }), } } ================================================ FILE: sea-orm-sync/src/database/sea_schema_shim.rs ================================================ use crate::{ ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr, QueryResult, QueryResultRow, RuntimeErr, Statement, }; use sea_query::SelectStatement; use sea_schema::sqlx_types::SqlxRow; use sqlx::Error as SqlxError; use std::sync::Arc; impl sea_schema::Connection for DatabaseConnection { fn query_all(&self, select: SelectStatement) -> Result, SqlxError> { map_result(ConnectionTrait::query_all(self, &select)) } fn query_all_raw(&self, sql: String) -> Result, SqlxError> { map_result(ConnectionTrait::query_all_raw( self, Statement::from_string(self.get_database_backend(), sql), )) } } impl sea_schema::Connection for DatabaseTransaction { fn query_all(&self, select: SelectStatement) -> Result, SqlxError> { map_result(ConnectionTrait::query_all(self, &select)) } fn query_all_raw(&self, sql: String) -> Result, SqlxError> { map_result(ConnectionTrait::query_all_raw( self, Statement::from_string(self.get_database_backend(), sql), )) } } impl sea_schema::Connection for crate::DatabaseExecutor<'_> { fn query_all(&self, select: SelectStatement) -> Result, SqlxError> { match self { crate::DatabaseExecutor::Connection(conn) => { ::query_all(conn, select) } crate::DatabaseExecutor::Transaction(txn) => { ::query_all(txn, select) } crate::DatabaseExecutor::OwnedTransaction(txn) => { ::query_all(txn, select) } } } fn query_all_raw(&self, sql: String) -> Result, SqlxError> { match self { crate::DatabaseExecutor::Connection(conn) => { ::query_all_raw(conn, sql) } crate::DatabaseExecutor::Transaction(txn) => { ::query_all_raw(txn, sql) } crate::DatabaseExecutor::OwnedTransaction(txn) => { ::query_all_raw(txn, sql) } } } } fn map_result(result: Result, DbErr>) -> Result, SqlxError> { match result { Ok(rows) => Ok(rows .into_iter() .filter_map(|r| match r.row { #[cfg(feature = "sqlx-mysql")] QueryResultRow::SqlxMySql(r) => Some(SqlxRow::MySql(r)), #[cfg(feature = "sqlx-postgres")] QueryResultRow::SqlxPostgres(r) => Some(SqlxRow::Postgres(r)), #[cfg(feature = "sqlx-sqlite")] QueryResultRow::SqlxSqlite(r) => Some(SqlxRow::Sqlite(r)), #[allow(unreachable_patterns)] _ => None, }) .collect()), Err(err) => Err(match err { DbErr::Conn(RuntimeErr::SqlxError(err)) => { Arc::into_inner(err).expect("Should only have one owner") } DbErr::Exec(RuntimeErr::SqlxError(err)) => { Arc::into_inner(err).expect("Should only have one owner") } DbErr::Query(RuntimeErr::SqlxError(err)) => { Arc::into_inner(err).expect("Should only have one owner") } _ => SqlxError::AnyDriverError(Box::new(err)), }), } } ================================================ FILE: sea-orm-sync/src/database/statement.rs ================================================ use crate::DbBackend; #[cfg(feature = "rbac")] pub use sea_query::audit::{AuditTrait, Error as AuditError, QueryAccessAudit}; use sea_query::{MysqlQueryBuilder, PostgresQueryBuilder, SqliteQueryBuilder, inject_parameters}; pub use sea_query::{Value, Values}; use std::fmt; /// Defines an SQL statement #[derive(Debug, Clone, PartialEq)] pub struct Statement { /// The SQL query pub sql: String, /// The values for the SQL statement's parameters pub values: Option, /// The database backend this statement is constructed for. /// The SQL dialect and values should be valid for the DbBackend. pub db_backend: DbBackend, } /// Any type that can build a [Statement] pub trait StatementBuilder { /// Method to build a [Statement] fn build(&self, db_backend: &DbBackend) -> Statement; #[cfg(feature = "rbac")] /// Method to audit access request of query fn audit(&self) -> Result; } impl Statement { /// Create a [Statement] from a [crate::DatabaseBackend] and a raw SQL statement pub fn from_string(db_backend: DbBackend, stmt: T) -> Statement where T: Into, { Statement { sql: stmt.into(), values: None, db_backend, } } /// Create a SQL statement from a [crate::DatabaseBackend], a /// raw SQL statement and param values pub fn from_sql_and_values(db_backend: DbBackend, sql: T, values: I) -> Self where I: IntoIterator, T: Into, { Self::from_string_values_tuple(db_backend, (sql, Values(values.into_iter().collect()))) } pub(crate) fn from_string_values_tuple(db_backend: DbBackend, stmt: (T, Values)) -> Statement where T: Into, { Statement { sql: stmt.0.into(), values: Some(stmt.1), db_backend, } } } impl fmt::Display for Statement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.values { Some(values) => { let string = match self.db_backend { DbBackend::MySql => inject_parameters(&self.sql, &values.0, &MysqlQueryBuilder), DbBackend::Postgres => { inject_parameters(&self.sql, &values.0, &PostgresQueryBuilder) } DbBackend::Sqlite => { inject_parameters(&self.sql, &values.0, &SqliteQueryBuilder) } }; write!(f, "{}", &string) } None => { write!(f, "{}", &self.sql) } } } } macro_rules! build_any_stmt { ($stmt: expr, $db_backend: expr) => { match $db_backend { DbBackend::MySql => $stmt.build(MysqlQueryBuilder), DbBackend::Postgres => $stmt.build(PostgresQueryBuilder), DbBackend::Sqlite => $stmt.build(SqliteQueryBuilder), } }; } macro_rules! build_postgres_stmt { ($stmt: expr, $db_backend: expr) => { match $db_backend { DbBackend::Postgres => $stmt.to_string(PostgresQueryBuilder), DbBackend::MySql | DbBackend::Sqlite => unimplemented!(), } }; } macro_rules! build_query_stmt { ($stmt: ty) => { impl StatementBuilder for $stmt { fn build(&self, db_backend: &DbBackend) -> Statement { let stmt = build_any_stmt!(self, db_backend); Statement::from_string_values_tuple(*db_backend, stmt) } #[cfg(feature = "rbac")] fn audit(&self) -> Result { AuditTrait::audit(self) } } }; } build_query_stmt!(sea_query::InsertStatement); build_query_stmt!(sea_query::SelectStatement); build_query_stmt!(sea_query::UpdateStatement); build_query_stmt!(sea_query::DeleteStatement); build_query_stmt!(sea_query::WithQuery); macro_rules! build_schema_stmt { ($stmt: ty) => { impl StatementBuilder for $stmt { fn build(&self, db_backend: &DbBackend) -> Statement { let stmt = build_any_stmt!(self, db_backend); Statement::from_string(*db_backend, stmt) } #[cfg(feature = "rbac")] fn audit(&self) -> Result { todo!("Audit not supported for {} yet", stringify!($stmt)) } } }; } build_schema_stmt!(sea_query::TableCreateStatement); build_schema_stmt!(sea_query::TableDropStatement); build_schema_stmt!(sea_query::TableAlterStatement); build_schema_stmt!(sea_query::TableRenameStatement); build_schema_stmt!(sea_query::TableTruncateStatement); build_schema_stmt!(sea_query::IndexCreateStatement); build_schema_stmt!(sea_query::IndexDropStatement); build_schema_stmt!(sea_query::ForeignKeyCreateStatement); build_schema_stmt!(sea_query::ForeignKeyDropStatement); macro_rules! build_type_stmt { ($stmt: ty) => { impl StatementBuilder for $stmt { fn build(&self, db_backend: &DbBackend) -> Statement { let stmt = build_postgres_stmt!(self, db_backend); Statement::from_string(*db_backend, stmt) } #[cfg(feature = "rbac")] fn audit(&self) -> Result { Err(AuditError::UnsupportedQuery) } } }; } build_type_stmt!(sea_query::extension::postgres::TypeAlterStatement); build_type_stmt!(sea_query::extension::postgres::TypeCreateStatement); build_type_stmt!(sea_query::extension::postgres::TypeDropStatement); ================================================ FILE: sea-orm-sync/src/database/stream/metric.rs ================================================ use std::time::Duration; use crate::{DbErr, QueryResult, Statement}; #[cfg(not(feature = "sync"))] type PinBoxStream<'a> = Pin> + 'a>>; #[cfg(feature = "sync")] type PinBoxStream<'a> = Box> + 'a>; pub(crate) struct MetricStream<'a> { metric_callback: &'a Option, stmt: &'a Statement, elapsed: Option, stream: PinBoxStream<'a>, } impl<'a> MetricStream<'a> { #[allow(dead_code)] pub(crate) fn new( metric_callback: &'a Option, stmt: &'a Statement, elapsed: Option, stream: S, ) -> Self where S: Iterator> + 'a, { MetricStream { metric_callback, stmt, elapsed, stream: Box::new(stream), } } } #[cfg(not(feature = "sync"))] impl Stream for MetricStream<'_> { type Item = Result; fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { let this = self.get_mut(); let _start = this .metric_callback .is_some() .then(std::time::SystemTime::now); let res = Pin::new(&mut this.stream).poll_next(cx); if let (Some(_start), Some(elapsed)) = (_start, &mut this.elapsed) { *elapsed += _start.elapsed().unwrap_or_default(); } res } } #[cfg(feature = "sync")] impl Iterator for MetricStream<'_> { type Item = Result; fn next(&mut self) -> Option { let _start = self .metric_callback .is_some() .then(std::time::SystemTime::now); let res = self.stream.next(); if let (Some(_start), Some(elapsed)) = (_start, &mut self.elapsed) { *elapsed += _start.elapsed().unwrap_or_default(); } res } } impl Drop for MetricStream<'_> { fn drop(&mut self) { if let (Some(callback), Some(elapsed)) = (self.metric_callback.as_deref(), self.elapsed) { let info = crate::metric::Info { elapsed, statement: self.stmt, failed: false, }; callback(&info); } } } ================================================ FILE: sea-orm-sync/src/database/stream/mod.rs ================================================ mod metric; mod query; mod transaction; pub use query::*; pub use transaction::*; ================================================ FILE: sea-orm-sync/src/database/stream/query.rs ================================================ #![allow(missing_docs, unreachable_code, unused_variables)] use tracing::instrument; #[cfg(feature = "sqlx-dep")] use futures_util::TryStreamExt; #[cfg(feature = "sqlx-dep")] use sqlx::Executor; use super::metric::MetricStream; #[cfg(feature = "sqlx-dep")] use crate::driver::*; use crate::{DbErr, InnerConnection, QueryResult, Statement}; /// Creates a stream from a [QueryResult] #[ouroboros::self_referencing] pub struct QueryStream { stmt: Statement, conn: InnerConnection, metric_callback: Option, #[borrows(mut conn, stmt, metric_callback)] #[not_covariant] stream: MetricStream<'this>, } impl std::fmt::Debug for QueryStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "QueryStream") } } impl QueryStream { #[allow(dead_code)] #[instrument(level = "trace", skip(metric_callback))] pub(crate) fn build( stmt: Statement, conn: InnerConnection, metric_callback: Option, ) -> QueryStream { QueryStreamBuilder { stmt, conn, metric_callback, stream_builder: |conn, stmt, _metric_callback| match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(c) => { let query = crate::driver::sqlx_mysql::sqlx_query(stmt); let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c .fetch(query) .map_ok(Into::into) .map_err(sqlx_error_to_query_err); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(c) => { let query = crate::driver::sqlx_postgres::sqlx_query(stmt); let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c .fetch(query) .map_ok(Into::into) .map_err(sqlx_error_to_query_err); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(c) => { let query = crate::driver::sqlx_sqlite::sqlx_query(stmt); let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c .fetch(query) .map_ok(Into::into) .map_err(sqlx_error_to_query_err); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(conn) => { use itertools::Either; let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = match conn.stream(stmt) { Ok(rows) => Either::Left(rows.into_iter().map(Ok)), Err(err) => Either::Right(std::iter::once(Err(err))), }; let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "mock")] InnerConnection::Mock(c) => { let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c.fetch(stmt); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "proxy")] InnerConnection::Proxy(c) => { let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = futures_util::stream::once({ Err(DbErr::BackendNotSupported { db: "Proxy", ctx: "QueryStream", }) }); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[allow(unreachable_patterns)] _ => unreachable!(), }, } .build() } } #[cfg(not(feature = "sync"))] impl Stream for QueryStream { type Item = Result; fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { let this = self.get_mut(); this.with_stream_mut(|stream| Pin::new(stream).poll_next(cx)) } } #[cfg(feature = "sync")] impl Iterator for QueryStream { type Item = Result; fn next(&mut self) -> Option { self.with_stream_mut(|stream| stream.next()) } } ================================================ FILE: sea-orm-sync/src/database/stream/transaction.rs ================================================ #![allow(missing_docs)] use std::ops::DerefMut; use tracing::instrument; #[cfg(feature = "sqlx-dep")] use futures_util::TryStreamExt; use std::sync::MutexGuard; #[cfg(feature = "sqlx-dep")] use sqlx::Executor; use super::metric::MetricStream; #[cfg(feature = "sqlx-dep")] use crate::driver::*; use crate::{DbErr, InnerConnection, QueryResult, Statement}; /// `TransactionStream` cannot be used in a `transaction` closure as it does not impl `Send`. /// It seems to be a Rust limitation right now, and solution to work around this deemed to be extremely hard. #[ouroboros::self_referencing] pub struct TransactionStream<'a> { stmt: Statement, conn: MutexGuard<'a, InnerConnection>, metric_callback: Option, #[borrows(mut conn, stmt, metric_callback)] #[not_covariant] stream: MetricStream<'this>, } impl std::fmt::Debug for TransactionStream<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "TransactionStream") } } impl TransactionStream<'_> { #[instrument(level = "trace", skip(metric_callback))] #[allow(unused_variables)] pub(crate) fn build( conn: MutexGuard<'_, InnerConnection>, stmt: Statement, metric_callback: Option, ) -> TransactionStream<'_> { TransactionStreamBuilder { stmt, conn, metric_callback, stream_builder: |conn, stmt, _metric_callback| match conn.deref_mut() { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(c) => { let query = crate::driver::sqlx_mysql::sqlx_query(stmt); let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c .fetch(query) .map_ok(Into::into) .map_err(sqlx_error_to_query_err); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(c) => { let query = crate::driver::sqlx_postgres::sqlx_query(stmt); let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c .fetch(query) .map_ok(Into::into) .map_err(sqlx_error_to_query_err); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(c) => { let query = crate::driver::sqlx_sqlite::sqlx_query(stmt); let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c .fetch(query) .map_ok(Into::into) .map_err(sqlx_error_to_query_err); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "mock")] InnerConnection::Mock(c) => { let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = c.fetch(stmt); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[cfg(feature = "proxy")] InnerConnection::Proxy(c) => { let start = _metric_callback.is_some().then(std::time::SystemTime::now); let stream = futures_util::stream::once({ Err(DbErr::BackendNotSupported { db: "Proxy", ctx: "TransactionStream", }) }); let elapsed = start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } #[allow(unreachable_patterns)] _ => unreachable!(), }, } .build() } } #[cfg(not(feature = "sync"))] impl Stream for TransactionStream<'_> { type Item = Result; fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { let this = self.get_mut(); this.with_stream_mut(|stream| Pin::new(stream).poll_next(cx)) } } #[cfg(feature = "sync")] impl Iterator for TransactionStream<'_> { type Item = Result; fn next(&mut self) -> Option { self.with_stream_mut(|stream| stream.next()) } } ================================================ FILE: sea-orm-sync/src/database/tracing_spans.rs ================================================ //! Tracing support for database operations. //! //! This module provides utilities for instrumenting database operations //! with tracing spans. Enable the `tracing-spans` feature to automatically //! generate spans for all database operations. //! //! # Example //! //! ```toml //! [dependencies] //! sea-orm = { version = "2.0", features = ["tracing-spans"] } //! ``` //! //! ```ignore //! use sea_orm::Database; //! //! // Set up a tracing subscriber //! tracing_subscriber::fmt() //! .with_max_level(tracing::Level::INFO) //! .init(); //! //! // All database operations will now generate tracing spans //! let db = Database::connect("sqlite::memory:")?; //! let cakes = Cake::find().all(&db)?; // Generates a span //! ``` #[cfg(feature = "tracing-spans")] mod inner { use crate::DbBackend; /// Database operation type, following OpenTelemetry conventions. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum DbOperation { /// SELECT query Select, /// INSERT statement Insert, /// UPDATE statement Update, /// DELETE statement Delete, /// Other/unknown SQL execution Execute, } impl DbOperation { /// Parse the operation type from an SQL query string. /// /// This function is allocation-free and uses case-insensitive comparison. pub fn from_sql(sql: &str) -> Self { let first_word = sql.trim_start().split_whitespace().next().unwrap_or(""); if first_word.eq_ignore_ascii_case("SELECT") { DbOperation::Select } else if first_word.eq_ignore_ascii_case("INSERT") { DbOperation::Insert } else if first_word.eq_ignore_ascii_case("UPDATE") { DbOperation::Update } else if first_word.eq_ignore_ascii_case("DELETE") { DbOperation::Delete } else { DbOperation::Execute } } } impl std::fmt::Display for DbOperation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DbOperation::Select => write!(f, "SELECT"), DbOperation::Insert => write!(f, "INSERT"), DbOperation::Update => write!(f, "UPDATE"), DbOperation::Delete => write!(f, "DELETE"), DbOperation::Execute => write!(f, "EXECUTE"), } } } /// Get the OpenTelemetry system name from DbBackend. pub(crate) fn db_system_name(backend: DbBackend) -> &'static str { match backend { DbBackend::Postgres => "postgresql", DbBackend::MySql => "mysql", DbBackend::Sqlite => "sqlite", } } /// Record query result on a span (success/failure status and error message). pub(crate) fn record_query_result( span: &tracing::Span, result: &Result, ) { match result { Ok(_) => { span.record("otel.status_code", "OK"); } Err(e) => { span.record("otel.status_code", "ERROR"); span.record("exception.message", tracing::field::display(e)); } } } } #[cfg(feature = "tracing-spans")] pub(crate) use inner::*; /// Create a tracing span for database operations. /// /// Arguments: /// - `$name`: Span name (e.g., "sea_orm.execute", "sea_orm.query_one") /// - `$backend`: DbBackend value /// - `$sql`: SQL statement string (used for operation parsing) /// /// Note: `db.statement` is set to Empty. Call `span.record("db.statement", sql)` /// separately if the query is parameterized (safe to log). #[cfg(feature = "tracing-spans")] macro_rules! db_span { ($name:expr, $backend:expr, $sql:expr) => {{ let sql: &str = $sql; let op = $crate::database::tracing_spans::DbOperation::from_sql(sql); ::tracing::info_span!( $name, otel.kind = "client", db.system = $crate::database::tracing_spans::db_system_name($backend), db.operation = %op, db.statement = ::tracing::field::Empty, otel.status_code = ::tracing::field::Empty, exception.message = ::tracing::field::Empty, ) }}; } #[cfg(feature = "tracing-spans")] pub(crate) use db_span; /// Execute a future and wrap it in a tracing span when `tracing-spans` is enabled. /// /// When the feature is disabled, this macro simply awaits the future with zero overhead. /// /// # Arguments /// - `$name`: span name (e.g., "sea_orm.execute") /// - `$backend`: DbBackend /// - `$sql`: &str used for db.operation parsing /// - `record_stmt`: whether to record `db.statement` /// - `$fut`: the future to execute macro_rules! with_db_span { ($name:expr, $backend:expr, $sql:expr, record_stmt = $record_stmt:expr, $fut:expr) => {{ #[cfg(all(feature = "tracing-spans", not(feature = "sync")))] { let span = $crate::database::tracing_spans::db_span!($name, $backend, $sql); if $record_stmt { span.record("db.statement", $sql); } let result = ::tracing::Instrument::instrument($fut, span.clone()); $crate::database::tracing_spans::record_query_result(&span, &result); result } #[cfg(all(feature = "tracing-spans", feature = "sync"))] { let span = $crate::database::tracing_spans::db_span!($name, $backend, $sql); if $record_stmt { span.record("db.statement", $sql); } span.in_scope($fut) } #[cfg(not(feature = "tracing-spans"))] { $fut } }}; } pub(crate) use with_db_span; #[cfg(feature = "tracing-spans")] #[cfg(test)] mod tests { use super::*; #[test] fn test_db_operation_from_sql() { assert_eq!( DbOperation::from_sql("SELECT * FROM users"), DbOperation::Select ); assert_eq!( DbOperation::from_sql(" SELECT * FROM users"), DbOperation::Select ); assert_eq!( DbOperation::from_sql("select * from users"), DbOperation::Select ); assert_eq!( DbOperation::from_sql("INSERT INTO users"), DbOperation::Insert ); assert_eq!( DbOperation::from_sql("UPDATE users SET"), DbOperation::Update ); assert_eq!( DbOperation::from_sql("DELETE FROM users"), DbOperation::Delete ); assert_eq!( DbOperation::from_sql("CREATE TABLE users"), DbOperation::Execute ); assert_eq!( DbOperation::from_sql("DROP TABLE users"), DbOperation::Execute ); } #[test] fn test_db_system_name() { assert_eq!(db_system_name(DbBackend::Postgres), "postgresql"); assert_eq!(db_system_name(DbBackend::MySql), "mysql"); assert_eq!(db_system_name(DbBackend::Sqlite), "sqlite"); } #[test] fn test_db_operation_display() { assert_eq!(DbOperation::Select.to_string(), "SELECT"); assert_eq!(DbOperation::Insert.to_string(), "INSERT"); assert_eq!(DbOperation::Update.to_string(), "UPDATE"); assert_eq!(DbOperation::Delete.to_string(), "DELETE"); assert_eq!(DbOperation::Execute.to_string(), "EXECUTE"); } } ================================================ FILE: sea-orm-sync/src/database/transaction.rs ================================================ #![allow(unused_assignments)] use std::sync::Arc; #[cfg(feature = "sqlx-dep")] use sqlx::TransactionManager; use std::sync::Mutex; use tracing::instrument; use crate::{ AccessMode, ConnectionTrait, DbBackend, DbErr, ExecResult, InnerConnection, IsolationLevel, QueryResult, SqliteTransactionMode, Statement, StreamTrait, TransactionOptions, TransactionSession, TransactionStream, TransactionTrait, debug_print, error::*, }; #[cfg(feature = "sqlx-dep")] use crate::{sqlx_error_to_exec_err, sqlx_error_to_query_err}; /// Defines a database transaction, whether it is an open transaction and the type of /// backend to use. /// Under the hood, a Transaction is just a wrapper for a connection where /// START TRANSACTION has been executed. pub struct DatabaseTransaction { conn: Arc>, backend: DbBackend, open: bool, metric_callback: Option, } impl std::fmt::Debug for DatabaseTransaction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "DatabaseTransaction") } } impl DatabaseTransaction { #[instrument(level = "trace", skip(metric_callback))] pub(crate) fn begin( conn: Arc>, backend: DbBackend, metric_callback: Option, isolation_level: Option, access_mode: Option, sqlite_transaction_mode: Option, ) -> Result { let res = DatabaseTransaction { conn, backend, open: true, metric_callback, }; let begin_result: Result<(), DbErr> = super::tracing_spans::with_db_span!( "sea_orm.begin", backend, "BEGIN", record_stmt = false, { #[cfg(not(feature = "sync"))] let conn = &mut *res.conn.lock(); #[cfg(feature = "sync")] let conn = &mut *res.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(c) => { // in MySQL SET TRANSACTION operations must be executed before transaction start crate::driver::sqlx_mysql::set_transaction_config( c, isolation_level, access_mode, )?; ::TransactionManager::begin(c, None) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(c) => { ::TransactionManager::begin(c, None) .map_err(sqlx_error_to_query_err)?; // in PostgreSQL SET TRANSACTION operations must be executed inside transaction crate::driver::sqlx_postgres::set_transaction_config( c, isolation_level, access_mode, ) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(c) => { crate::driver::sqlx_sqlite::set_transaction_config( c, isolation_level, access_mode, )?; let depth = ::TransactionManager::get_transaction_depth(c); let statement = if depth == 0 { sqlite_transaction_mode.map(|mode| { std::borrow::Cow::from(format!("BEGIN {}", mode.sqlite_keyword())) }) } else { // Nested transaction uses SAVEPOINT; the mode only applies to the top-level BEGIN None }; ::TransactionManager::begin(c, statement) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(c) => c.begin(sqlite_transaction_mode), #[cfg(feature = "mock")] InnerConnection::Mock(c) => { c.begin(); Ok(()) } #[cfg(feature = "proxy")] InnerConnection::Proxy(c) => { c.begin(); Ok(()) } #[allow(unreachable_patterns)] _ => Err(conn_err("Disconnected")), } } ); begin_result?; Ok(res) } /// Runs a transaction to completion passing through the result. /// Rolling back the transaction on encountering an error. #[instrument(level = "trace", skip(callback))] pub(crate) fn run(self, callback: F) -> Result> where F: for<'b> FnOnce(&'b DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let res = callback(&self).map_err(TransactionError::Transaction); if res.is_ok() { self.commit().map_err(TransactionError::Connection)?; } else { self.rollback().map_err(TransactionError::Connection)?; } res } /// Commit a transaction #[instrument(level = "trace")] #[allow(unreachable_code, unused_mut)] pub fn commit(mut self) -> Result<(), DbErr> { let result: Result<(), DbErr> = super::tracing_spans::with_db_span!( "sea_orm.commit", self.backend, "COMMIT", record_stmt = false, { #[cfg(not(feature = "sync"))] let conn = &mut *self.conn.lock(); #[cfg(feature = "sync")] let conn = &mut *self.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(c) => { ::TransactionManager::commit(c) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(c) => { ::TransactionManager::commit(c) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(c) => { ::TransactionManager::commit(c) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(c) => c.commit(), #[cfg(feature = "mock")] InnerConnection::Mock(c) => { c.commit(); Ok(()) } #[cfg(feature = "proxy")] InnerConnection::Proxy(c) => { c.commit(); Ok(()) } #[allow(unreachable_patterns)] _ => Err(conn_err("Disconnected")), } } ); result?; self.open = false; // read by start_rollback Ok(()) } /// Rolls back a transaction explicitly #[instrument(level = "trace")] #[allow(unreachable_code, unused_mut)] pub fn rollback(mut self) -> Result<(), DbErr> { let result: Result<(), DbErr> = super::tracing_spans::with_db_span!( "sea_orm.rollback", self.backend, "ROLLBACK", record_stmt = false, { #[cfg(not(feature = "sync"))] let conn = &mut *self.conn.lock(); #[cfg(feature = "sync")] let conn = &mut *self.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(c) => { ::TransactionManager::rollback(c) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(c) => { ::TransactionManager::rollback(c) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(c) => { ::TransactionManager::rollback(c) .map_err(sqlx_error_to_query_err) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(c) => c.rollback(), #[cfg(feature = "mock")] InnerConnection::Mock(c) => { c.rollback(); Ok(()) } #[cfg(feature = "proxy")] InnerConnection::Proxy(c) => { c.rollback(); Ok(()) } #[allow(unreachable_patterns)] _ => Err(conn_err("Disconnected")), } } ); result?; self.open = false; // read by start_rollback Ok(()) } // the rollback is queued and will be performed on next operation, like returning the connection to the pool #[instrument(level = "trace")] fn start_rollback(&mut self) -> Result<(), DbErr> { if self.open { if let Some(mut conn) = self.conn.try_lock().ok() { match &mut *conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(c) => { ::TransactionManager::start_rollback(c); } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(c) => { ::TransactionManager::start_rollback(c); } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(c) => { ::TransactionManager::start_rollback(c); } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(c) => { c.start_rollback()?; } #[cfg(feature = "mock")] InnerConnection::Mock(c) => { c.rollback(); } #[cfg(feature = "proxy")] InnerConnection::Proxy(c) => { c.start_rollback(); } #[allow(unreachable_patterns)] _ => return Err(conn_err("Disconnected")), } } else { //this should never happen return Err(conn_err("Dropping a locked Transaction")); } } Ok(()) } } impl TransactionSession for DatabaseTransaction { fn commit(self) -> Result<(), DbErr> { self.commit() } fn rollback(self) -> Result<(), DbErr> { self.rollback() } } impl Drop for DatabaseTransaction { fn drop(&mut self) { self.start_rollback().expect("Fail to rollback transaction"); } } impl ConnectionTrait for DatabaseTransaction { fn get_database_backend(&self) -> DbBackend { // this way we don't need to lock just to know the backend self.backend } #[instrument(level = "trace")] #[allow(unused_variables)] fn execute_raw(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); super::tracing_spans::with_db_span!( "sea_orm.execute", self.backend, stmt.sql.as_str(), record_stmt = true, { #[cfg(not(feature = "sync"))] let conn = &mut *self.conn.lock(); #[cfg(feature = "sync")] let conn = &mut *self.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(conn) => { let query = crate::driver::sqlx_mysql::sqlx_query(&stmt); let conn: &mut sqlx::MySqlConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { query.execute(conn).map(Into::into) }) .map_err(sqlx_error_to_exec_err) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(conn) => { let query = crate::driver::sqlx_postgres::sqlx_query(&stmt); let conn: &mut sqlx::PgConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { query.execute(conn).map(Into::into) }) .map_err(sqlx_error_to_exec_err) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(conn) => { let query = crate::driver::sqlx_sqlite::sqlx_query(&stmt); let conn: &mut sqlx::SqliteConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { query.execute(conn).map(Into::into) }) .map_err(sqlx_error_to_exec_err) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(conn) => conn.execute(stmt, &self.metric_callback), #[cfg(feature = "mock")] InnerConnection::Mock(conn) => conn.execute(stmt), #[cfg(feature = "proxy")] InnerConnection::Proxy(conn) => conn.execute(stmt), #[allow(unreachable_patterns)] _ => Err(conn_err("Disconnected")), } } ) } #[instrument(level = "trace")] #[allow(unused_variables)] fn execute_unprepared(&self, sql: &str) -> Result { debug_print!("{}", sql); super::tracing_spans::with_db_span!( "sea_orm.execute_unprepared", self.backend, sql, record_stmt = false, { #[cfg(not(feature = "sync"))] let conn = &mut *self.conn.lock(); #[cfg(feature = "sync")] let conn = &mut *self.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(conn) => { let conn: &mut sqlx::MySqlConnection = &mut *conn; sqlx::Executor::execute(conn, sql) .map(Into::into) .map_err(sqlx_error_to_exec_err) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(conn) => { let conn: &mut sqlx::PgConnection = &mut *conn; sqlx::Executor::execute(conn, sql) .map(Into::into) .map_err(sqlx_error_to_exec_err) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(conn) => { let conn: &mut sqlx::SqliteConnection = &mut *conn; sqlx::Executor::execute(conn, sql) .map(Into::into) .map_err(sqlx_error_to_exec_err) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(conn) => conn.execute_unprepared(sql), #[cfg(feature = "mock")] InnerConnection::Mock(conn) => { let db_backend = conn.get_database_backend(); let stmt = Statement::from_string(db_backend, sql); conn.execute(stmt) } #[cfg(feature = "proxy")] InnerConnection::Proxy(conn) => { let db_backend = conn.get_database_backend(); let stmt = Statement::from_string(db_backend, sql); conn.execute(stmt) } #[allow(unreachable_patterns)] _ => Err(conn_err("Disconnected")), } } ) } #[instrument(level = "trace")] #[allow(unused_variables)] fn query_one_raw(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); super::tracing_spans::with_db_span!( "sea_orm.query_one", self.backend, stmt.sql.as_str(), record_stmt = true, { #[cfg(not(feature = "sync"))] let conn = &mut *self.conn.lock(); #[cfg(feature = "sync")] let conn = &mut *self.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(conn) => { let query = crate::driver::sqlx_mysql::sqlx_query(&stmt); let conn: &mut sqlx::MySqlConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { crate::sqlx_map_err_ignore_not_found( query.fetch_one(conn).map(|row| Some(row.into())), ) }) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(conn) => { let query = crate::driver::sqlx_postgres::sqlx_query(&stmt); let conn: &mut sqlx::PgConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { crate::sqlx_map_err_ignore_not_found( query.fetch_one(conn).map(|row| Some(row.into())), ) }) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(conn) => { let query = crate::driver::sqlx_sqlite::sqlx_query(&stmt); let conn: &mut sqlx::SqliteConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { crate::sqlx_map_err_ignore_not_found( query.fetch_one(conn).map(|row| Some(row.into())), ) }) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(conn) => conn.query_one(stmt, &self.metric_callback), #[cfg(feature = "mock")] InnerConnection::Mock(conn) => conn.query_one(stmt), #[cfg(feature = "proxy")] InnerConnection::Proxy(conn) => conn.query_one(stmt), #[allow(unreachable_patterns)] _ => Err(conn_err("Disconnected")), } } ) } #[instrument(level = "trace")] #[allow(unused_variables)] fn query_all_raw(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); super::tracing_spans::with_db_span!( "sea_orm.query_all", self.backend, stmt.sql.as_str(), record_stmt = true, { #[cfg(not(feature = "sync"))] let conn = &mut *self.conn.lock(); #[cfg(feature = "sync")] let conn = &mut *self.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; match conn { #[cfg(feature = "sqlx-mysql")] InnerConnection::MySql(conn) => { let query = crate::driver::sqlx_mysql::sqlx_query(&stmt); let conn: &mut sqlx::MySqlConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { query .fetch_all(conn) .map(|rows| rows.into_iter().map(|r| r.into()).collect()) .map_err(sqlx_error_to_query_err) }) } #[cfg(feature = "sqlx-postgres")] InnerConnection::Postgres(conn) => { let query = crate::driver::sqlx_postgres::sqlx_query(&stmt); let conn: &mut sqlx::PgConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { query .fetch_all(conn) .map(|rows| rows.into_iter().map(|r| r.into()).collect()) .map_err(sqlx_error_to_query_err) }) } #[cfg(feature = "sqlx-sqlite")] InnerConnection::Sqlite(conn) => { let query = crate::driver::sqlx_sqlite::sqlx_query(&stmt); let conn: &mut sqlx::SqliteConnection = &mut *conn; crate::metric::metric!(self.metric_callback, &stmt, { query .fetch_all(conn) .map(|rows| rows.into_iter().map(|r| r.into()).collect()) .map_err(sqlx_error_to_query_err) }) } #[cfg(feature = "rusqlite")] InnerConnection::Rusqlite(conn) => conn.query_all(stmt, &self.metric_callback), #[cfg(feature = "mock")] InnerConnection::Mock(conn) => conn.query_all(stmt), #[cfg(feature = "proxy")] InnerConnection::Proxy(conn) => conn.query_all(stmt), #[allow(unreachable_patterns)] _ => Err(conn_err("Disconnected")), } } ) } } impl StreamTrait for DatabaseTransaction { type Stream<'a> = TransactionStream<'a>; fn get_database_backend(&self) -> DbBackend { self.backend } #[instrument(level = "trace")] fn stream_raw<'a>(&'a self, stmt: Statement) -> Result, DbErr> { ({ #[cfg(not(feature = "sync"))] let conn = self.conn.lock(); #[cfg(feature = "sync")] let conn = self.conn.lock().map_err(|_| DbErr::MutexPoisonError)?; Ok(crate::TransactionStream::build( conn, stmt, self.metric_callback.clone(), )) }) } } impl TransactionTrait for DatabaseTransaction { type Transaction = DatabaseTransaction; #[instrument(level = "trace")] fn begin(&self) -> Result { DatabaseTransaction::begin( Arc::clone(&self.conn), self.backend, self.metric_callback.clone(), None, None, None, ) } #[instrument(level = "trace")] fn begin_with_config( &self, isolation_level: Option, access_mode: Option, ) -> Result { DatabaseTransaction::begin( Arc::clone(&self.conn), self.backend, self.metric_callback.clone(), isolation_level, access_mode, None, ) } #[instrument(level = "trace")] fn begin_with_options( &self, options: TransactionOptions, ) -> Result { DatabaseTransaction::begin( Arc::clone(&self.conn), self.backend, self.metric_callback.clone(), options.isolation_level, options.access_mode, options.sqlite_transaction_mode, ) } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. /// Otherwise, the transaction will be committed. #[instrument(level = "trace", skip(_callback))] fn transaction(&self, _callback: F) -> Result> where F: for<'c> FnOnce(&'c DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let transaction = self.begin().map_err(TransactionError::Connection)?; transaction.run(_callback) } /// Execute the function inside a transaction. /// If the function returns an error, the transaction will be rolled back. /// Otherwise, the transaction will be committed. #[instrument(level = "trace", skip(_callback))] fn transaction_with_config( &self, _callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'c> FnOnce(&'c DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let transaction = self .begin_with_config(isolation_level, access_mode) .map_err(TransactionError::Connection)?; transaction.run(_callback) } } /// Defines errors for handling transaction failures #[derive(Debug)] pub enum TransactionError { /// A Database connection error Connection(DbErr), /// An error occurring when doing database transactions Transaction(E), } impl std::fmt::Display for TransactionError where E: std::fmt::Display + std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TransactionError::Connection(e) => std::fmt::Display::fmt(e, f), TransactionError::Transaction(e) => std::fmt::Display::fmt(e, f), } } } impl std::error::Error for TransactionError where E: std::fmt::Display + std::fmt::Debug {} impl From for TransactionError where E: std::fmt::Display + std::fmt::Debug, { fn from(e: DbErr) -> Self { Self::Connection(e) } } ================================================ FILE: sea-orm-sync/src/docs.rs ================================================ //! 1. Async //! //! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with support from day 1. //! //! ``` //! # use sea_orm::{error::*, tests_cfg::*, *}; //! # //! # #[cfg(all(feature = "mock", not(feature = "sync")))] //! # pub fn main() -> Result<(), DbErr> { //! # //! # let db = MockDatabase::new(DbBackend::Postgres) //! # .append_query_results([ //! # [cake::Model { //! # id: 1, //! # name: "New York Cheese".to_owned(), //! # } //! # .into_mock_row()], //! # [fruit::Model { //! # id: 1, //! # name: "Apple".to_owned(), //! # cake_id: Some(1), //! # } //! # .into_mock_row()], //! # ]) //! # .into_connection(); //! # //! // execute multiple queries in parallel //! let cakes_and_fruits: (Vec, Vec) = //! futures_util::future::try_join(Cake::find().all(&db), Fruit::find().all(&db))?; //! # assert_eq!( //! # cakes_and_fruits, //! # ( //! # vec![cake::Model { //! # id: 1, //! # name: "New York Cheese".to_owned(), //! # }], //! # vec![fruit::Model { //! # id: 1, //! # name: "Apple".to_owned(), //! # cake_id: Some(1), //! # }] //! # ) //! # ); //! # assert_eq!( //! # db.into_transaction_log(), //! # [ //! # Transaction::from_sql_and_values( //! # DbBackend::Postgres, //! # r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, //! # [] //! # ), //! # Transaction::from_sql_and_values( //! # DbBackend::Postgres, //! # r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#, //! # [] //! # ), //! # ] //! # ); //! # Ok(()) //! # } //! # #[cfg(all(feature = "mock", feature = "sync"))] //! # fn main() {} //! ``` //! //! 2. Dynamic //! //! Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. //! //! ``` //! # use sea_query::Query; //! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; //! # fn function(db: DbConn) -> Result<(), DbErr> { //! // build subquery with ease //! let cakes_with_filling: Vec = cake::Entity::find() //! .filter( //! Condition::any().add( //! cake::Column::Id.in_subquery( //! Query::select() //! .column(cake_filling::Column::CakeId) //! .from(cake_filling::Entity) //! .to_owned(), //! ), //! ), //! ) //! .all(&db) //! ?; //! //! # Ok(()) //! # } //! ``` //! //! 3. Testable //! //! Use mock connections to write unit tests for your logic. //! //! ``` //! # use sea_orm::{error::*, entity::*, query::*, tests_cfg::*, DbConn, MockDatabase, Transaction, DbBackend}; //! # fn function(db: DbConn) -> Result<(), DbErr> { //! // Setup mock connection //! let db = MockDatabase::new(DbBackend::Postgres) //! .append_query_results([ //! [ //! cake::Model { //! id: 1, //! name: "New York Cheese".to_owned(), //! }, //! ], //! ]) //! .into_connection(); //! //! // Perform your application logic //! assert_eq!( //! cake::Entity::find().one(&db)?, //! Some(cake::Model { //! id: 1, //! name: "New York Cheese".to_owned(), //! }) //! ); //! //! // Compare it against the expected transaction log //! assert_eq!( //! db.into_transaction_log(), //! [ //! Transaction::from_sql_and_values( //! DbBackend::Postgres, //! r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, //! [1u64.into()] //! ), //! ] //! ); //! # Ok(()) //! # } //! ``` //! //! 4. Service Oriented //! //! Quickly build services that join, filter, sort and paginate data in APIs. //! //! ```ignore //! #[get("/?&")] //! fn list( //! conn: Connection, //! page: Option, //! per_page: Option, //! ) -> Template { //! // Set page number and items per page //! let page = page.unwrap_or(1); //! let per_page = per_page.unwrap_or(10); //! //! // Setup paginator //! let paginator = Post::find() //! .order_by_asc(post::Column::Id) //! .paginate(&conn, per_page); //! let num_pages = paginator.num_pages().unwrap(); //! //! // Fetch paginated posts //! let posts = paginator //! .fetch_page(page - 1) //! //! .expect("could not retrieve posts"); //! //! Template::render( //! "index", //! context! { //! page: page, //! per_page: per_page, //! posts: posts, //! num_pages: num_pages, //! }, //! ) //! } //! ``` ================================================ FILE: sea-orm-sync/src/driver/mock.rs ================================================ use crate::{ DatabaseConnection, DatabaseConnectionType, DbBackend, ExecResult, MockDatabase, QueryResult, Statement, Transaction, debug_print, error::*, }; use std::{ fmt::Debug, pin::Pin, sync::{ Arc, Mutex, atomic::{AtomicUsize, Ordering}, }, }; use tracing::instrument; #[cfg(not(feature = "sync"))] type PinBoxStream = Pin>>>; #[cfg(feature = "sync")] type PinBoxStream = Box>>; /// Defines a database driver for the [MockDatabase] #[derive(Debug)] pub struct MockDatabaseConnector; /// Defines a connection for the [MockDatabase] #[derive(Debug)] pub struct MockDatabaseConnection { execute_counter: AtomicUsize, query_counter: AtomicUsize, mocker: Mutex>, } /// A Trait for any type wanting to perform operations on the [MockDatabase] pub trait MockDatabaseTrait: Debug { /// Execute a statement in the [MockDatabase] fn execute(&mut self, counter: usize, stmt: Statement) -> Result; /// Execute a SQL query in the [MockDatabase] fn query(&mut self, counter: usize, stmt: Statement) -> Result, DbErr>; /// Create a transaction that can be committed atomically fn begin(&mut self); /// Commit a successful transaction atomically into the [MockDatabase] fn commit(&mut self); /// Roll back a transaction since errors were encountered fn rollback(&mut self); /// Get all logs from a [MockDatabase] and return a [Transaction] fn drain_transaction_log(&mut self) -> Vec; /// Get the backend being used in the [MockDatabase] fn get_database_backend(&self) -> DbBackend; /// Ping the [MockDatabase] fn ping(&self) -> Result<(), DbErr>; } impl MockDatabaseConnector { /// Check if the database URI given and the [DatabaseBackend](crate::DatabaseBackend) selected are the same #[allow(unused_variables)] pub fn accepts(string: &str) -> bool { #[cfg(feature = "sqlx-mysql")] if DbBackend::MySql.is_prefix_of(string) { return true; } #[cfg(feature = "sqlx-postgres")] if DbBackend::Postgres.is_prefix_of(string) { return true; } #[cfg(feature = "sqlx-sqlite")] if DbBackend::Sqlite.is_prefix_of(string) { return true; } #[cfg(feature = "rusqlite")] if DbBackend::Sqlite.is_prefix_of(string) { return true; } false } /// Connect to the [MockDatabase] #[allow(unused_variables)] #[instrument(level = "trace")] pub fn connect(string: &str) -> Result { macro_rules! connect_mock_db { ( $syntax: expr ) => { Ok(DatabaseConnectionType::MockDatabaseConnection(Arc::new( MockDatabaseConnection::new(MockDatabase::new($syntax)), )) .into()) }; } #[cfg(feature = "sqlx-mysql")] if crate::SqlxMySqlConnector::accepts(string) { return connect_mock_db!(DbBackend::MySql); } #[cfg(feature = "sqlx-postgres")] if crate::SqlxPostgresConnector::accepts(string) { return connect_mock_db!(DbBackend::Postgres); } #[cfg(feature = "sqlx-sqlite")] if crate::SqlxSqliteConnector::accepts(string) { return connect_mock_db!(DbBackend::Sqlite); } #[cfg(feature = "rusqlite")] if crate::driver::rusqlite::RusqliteConnector::accepts(string) { return connect_mock_db!(DbBackend::Sqlite); } connect_mock_db!(DbBackend::Postgres) } } impl MockDatabaseConnection { /// Create a connection to the [MockDatabase] pub fn new(m: M) -> Self where M: MockDatabaseTrait, M: 'static, { Self { execute_counter: AtomicUsize::new(0), query_counter: AtomicUsize::new(0), mocker: Mutex::new(Box::new(m)), } } pub(crate) fn get_mocker_mutex(&self) -> &Mutex> { &self.mocker } /// Get the [DatabaseBackend](crate::DatabaseBackend) being used by the [MockDatabase] /// /// # Panics /// /// Will panic if the lock cannot be acquired. pub fn get_database_backend(&self) -> DbBackend { self.mocker .lock() .expect("Fail to acquire mocker") .get_database_backend() } /// Execute the SQL statement in the [MockDatabase] #[instrument(level = "trace")] pub fn execute(&self, statement: Statement) -> Result { debug_print!("{}", statement); let counter = self.execute_counter.fetch_add(1, Ordering::SeqCst); self.mocker .lock() .map_err(exec_err)? .execute(counter, statement) } /// Return one [QueryResult] if the query was successful #[instrument(level = "trace")] pub fn query_one(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); let counter = self.query_counter.fetch_add(1, Ordering::SeqCst); let result = self .mocker .lock() .map_err(query_err)? .query(counter, statement)?; Ok(result.into_iter().next()) } /// Return all [QueryResult]s if the query was successful #[instrument(level = "trace")] pub fn query_all(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); let counter = self.query_counter.fetch_add(1, Ordering::SeqCst); self.mocker .lock() .map_err(query_err)? .query(counter, statement) } /// Return [QueryResult]s from a multi-query operation #[instrument(level = "trace")] pub fn fetch(&self, statement: &Statement) -> PinBoxStream { #[cfg(not(feature = "sync"))] { match self.query_all(statement.clone()) { Ok(v) => Box::new(futures_util::stream::iter(v.into_iter().map(Ok))), Err(e) => Box::new(futures_util::stream::iter(Some(Err(e)).into_iter())), } } #[cfg(feature = "sync")] { match self.query_all(statement.clone()) { Ok(v) => Box::new(v.into_iter().map(Ok)), Err(e) => Box::new(Some(Err(e)).into_iter()), } } } /// Create a statement block of SQL statements that execute together. /// /// # Panics /// /// Will panic if the lock cannot be acquired. #[instrument(level = "trace")] pub fn begin(&self) { self.mocker .lock() .expect("Failed to acquire mocker") .begin() } /// Commit a transaction atomically to the database /// /// # Panics /// /// Will panic if the lock cannot be acquired. #[instrument(level = "trace")] pub fn commit(&self) { self.mocker .lock() .expect("Failed to acquire mocker") .commit() } /// Roll back a faulty transaction /// /// # Panics /// /// Will panic if the lock cannot be acquired. #[instrument(level = "trace")] pub fn rollback(&self) { self.mocker .lock() .expect("Failed to acquire mocker") .rollback() } /// Checks if a connection to the database is still valid. pub fn ping(&self) -> Result<(), DbErr> { self.mocker.lock().map_err(query_err)?.ping() } } impl From<( Arc, Statement, Option, )> for crate::QueryStream { fn from( (conn, stmt, metric_callback): ( Arc, Statement, Option, ), ) -> Self { crate::QueryStream::build(stmt, crate::InnerConnection::Mock(conn), metric_callback) } } impl crate::DatabaseTransaction { pub(crate) fn new_mock( inner: Arc, metric_callback: Option, ) -> Result { use std::sync::Mutex; let backend = inner.get_database_backend(); Self::begin( Arc::new(Mutex::new(crate::InnerConnection::Mock(inner))), backend, metric_callback, None, None, None, ) } } ================================================ FILE: sea-orm-sync/src/driver/mod.rs ================================================ #[cfg(feature = "mock")] mod mock; #[cfg(feature = "proxy")] mod proxy; #[cfg(feature = "rusqlite")] pub(crate) mod rusqlite; #[cfg(any(feature = "sqlx-sqlite", feature = "rusqlite"))] mod sqlite; #[cfg(feature = "sqlx-dep")] mod sqlx_common; #[cfg(feature = "sqlx-mysql")] pub(crate) mod sqlx_mysql; #[cfg(feature = "sqlx-postgres")] pub(crate) mod sqlx_postgres; #[cfg(feature = "sqlx-sqlite")] pub(crate) mod sqlx_sqlite; #[cfg(feature = "mock")] pub use mock::*; #[cfg(feature = "proxy")] pub use proxy::*; #[cfg(feature = "sqlx-dep")] pub(crate) use sqlx_common::*; #[cfg(feature = "sqlx-mysql")] pub use sqlx_mysql::*; #[cfg(feature = "sqlx-postgres")] pub use sqlx_postgres::*; #[cfg(feature = "sqlx-sqlite")] pub use sqlx_sqlite::*; ================================================ FILE: sea-orm-sync/src/driver/proxy.rs ================================================ use crate::{ DatabaseConnection, DatabaseConnectionType, DbBackend, ExecResult, ProxyDatabaseTrait, QueryResult, Statement, debug_print, error::*, }; use std::{fmt::Debug, sync::Arc}; use tracing::instrument; /// Defines a database driver for the [ProxyDatabase] #[derive(Debug)] pub struct ProxyDatabaseConnector; /// Defines a connection for the [ProxyDatabase] #[derive(Debug)] pub struct ProxyDatabaseConnection { db_backend: DbBackend, proxy: Arc>, } impl ProxyDatabaseConnector { /// Check if the database URI given and the [DatabaseBackend](crate::DatabaseBackend) selected are the same #[allow(unused_variables)] pub fn accepts(string: &str) -> bool { // As this is a proxy database, it accepts any URI true } /// Connect to the [ProxyDatabase] #[allow(unused_variables)] #[instrument(level = "trace")] pub fn connect( db_type: DbBackend, func: Arc>, ) -> Result { Ok( DatabaseConnectionType::ProxyDatabaseConnection(Arc::new( ProxyDatabaseConnection::new(db_type, func), )) .into(), ) } } impl ProxyDatabaseConnection { /// Create a connection to the [ProxyDatabase] pub fn new(db_backend: DbBackend, funcs: Arc>) -> Self { Self { db_backend, proxy: funcs.to_owned(), } } /// Get the [DatabaseBackend](crate::DatabaseBackend) being used by the [ProxyDatabase] pub fn get_database_backend(&self) -> DbBackend { self.db_backend } /// Execute the SQL statement in the [ProxyDatabase] #[instrument(level = "trace")] pub fn execute(&self, statement: Statement) -> Result { debug_print!("{}", statement); Ok(self.proxy.execute(statement)?.into()) } /// Return one [QueryResult] if the query was successful #[instrument(level = "trace")] pub fn query_one(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); let result = self.proxy.query(statement)?; if let Some(first) = result.first() { return Ok(Some(QueryResult { row: crate::QueryResultRow::Proxy(first.to_owned()), })); } else { return Ok(None); } } /// Return all [QueryResult]s if the query was successful #[instrument(level = "trace")] pub fn query_all(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); let result = self.proxy.query(statement)?; Ok(result .into_iter() .map(|row| QueryResult { row: crate::QueryResultRow::Proxy(row), }) .collect()) } /// Create a statement block of SQL statements that execute together. #[instrument(level = "trace")] pub fn begin(&self) { self.proxy.begin() } /// Commit a transaction atomically to the database #[instrument(level = "trace")] pub fn commit(&self) { self.proxy.commit() } /// Roll back a faulty transaction #[instrument(level = "trace")] pub fn rollback(&self) { self.proxy.rollback() } /// Start rollback a transaction #[instrument(level = "trace")] pub fn start_rollback(&self) { self.proxy.start_rollback() } /// Checks if a connection to the database is still valid. pub fn ping(&self) -> Result<(), DbErr> { self.proxy.ping() } } impl From<( Arc, Statement, Option, )> for crate::QueryStream { fn from( (conn, stmt, metric_callback): ( Arc, Statement, Option, ), ) -> Self { crate::QueryStream::build(stmt, crate::InnerConnection::Proxy(conn), metric_callback) } } impl crate::DatabaseTransaction { pub(crate) fn new_proxy( inner: Arc, metric_callback: Option, ) -> Result { use std::sync::Mutex; let backend = inner.get_database_backend(); Self::begin( Arc::new(Mutex::new(crate::InnerConnection::Proxy(inner))), backend, metric_callback, None, None, None, ) } } ================================================ FILE: sea-orm-sync/src/driver/rusqlite.rs ================================================ use std::{ ops::Deref, sync::{Arc, Mutex, MutexGuard, TryLockError}, time::{Duration, Instant}, }; use tracing::{debug, instrument, warn}; pub use OwnedRow as RusqliteRow; use rusqlite::{ CachedStatement, OpenFlags, Row, types::{FromSql, FromSqlError, Value}, }; pub use rusqlite::{ Connection as RusqliteConnection, Error as RusqliteError, types::Value as RusqliteOwnedValue, }; use sea_query_rusqlite::{RusqliteValue, RusqliteValues, rusqlite}; use crate::{ AccessMode, ColIdx, ConnectOptions, DatabaseConnection, DatabaseConnectionType, DatabaseTransaction, InnerConnection, IsolationLevel, QueryStream, SqliteTransactionMode, Statement, TransactionError, error::*, executor::*, }; /// A helper class to connect to Rusqlite #[derive(Debug)] pub struct RusqliteConnector; const DEFAULT_ACQUIRE_TIMEOUT: Duration = Duration::from_secs(60); /// A shared SQLite connection for synchronous contexts. /// /// Unlike sqlx's connection pool, which maintains multiple connections that can /// be checked out concurrently by different tasks, this holds a single /// [`RusqliteConnection`] behind an `Arc>`. All callers contend on /// the same mutex; [`acquire`](Self::acquire) spins with `try_lock` until the /// mutex is available or the `acquire_timeout` deadline expires. /// /// The connection can also be *loaned* out (via [`State::Loaned`]) for the /// duration of a transaction or streaming query, during which `acquire` will /// keep retrying until the loan is returned. #[derive(Clone)] pub struct RusqliteSharedConnection { pub(crate) conn: Arc>, acquire_timeout: Duration, metric_callback: Option, } /// A loaned connection that supports nested transactions. /// /// Created by [`RusqliteSharedConnection::loan`], which moves the underlying /// [`RusqliteConnection`] out of the shared mutex (leaving it in /// [`State::Loaned`]) so that a transaction or streaming query can hold it /// without blocking other callers on the mutex itself - they will still wait /// via `acquire`, but the mutex is not held for the entire duration. /// /// On [`Drop`], the connection is returned to the shared mutex so that /// subsequent callers can acquire it again. /// /// ## Transaction semantics /// /// Nested transactions follow the same model as sqlx's SQLite driver: /// /// | Depth | `begin` | `commit` | `rollback` | /// |-------|--------------------|-----------------------|-------------------------| /// | 0 | `BEGIN` | - | - | /// | 1 | `SAVEPOINT sp1` | `COMMIT` | `ROLLBACK` | /// | 2 | `SAVEPOINT sp2` | `RELEASE SAVEPOINT sp1` | `ROLLBACK TO sp1` | /// | *n* | `SAVEPOINT sp`*n* | `RELEASE SAVEPOINT sp`*n-1* | `ROLLBACK TO sp`*n-1* | /// /// ## Comparison with rusqlite's native `Transaction` /// /// rusqlite's [`rusqlite::Transaction`] borrows `&mut Connection` for its /// entire lifetime. This makes it difficult to pass around, store in structs, /// or nest - each nested [`rusqlite::Savepoint`] borrows `&mut Transaction`, /// creating a tower of coupled lifetimes that the borrow checker enforces /// strictly. /// /// Here, the connection is *moved* (loaned) into this struct as owned state, /// and nesting is tracked with a simple `transaction_depth` counter. This /// means transactions and savepoints can be started, committed, or rolled back /// through the same flat API without lifetime gymnastics. The connection is /// returned to the shared pool on [`Drop`], rather than relying on the borrow /// ending. pub struct RusqliteInnerConnection { conn: State, loan: Arc>, transaction_depth: u32, } #[derive(Debug)] pub struct RusqliteExecResult { pub(crate) rows_affected: u64, pub(crate) last_insert_rowid: i64, } #[derive(Debug)] pub struct OwnedRow { pub columns: Vec>, pub values: Vec, } #[derive(Debug, Default)] pub enum State { Idle(RusqliteConnection), Loaned, #[default] Disconnected, } impl OwnedRow { pub fn columns(&self) -> &[Arc] { &self.columns } pub fn from_row(columns: Vec>, row: &Row) -> OwnedRow { let mut values = Vec::new(); for i in 0..columns.len() { let v: Value = row.get_unwrap(i); values.push(v); } OwnedRow { columns, values } } pub fn try_get(&self, idx: I) -> Result { let (idx, col, value) = if let Some(idx) = idx.as_usize() { (*idx, None, &self.values[*idx]) } else if let Some(name) = idx.as_str() { if let Some(idx) = self.columns.iter().position(|c| c.deref() == name) { (idx, Some(name), &self.values[idx]) } else { return Err(TryGetError::Null(format!( "column `{name}` does not exist in row" ))); } } else { unreachable!("ColIdx must be either usize or str") }; FromSql::column_result(value.into()) .map_err(|err| match err { FromSqlError::OutOfRange(i) => RusqliteError::IntegralValueOutOfRange(idx, i), FromSqlError::Other(err) => { RusqliteError::FromSqlConversionFailure(idx, value.data_type(), err) } FromSqlError::InvalidBlobSize { .. } => { RusqliteError::FromSqlConversionFailure(idx, value.data_type(), Box::new(err)) } // FromSqlError::InvalidType _ => RusqliteError::InvalidColumnType( idx, col.map(|c| c.to_owned()).unwrap_or_default(), value.data_type(), ), }) .map_err(|err| TryGetError::DbErr(query_err(err))) } } impl std::fmt::Debug for RusqliteSharedConnection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "RusqliteSharedConnection {{ conn: {:?} }}", self.conn) } } impl std::fmt::Debug for RusqliteInnerConnection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "RusqliteInnerConnection {{ conn: {:?} }}", self.conn) } } impl From for RusqliteSharedConnection { fn from(conn: RusqliteConnection) -> Self { RusqliteSharedConnection { conn: Arc::new(Mutex::new(State::Idle(conn))), acquire_timeout: DEFAULT_ACQUIRE_TIMEOUT, metric_callback: None, } } } impl From for DatabaseConnection { fn from(conn: RusqliteSharedConnection) -> Self { DatabaseConnectionType::RusqliteSharedConnection(conn).into() } } impl RusqliteConnector { #[cfg(feature = "mock")] /// Check if the URI provided corresponds to `sqlite:` for a SQLite database pub fn accepts(string: &str) -> bool { string.starts_with("sqlite:") } /// Add configuration options for the SQLite database #[instrument(level = "trace")] pub fn connect(options: ConnectOptions) -> Result { let acquire_timeout = options.acquire_timeout.unwrap_or(DEFAULT_ACQUIRE_TIMEOUT); // TODO handle disable_statement_logging let after_conn = options.after_connect; let raw = options .url .trim_start_matches("sqlite://") .trim_start_matches("sqlite:"); let (path, mode) = match raw.find('?') { Some(q) => { let query = &raw[q + 1..]; let mut mode = None; for kv in query.split('&') { if let Some(val) = kv.strip_prefix("mode=") { mode = Some(val); } else if !kv.is_empty() { return Err(DbErr::Conn(RuntimeErr::Internal(format!( "unsupported SQLite connection parameter: {kv}" )))); } } (&raw[..q], mode) } None => (raw, None), }; let conn = match mode { None | Some("rwc") => RusqliteConnection::open(path), Some("ro") => RusqliteConnection::open_with_flags( path, OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX, ), Some("rw") => RusqliteConnection::open_with_flags( path, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX, ), Some(other) => { return Err(DbErr::Conn(RuntimeErr::Internal(format!( "unknown SQLite mode: {other}" )))); } } .map_err(conn_err)?; let conn = RusqliteSharedConnection { conn: Arc::new(Mutex::new(State::Idle(conn))), acquire_timeout, metric_callback: None, }; #[cfg(feature = "sqlite-use-returning-for-3_35")] { let version = get_version(&conn)?; super::sqlite::ensure_returning_version(&version)?; } // SQLx also enables this by default conn.execute_unprepared("PRAGMA foreign_keys = ON")?; let conn: DatabaseConnection = conn.into(); if let Some(cb) = after_conn { cb(conn.clone())?; } Ok(conn) } } // impl RusqliteConnector { // /// Convert a Rusqlite connection to a [DatabaseConnection] // pub fn from_rusqlite_connection(conn: RusqliteConnection) -> DatabaseConnection { // let conn: RusqliteSharedConnection = conn.into(); // conn.into() // } // } impl RusqliteSharedConnection { pub fn acquire(&self) -> Result, DbErr> { let deadline = Instant::now() + self.acquire_timeout; loop { match self.conn.try_lock() { Ok(state) => match *state { State::Idle(_) => return Ok(state), State::Loaned => (), // borrowed for streaming or transaction State::Disconnected => { return Err(DbErr::ConnectionAcquire(ConnAcquireErr::ConnectionClosed)); } }, Err(TryLockError::WouldBlock) => (), Err(TryLockError::Poisoned(_)) => { return Err(DbErr::ConnectionAcquire(ConnAcquireErr::ConnectionClosed)); } } if Instant::now() >= deadline { return Err(DbErr::ConnectionAcquire(ConnAcquireErr::Timeout)); } std::thread::yield_now(); } } fn loan(&self) -> Result { let conn = { let mut conn = self.acquire()?; conn.loan() }; Ok(RusqliteInnerConnection { conn: State::Idle(conn), loan: self.conn.clone(), transaction_depth: 0, }) } /// Execute a [Statement] on a SQLite backend #[instrument(level = "trace")] pub fn execute(&self, stmt: Statement) -> Result { debug!("{}", stmt); let values = sql_values(&stmt); let conn = self.acquire()?; let conn = conn.conn(); crate::metric::metric!(self.metric_callback, &stmt, { match conn.execute(&stmt.sql, &*values.as_params()) { Ok(rows_affected) => Ok(RusqliteExecResult { rows_affected: rows_affected as u64, last_insert_rowid: conn.last_insert_rowid(), } .into()), Err(err) => Err(exec_err(err)), } }) } /// Execute an unprepared SQL statement on a SQLite backend #[instrument(level = "trace")] pub fn execute_unprepared(&self, sql: &str) -> Result { debug!("{}", sql); let conn = self.acquire()?; let conn = conn.conn(); match conn.execute_batch(sql) { Ok(()) => Ok(RusqliteExecResult { rows_affected: conn.changes(), last_insert_rowid: conn.last_insert_rowid(), } .into()), Err(err) => Err(exec_err(err)), } } /// Get one result from a SQL query. Returns [Option::None] if no match was found #[instrument(level = "trace")] pub fn query_one(&self, stmt: Statement) -> Result, DbErr> { debug!("{}", stmt); let values = sql_values(&stmt); let conn = self.acquire()?; let conn = conn.conn(); let mut sql = conn.prepare_cached(&stmt.sql).map_err(query_err)?; let columns: Vec> = column_names(&sql); crate::metric::metric!(self.metric_callback, &stmt, { match sql.query(&*values.as_params()) { Ok(mut rows) => { let mut out = None; if let Some(row) = rows.next().map_err(query_err)? { out = Some(OwnedRow::from_row(columns.clone(), row).into()); } Ok(out) } Err(err) => Err(query_err(err)), } }) } /// Get the results of a query returning them as a Vec<[QueryResult]> #[instrument(level = "trace")] pub fn query_all(&self, stmt: Statement) -> Result, DbErr> { debug!("{}", stmt); let values = sql_values(&stmt); let conn = self.acquire()?; let conn = conn.conn(); let mut sql = conn.prepare_cached(&stmt.sql).map_err(query_err)?; let columns: Vec> = column_names(&sql); crate::metric::metric!(self.metric_callback, &stmt, { match sql.query(&*values.as_params()) { Ok(mut rows) => { let mut out = Vec::new(); while let Some(row) = rows.next().map_err(query_err)? { out.push(OwnedRow::from_row(columns.clone(), row).into()); } Ok(out) } Err(err) => Err(query_err(err)), } }) } /// Stream the results of executing a SQL query #[instrument(level = "trace")] pub fn stream(&self, stmt: Statement) -> Result { debug!("{}", stmt); Ok(QueryStream::build( stmt, InnerConnection::Rusqlite(self.loan()?), self.metric_callback.clone(), )) } /// Bundle a set of SQL statements that execute together. #[instrument(level = "trace")] pub fn begin( &self, isolation_level: Option, access_mode: Option, sqlite_transaction_mode: Option, ) -> Result { let conn = self.loan()?; DatabaseTransaction::begin( Arc::new(Mutex::new(InnerConnection::Rusqlite(conn))), crate::DbBackend::Sqlite, self.metric_callback.clone(), isolation_level, access_mode, sqlite_transaction_mode, ) } /// Create a SQLite transaction #[instrument(level = "trace", skip(callback))] pub fn transaction( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'b> FnOnce(&'b DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { self.begin(isolation_level, access_mode, None) .map_err(|e| TransactionError::Connection(e))? .run(callback) } pub(crate) fn set_metric_callback(&mut self, callback: F) where F: Fn(&crate::metric::Info<'_>) + 'static, { self.metric_callback = Some(Arc::new(callback)); } /// Checks if a connection to the database is still valid. pub fn ping(&self) -> Result<(), DbErr> { let conn = self.acquire()?; let conn = conn.conn(); let mut stmt = conn.prepare("SELECT 1").map_err(conn_err)?; match stmt.query([]) { Ok(_) => Ok(()), Err(err) => Err(conn_err(err)), } } /// Explicitly close the SQLite connection. /// See [`Self::close_by_ref`] for usage with references. pub fn close(self) -> Result<(), DbErr> { self.close_by_ref() } /// Explicitly close the SQLite connection pub fn close_by_ref(&self) -> Result<(), DbErr> { let mut conn = self.acquire()?; *conn = State::Disconnected; Ok(()) } } impl RusqliteInnerConnection { #[instrument(level = "trace", skip(metric_callback))] pub fn execute( &self, stmt: Statement, metric_callback: &Option, ) -> Result { debug!("{}", stmt); let values = sql_values(&stmt); let conn = self.conn.conn(); crate::metric::metric!(metric_callback, &stmt, { match conn.execute(&stmt.sql, &*values.as_params()) { Ok(rows_affected) => Ok(RusqliteExecResult { rows_affected: rows_affected as u64, last_insert_rowid: conn.last_insert_rowid(), } .into()), Err(err) => Err(exec_err(err)), } }) } #[instrument(level = "trace")] pub(crate) fn execute_unprepared(&self, sql: &str) -> Result { debug!("{}", sql); let conn = self.conn.conn(); match conn.execute_batch(sql) { Ok(()) => Ok(RusqliteExecResult { rows_affected: conn.changes(), last_insert_rowid: conn.last_insert_rowid(), } .into()), Err(err) => Err(exec_err(err)), } } #[instrument(level = "trace", skip(metric_callback))] pub fn query_one( &self, stmt: Statement, metric_callback: &Option, ) -> Result, DbErr> { debug!("{}", stmt); let values = sql_values(&stmt); let conn = self.conn.conn(); let mut sql = conn.prepare_cached(&stmt.sql).map_err(query_err)?; let columns: Vec> = column_names(&sql); crate::metric::metric!(metric_callback, &stmt, { match sql.query(&*values.as_params()) { Ok(mut rows) => { let mut out = None; if let Some(row) = rows.next().map_err(query_err)? { out = Some(OwnedRow::from_row(columns.clone(), row).into()); } Ok(out) } Err(err) => Err(query_err(err)), } }) } #[instrument(level = "trace", skip(metric_callback))] pub fn query_all( &self, stmt: Statement, metric_callback: &Option, ) -> Result, DbErr> { debug!("{}", stmt); let values = sql_values(&stmt); let conn = self.conn.conn(); let mut sql = conn.prepare_cached(&stmt.sql).map_err(query_err)?; let columns: Vec> = column_names(&sql); crate::metric::metric!(metric_callback, &stmt, { match sql.query(&*values.as_params()) { Ok(mut rows) => { let mut out = Vec::new(); while let Some(row) = rows.next().map_err(query_err)? { out.push(OwnedRow::from_row(columns.clone(), row).into()); } Ok(out) } Err(err) => Err(query_err(err)), } }) } #[instrument(level = "trace")] pub(crate) fn stream(&self, stmt: &Statement) -> Result, DbErr> { debug!("{}", stmt); let values = sql_values(stmt); let conn = self.conn.conn(); let mut sql = conn.prepare_cached(&stmt.sql).map_err(query_err)?; let columns: Vec> = column_names(&sql); let rows = match sql.query(&*values.as_params()) { Ok(mut rows) => { let mut out = Vec::new(); while let Some(row) = rows.next().map_err(query_err)? { out.push(OwnedRow::from_row(columns.clone(), row).into()); } out } Err(err) => return Err(query_err(err)), }; Ok(rows) } #[instrument(level = "trace")] pub(crate) fn begin( &mut self, sqlite_transaction_mode: Option, ) -> Result<(), DbErr> { if self.transaction_depth == 0 { match sqlite_transaction_mode { Some(mode) => { self.execute_unprepared(&format!("BEGIN {}", mode.sqlite_keyword()))? } None => self.execute_unprepared("BEGIN")?, }; } else { self.execute_unprepared(&format!("SAVEPOINT sp{}", self.transaction_depth))?; } self.transaction_depth += 1; Ok(()) } #[instrument(level = "trace")] pub(crate) fn commit(&mut self) -> Result<(), DbErr> { if self.transaction_depth == 1 { self.execute_unprepared("COMMIT")?; } else { self.execute_unprepared(&format!( "RELEASE SAVEPOINT sp{}", self.transaction_depth - 1 ))?; } self.transaction_depth -= 1; Ok(()) } #[instrument(level = "trace")] pub(crate) fn rollback(&mut self) -> Result<(), DbErr> { if self.transaction_depth == 1 { self.execute_unprepared("ROLLBACK")?; } else { self.execute_unprepared(&format!("ROLLBACK TO sp{}", self.transaction_depth - 1))?; } self.transaction_depth -= 1; Ok(()) } #[instrument(level = "trace")] pub(crate) fn start_rollback(&mut self) -> Result<(), DbErr> { if self.transaction_depth > 0 { self.rollback()?; } Ok(()) } } impl Drop for RusqliteInnerConnection { fn drop(&mut self) { let mut loan = self.loan.lock().unwrap(); loan.return_(self.conn.loan()); } } impl State { fn conn(&self) -> &RusqliteConnection { match self { State::Idle(conn) => conn, _ => panic!("No connection"), } } fn loan(&mut self) -> RusqliteConnection { let mut conn = State::Loaned; std::mem::swap(&mut conn, self); match conn { State::Idle(conn) => conn, _ => panic!("No connection"), } } fn return_(&mut self, conn: RusqliteConnection) { *self = State::Idle(conn); } } impl From for QueryResult { fn from(row: OwnedRow) -> QueryResult { QueryResult { row: QueryResultRow::Rusqlite(row), } } } impl From for ExecResult { fn from(result: RusqliteExecResult) -> ExecResult { ExecResult { result: ExecResultHolder::Rusqlite(result), } } } pub(crate) fn sql_values(stmt: &Statement) -> RusqliteValues { let values = match &stmt.values { Some(values) => values.iter().cloned().map(RusqliteValue).collect(), None => Vec::new(), }; RusqliteValues(values) } fn column_names(sql: &CachedStatement) -> Vec> { sql.column_names() .into_iter() .map(|r| Arc::from(r)) .collect() } #[cfg(feature = "sqlite-use-returning-for-3_35")] fn get_version(conn: &RusqliteSharedConnection) -> Result { let stmt = Statement { sql: "SELECT sqlite_version()".to_string(), values: None, db_backend: crate::DbBackend::Sqlite, }; conn.query_one(stmt)? .ok_or_else(|| { DbErr::Conn(RuntimeErr::Internal( "Error reading SQLite version".to_string(), )) })? .try_get_by(0) } fn conn_err(err: RusqliteError) -> DbErr { DbErr::Conn(RuntimeErr::Rusqlite(err.into())) } fn exec_err(err: RusqliteError) -> DbErr { DbErr::Exec(RuntimeErr::Rusqlite(err.into())) } fn query_err(err: RusqliteError) -> DbErr { DbErr::Query(RuntimeErr::Rusqlite(err.into())) } ================================================ FILE: sea-orm-sync/src/driver/sqlite.rs ================================================ use crate::error::{DbErr, RuntimeErr}; #[cfg(feature = "sqlite-use-returning-for-3_35")] pub fn ensure_returning_version(version: &str) -> Result<(), DbErr> { let mut parts = version.trim().split('.').map(|part| { part.parse::().map_err(|_| { DbErr::Conn(RuntimeErr::Internal( "Error parsing SQLite version".to_string(), )) }) }); let mut extract_next = || { parts.next().transpose().and_then(|part| { part.ok_or_else(|| { DbErr::Conn(RuntimeErr::Internal("SQLite version too short".to_string())) }) }) }; let major = extract_next()?; let minor = extract_next()?; if major > 3 || (major == 3 && minor >= 35) { Ok(()) } else { Err(DbErr::BackendNotSupported { db: "SQLite", ctx: "SQLite version does not support returning", }) } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "sqlite-use-returning-for-3_35")] #[test] fn test_ensure_returning_version() { assert!(ensure_returning_version("").is_err()); assert!(ensure_returning_version(".").is_err()); assert!(ensure_returning_version(".a").is_err()); assert!(ensure_returning_version(".4.9").is_err()); assert!(ensure_returning_version("a").is_err()); assert!(ensure_returning_version("1.").is_err()); assert!(ensure_returning_version("1.a").is_err()); assert!(ensure_returning_version("1.1").is_err()); assert!(ensure_returning_version("1.0.").is_err()); assert!(ensure_returning_version("1.0.0").is_err()); assert!(ensure_returning_version("2.0.0").is_err()); assert!(ensure_returning_version("3.34.0").is_err()); assert!(ensure_returning_version("3.34.999").is_err()); // valid version assert!(ensure_returning_version("3.35.0").is_ok()); assert!(ensure_returning_version("3.35.1").is_ok()); assert!(ensure_returning_version("3.36.0").is_ok()); assert!(ensure_returning_version("4.0.0").is_ok()); assert!(ensure_returning_version("99.0.0").is_ok()); } } ================================================ FILE: sea-orm-sync/src/driver/sqlx_common.rs ================================================ use crate::{ConnAcquireErr, ConnectOptions, DbErr, RuntimeErr}; /// Converts an [sqlx::error] execution error to a [DbErr] pub fn sqlx_error_to_exec_err(err: sqlx::Error) -> DbErr { DbErr::Exec(RuntimeErr::SqlxError(err.into())) } /// Converts an [sqlx::error] query error to a [DbErr] pub fn sqlx_error_to_query_err(err: sqlx::Error) -> DbErr { DbErr::Query(RuntimeErr::SqlxError(err.into())) } /// Converts an [sqlx::error] connection error to a [DbErr] pub fn sqlx_error_to_conn_err(err: sqlx::Error) -> DbErr { DbErr::Conn(RuntimeErr::SqlxError(err.into())) } /// Converts an [sqlx::error] error to a [DbErr] pub fn sqlx_map_err_ignore_not_found( err: Result, sqlx::Error>, ) -> Result, DbErr> { if let Err(sqlx::Error::RowNotFound) = err { Ok(None) } else { err.map_err(sqlx_error_to_query_err) } } /// Converts an [sqlx::error] error to a [DbErr] pub fn sqlx_conn_acquire_err(sqlx_err: sqlx::Error) -> DbErr { match sqlx_err { sqlx::Error::PoolTimedOut => DbErr::ConnectionAcquire(ConnAcquireErr::Timeout), sqlx::Error::PoolClosed => DbErr::ConnectionAcquire(ConnAcquireErr::ConnectionClosed), _ => DbErr::Conn(RuntimeErr::SqlxError(sqlx_err.into())), } } impl ConnectOptions { /// Convert [ConnectOptions] into [sqlx::pool::PoolOptions] pub fn sqlx_pool_options(self) -> sqlx::pool::PoolOptions where DB: sqlx::Database, { let mut opt = sqlx::pool::PoolOptions::new(); if let Some(max_connections) = self.max_connections { opt = opt.max_connections(max_connections); } if let Some(min_connections) = self.min_connections { opt = opt.min_connections(min_connections); } if let Some(connect_timeout) = self.connect_timeout { opt = opt.acquire_timeout(connect_timeout); } if let Some(idle_timeout) = self.idle_timeout { opt = opt.idle_timeout(idle_timeout); } if let Some(acquire_timeout) = self.acquire_timeout { opt = opt.acquire_timeout(acquire_timeout); } if let Some(max_lifetime) = self.max_lifetime { opt = opt.max_lifetime(max_lifetime); } opt = opt.test_before_acquire(self.test_before_acquire); opt } } ================================================ FILE: sea-orm-sync/src/driver/sqlx_mysql.rs ================================================ use log::LevelFilter; use sea_query::Values; use std::sync::Arc; use std::sync::Mutex; use sqlx::{ Connection, Executor, MySql, MySqlPool, mysql::{MySqlConnectOptions, MySqlQueryResult, MySqlRow}, pool::PoolConnection, }; use sea_query_sqlx::SqlxValues; use tracing::instrument; use crate::{ AccessMode, ConnectOptions, DatabaseConnection, DatabaseConnectionType, DatabaseTransaction, DbBackend, IsolationLevel, QueryStream, Statement, TransactionError, debug_print, error::*, executor::*, }; use super::sqlx_common::*; /// Defines the [sqlx::mysql] connector #[derive(Debug)] pub struct SqlxMySqlConnector; /// Defines a sqlx MySQL pool #[derive(Clone)] pub struct SqlxMySqlPoolConnection { pub(crate) pool: MySqlPool, metric_callback: Option, } impl std::fmt::Debug for SqlxMySqlPoolConnection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SqlxMySqlPoolConnection {{ pool: {:?} }}", self.pool) } } impl From for SqlxMySqlPoolConnection { fn from(pool: MySqlPool) -> Self { SqlxMySqlPoolConnection { pool, metric_callback: None, } } } impl From for DatabaseConnection { fn from(pool: MySqlPool) -> Self { DatabaseConnectionType::SqlxMySqlPoolConnection(pool.into()).into() } } impl SqlxMySqlConnector { /// Check if the URI provided corresponds to `mysql://` for a MySQL database pub fn accepts(string: &str) -> bool { string.starts_with("mysql://") && string.parse::().is_ok() } /// Add configuration options for the MySQL database #[instrument(level = "trace")] pub fn connect(options: ConnectOptions) -> Result { let mut sqlx_opts = options .url .parse::() .map_err(sqlx_error_to_conn_err)?; use sqlx::ConnectOptions; if !options.sqlx_logging { sqlx_opts = sqlx_opts.disable_statement_logging(); } else { sqlx_opts = sqlx_opts.log_statements(options.sqlx_logging_level); if options.sqlx_slow_statements_logging_level != LevelFilter::Off { sqlx_opts = sqlx_opts.log_slow_statements( options.sqlx_slow_statements_logging_level, options.sqlx_slow_statements_logging_threshold, ); } } if let Some(f) = &options.mysql_opts_fn { sqlx_opts = f(sqlx_opts); } let after_connect = options.after_connect.clone(); let pool = if options.connect_lazy { options.sqlx_pool_options().connect_lazy_with(sqlx_opts) } else { options .sqlx_pool_options() .connect_with(sqlx_opts) .map_err(sqlx_error_to_conn_err)? }; let conn: DatabaseConnection = DatabaseConnectionType::SqlxMySqlPoolConnection(SqlxMySqlPoolConnection { pool, metric_callback: None, }) .into(); if let Some(cb) = after_connect { cb(conn.clone())?; } Ok(conn) } } impl SqlxMySqlConnector { /// Instantiate a sqlx pool connection to a [DatabaseConnection] pub fn from_sqlx_mysql_pool(pool: MySqlPool) -> DatabaseConnection { DatabaseConnectionType::SqlxMySqlPoolConnection(SqlxMySqlPoolConnection { pool, metric_callback: None, }) .into() } } impl SqlxMySqlPoolConnection { /// Execute a [Statement] on a MySQL backend #[instrument(level = "trace")] pub fn execute(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.execute(&mut *conn) { Ok(res) => Ok(res.into()), Err(err) => Err(sqlx_error_to_exec_err(err)), } }) } /// Execute an unprepared SQL statement on a MySQL backend #[instrument(level = "trace")] pub fn execute_unprepared(&self, sql: &str) -> Result { debug_print!("{}", sql); let conn = &mut self.pool.acquire().map_err(sqlx_conn_acquire_err)?; match conn.execute(sql) { Ok(res) => Ok(res.into()), Err(err) => Err(sqlx_error_to_exec_err(err)), } } /// Get one result from a SQL query. Returns [Option::None] if no match was found #[instrument(level = "trace")] pub fn query_one(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.fetch_one(&mut *conn) { Ok(row) => Ok(Some(row.into())), Err(err) => match err { sqlx::Error::RowNotFound => Ok(None), _ => Err(sqlx_error_to_query_err(err)), }, } }) } /// Get the results of a query returning them as a Vec<[QueryResult]> #[instrument(level = "trace")] pub fn query_all(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.fetch_all(&mut *conn) { Ok(rows) => Ok(rows.into_iter().map(|r| r.into()).collect()), Err(err) => Err(sqlx_error_to_query_err(err)), } }) } /// Stream the results of executing a SQL query #[instrument(level = "trace")] pub fn stream(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; Ok(QueryStream::from(( conn, stmt, self.metric_callback.clone(), ))) } /// Bundle a set of SQL statements that execute together. #[instrument(level = "trace")] pub fn begin( &self, isolation_level: Option, access_mode: Option, ) -> Result { let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; DatabaseTransaction::new_mysql( conn, self.metric_callback.clone(), isolation_level, access_mode, ) } /// Create a MySQL transaction #[instrument(level = "trace", skip(callback))] pub fn transaction( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'b> FnOnce(&'b DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; let transaction = DatabaseTransaction::new_mysql( conn, self.metric_callback.clone(), isolation_level, access_mode, ) .map_err(|e| TransactionError::Connection(e))?; transaction.run(callback) } pub(crate) fn set_metric_callback(&mut self, callback: F) where F: Fn(&crate::metric::Info<'_>) + 'static, { self.metric_callback = Some(Arc::new(callback)); } /// Checks if a connection to the database is still valid. pub fn ping(&self) -> Result<(), DbErr> { let conn = &mut self.pool.acquire().map_err(sqlx_conn_acquire_err)?; match conn.ping() { Ok(_) => Ok(()), Err(err) => Err(sqlx_error_to_conn_err(err)), } } /// Explicitly close the MySQL connection. /// See [`Self::close_by_ref`] for usage with references. pub fn close(self) -> Result<(), DbErr> { self.close_by_ref() } /// Explicitly close the MySQL connection pub fn close_by_ref(&self) -> Result<(), DbErr> { self.pool.close(); Ok(()) } } impl From for QueryResult { fn from(row: MySqlRow) -> QueryResult { QueryResult { row: QueryResultRow::SqlxMySql(row), } } } impl From for ExecResult { fn from(result: MySqlQueryResult) -> ExecResult { ExecResult { result: ExecResultHolder::SqlxMySql(result), } } } pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, SqlxValues> { let values = stmt .values .as_ref() .map_or(Values(Vec::new()), |values| values.clone()); sqlx::query_with(&stmt.sql, SqlxValues(values)) } pub(crate) fn set_transaction_config( conn: &mut PoolConnection, isolation_level: Option, access_mode: Option, ) -> Result<(), DbErr> { let mut settings = Vec::new(); if let Some(isolation_level) = isolation_level { settings.push(format!("ISOLATION LEVEL {isolation_level}")); } if let Some(access_mode) = access_mode { settings.push(access_mode.to_string()); } if !settings.is_empty() { let stmt = Statement { sql: format!("SET TRANSACTION {}", settings.join(", ")), values: None, db_backend: DbBackend::MySql, }; let query = sqlx_query(&stmt); conn.execute(query).map_err(sqlx_error_to_exec_err)?; } Ok(()) } impl From<( PoolConnection, Statement, Option, )> for crate::QueryStream { fn from( (conn, stmt, metric_callback): ( PoolConnection, Statement, Option, ), ) -> Self { crate::QueryStream::build(stmt, crate::InnerConnection::MySql(conn), metric_callback) } } impl crate::DatabaseTransaction { pub(crate) fn new_mysql( inner: PoolConnection, metric_callback: Option, isolation_level: Option, access_mode: Option, ) -> Result { Self::begin( Arc::new(Mutex::new(crate::InnerConnection::MySql(inner))), crate::DbBackend::MySql, metric_callback, isolation_level, access_mode, None, ) } } #[cfg(feature = "proxy")] pub(crate) fn from_sqlx_mysql_row_to_proxy_row(row: &sqlx::mysql::MySqlRow) -> crate::ProxyRow { // https://docs.rs/sqlx-mysql/0.7.2/src/sqlx_mysql/protocol/text/column.rs.html // https://docs.rs/sqlx-mysql/0.7.2/sqlx_mysql/types/index.html use sea_query::Value; use sqlx::{Column, Row, TypeInfo}; crate::ProxyRow { values: row .columns() .iter() .map(|c| { ( c.name().to_string(), match c.type_info().name() { "TINYINT(1)" | "BOOLEAN" => { Value::Bool(row.try_get(c.ordinal()).expect("Failed to get boolean")) } "TINYINT UNSIGNED" => Value::TinyUnsigned( row.try_get(c.ordinal()) .expect("Failed to get unsigned tiny integer"), ), "SMALLINT UNSIGNED" => Value::SmallUnsigned( row.try_get(c.ordinal()) .expect("Failed to get unsigned small integer"), ), "INT UNSIGNED" => Value::Unsigned( row.try_get(c.ordinal()) .expect("Failed to get unsigned integer"), ), "MEDIUMINT UNSIGNED" | "BIGINT UNSIGNED" => Value::BigUnsigned( row.try_get(c.ordinal()) .expect("Failed to get unsigned big integer"), ), "TINYINT" => Value::TinyInt( row.try_get(c.ordinal()) .expect("Failed to get tiny integer"), ), "SMALLINT" => Value::SmallInt( row.try_get(c.ordinal()) .expect("Failed to get small integer"), ), "INT" => { Value::Int(row.try_get(c.ordinal()).expect("Failed to get integer")) } "MEDIUMINT" | "BIGINT" => Value::BigInt( row.try_get(c.ordinal()).expect("Failed to get big integer"), ), "FLOAT" => { Value::Float(row.try_get(c.ordinal()).expect("Failed to get float")) } "DOUBLE" => { Value::Double(row.try_get(c.ordinal()).expect("Failed to get double")) } "BIT" | "BINARY" | "VARBINARY" | "TINYBLOB" | "BLOB" | "MEDIUMBLOB" | "LONGBLOB" => Value::Bytes( row.try_get::>, _>(c.ordinal()) .expect("Failed to get bytes") .map(Box::new), ), "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" => { Value::String( row.try_get::, _>(c.ordinal()) .expect("Failed to get string") .map(Box::new), ) } #[cfg(feature = "with-chrono")] "TIMESTAMP" => Value::ChronoDateTimeUtc( row.try_get::>, _>(c.ordinal()) .expect("Failed to get timestamp") .map(Box::new), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIMESTAMP" => Value::TimeDateTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get timestamp") .map(Box::new), ), #[cfg(feature = "with-chrono")] "DATE" => Value::ChronoDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get date") .map(Box::new), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "DATE" => Value::TimeDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get date") .map(Box::new), ), #[cfg(feature = "with-chrono")] "TIME" => Value::ChronoTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get time") .map(Box::new), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIME" => Value::TimeTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get time") .map(Box::new), ), #[cfg(feature = "with-chrono")] "DATETIME" => Value::ChronoDateTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get datetime") .map(Box::new), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "DATETIME" => Value::TimeDateTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get datetime") .map(Box::new), ), #[cfg(feature = "with-chrono")] "YEAR" => Value::ChronoDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get year") .map(Box::new), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "YEAR" => Value::TimeDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get year") .map(Box::new), ), "ENUM" | "SET" | "GEOMETRY" => Value::String( row.try_get::, _>(c.ordinal()) .expect("Failed to get serialized string") .map(Box::new), ), #[cfg(feature = "with-bigdecimal")] "DECIMAL" => Value::BigDecimal( row.try_get::, _>(c.ordinal()) .expect("Failed to get decimal") .map(Box::new), ), #[cfg(all( feature = "with-rust_decimal", not(feature = "with-bigdecimal") ))] "DECIMAL" => Value::Decimal( row.try_get::, _>(c.ordinal()) .expect("Failed to get decimal") .map(Box::new), ), #[cfg(feature = "with-json")] "JSON" => Value::Json( row.try_get::, _>(c.ordinal()) .expect("Failed to get json") .map(Box::new), ), _ => unreachable!("Unknown column type: {}", c.type_info().name()), }, ) }) .collect(), } } ================================================ FILE: sea-orm-sync/src/driver/sqlx_postgres.rs ================================================ use log::LevelFilter; use sea_query::Values; use std::sync::Mutex; use std::{fmt::Write, sync::Arc}; use sqlx::{ Connection, Executor, PgPool, Postgres, pool::PoolConnection, postgres::{PgConnectOptions, PgQueryResult, PgRow}, }; use sea_query_sqlx::SqlxValues; use tracing::instrument; use crate::{ AccessMode, ConnectOptions, DatabaseConnection, DatabaseConnectionType, DatabaseTransaction, IsolationLevel, QueryStream, Statement, TransactionError, debug_print, error::*, executor::*, }; use super::sqlx_common::*; /// Defines the [sqlx::postgres] connector #[derive(Debug)] pub struct SqlxPostgresConnector; /// Defines a sqlx PostgreSQL pool #[derive(Clone)] pub struct SqlxPostgresPoolConnection { pub(crate) pool: PgPool, metric_callback: Option, } impl std::fmt::Debug for SqlxPostgresPoolConnection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SqlxPostgresPoolConnection {{ pool: {:?} }}", self.pool) } } impl From for SqlxPostgresPoolConnection { fn from(pool: PgPool) -> Self { SqlxPostgresPoolConnection { pool, metric_callback: None, } } } impl From for DatabaseConnection { fn from(pool: PgPool) -> Self { DatabaseConnectionType::SqlxPostgresPoolConnection(pool.into()).into() } } impl SqlxPostgresConnector { /// Check if the URI provided corresponds to `postgres://` for a PostgreSQL database pub fn accepts(string: &str) -> bool { string.starts_with("postgres://") && string.parse::().is_ok() } /// Add configuration options for the PostgreSQL database #[instrument(level = "trace")] pub fn connect(options: ConnectOptions) -> Result { let mut sqlx_opts = options .url .parse::() .map_err(sqlx_error_to_conn_err)?; use sqlx::ConnectOptions; if !options.sqlx_logging { sqlx_opts = sqlx_opts.disable_statement_logging(); } else { sqlx_opts = sqlx_opts.log_statements(options.sqlx_logging_level); if options.sqlx_slow_statements_logging_level != LevelFilter::Off { sqlx_opts = sqlx_opts.log_slow_statements( options.sqlx_slow_statements_logging_level, options.sqlx_slow_statements_logging_threshold, ); } } if let Some(application_name) = &options.application_name { sqlx_opts = sqlx_opts.application_name(application_name); } if let Some(timeout) = options.statement_timeout { sqlx_opts = sqlx_opts.options([("statement_timeout", timeout.as_millis().to_string())]); } if let Some(f) = &options.pg_opts_fn { sqlx_opts = f(sqlx_opts); } let set_search_path_sql = options.schema_search_path.as_ref().map(|schema| { let mut string = "SET search_path = ".to_owned(); if schema.starts_with('"') { write!(&mut string, "{schema}").expect("Infallible"); } else { for (i, schema) in schema.split(',').enumerate() { if i > 0 { write!(&mut string, ",").expect("Infallible"); } if schema.starts_with('"') { write!(&mut string, "{schema}").expect("Infallible"); } else { write!(&mut string, "\"{schema}\"").expect("Infallible"); } } } string }); let lazy = options.connect_lazy; let after_connect = options.after_connect.clone(); let mut pool_options = options.sqlx_pool_options(); if let Some(sql) = set_search_path_sql { pool_options = pool_options.after_connect(move |conn, _| { let sql = sql.clone(); ({ sqlx::Executor::execute(conn, sql.as_str()).map(|_| ()) }) }); } let pool = if lazy { pool_options.connect_lazy_with(sqlx_opts) } else { pool_options .connect_with(sqlx_opts) .map_err(sqlx_error_to_conn_err)? }; let conn: DatabaseConnection = DatabaseConnectionType::SqlxPostgresPoolConnection(SqlxPostgresPoolConnection { pool, metric_callback: None, }) .into(); if let Some(cb) = after_connect { cb(conn.clone())?; } Ok(conn) } } impl SqlxPostgresConnector { /// Instantiate a sqlx pool connection to a [DatabaseConnection] pub fn from_sqlx_postgres_pool(pool: PgPool) -> DatabaseConnection { DatabaseConnectionType::SqlxPostgresPoolConnection(SqlxPostgresPoolConnection { pool, metric_callback: None, }) .into() } } impl SqlxPostgresPoolConnection { /// Execute a [Statement] on a PostgreSQL backend #[instrument(level = "trace")] pub fn execute(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.execute(&mut *conn) { Ok(res) => Ok(res.into()), Err(err) => Err(sqlx_error_to_exec_err(err)), } }) } /// Execute an unprepared SQL statement on a PostgreSQL backend #[instrument(level = "trace")] pub fn execute_unprepared(&self, sql: &str) -> Result { debug_print!("{}", sql); let conn = &mut self.pool.acquire().map_err(sqlx_conn_acquire_err)?; match conn.execute(sql) { Ok(res) => Ok(res.into()), Err(err) => Err(sqlx_error_to_exec_err(err)), } } /// Get one result from a SQL query. Returns [Option::None] if no match was found #[instrument(level = "trace")] pub fn query_one(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.fetch_one(&mut *conn) { Ok(row) => Ok(Some(row.into())), Err(err) => match err { sqlx::Error::RowNotFound => Ok(None), _ => Err(sqlx_error_to_query_err(err)), }, } }) } /// Get the results of a query returning them as a Vec<[QueryResult]> #[instrument(level = "trace")] pub fn query_all(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.fetch_all(&mut *conn) { Ok(rows) => Ok(rows.into_iter().map(|r| r.into()).collect()), Err(err) => Err(sqlx_error_to_query_err(err)), } }) } /// Stream the results of executing a SQL query #[instrument(level = "trace")] pub fn stream(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; Ok(QueryStream::from(( conn, stmt, self.metric_callback.clone(), ))) } /// Bundle a set of SQL statements that execute together. #[instrument(level = "trace")] pub fn begin( &self, isolation_level: Option, access_mode: Option, ) -> Result { let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; DatabaseTransaction::new_postgres( conn, self.metric_callback.clone(), isolation_level, access_mode, ) } /// Create a PostgreSQL transaction #[instrument(level = "trace", skip(callback))] pub fn transaction( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'b> FnOnce(&'b DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; let transaction = DatabaseTransaction::new_postgres( conn, self.metric_callback.clone(), isolation_level, access_mode, ) .map_err(|e| TransactionError::Connection(e))?; transaction.run(callback) } pub(crate) fn set_metric_callback(&mut self, callback: F) where F: Fn(&crate::metric::Info<'_>) + 'static, { self.metric_callback = Some(Arc::new(callback)); } /// Checks if a connection to the database is still valid. pub fn ping(&self) -> Result<(), DbErr> { let conn = &mut self.pool.acquire().map_err(sqlx_conn_acquire_err)?; match conn.ping() { Ok(_) => Ok(()), Err(err) => Err(sqlx_error_to_conn_err(err)), } } /// Explicitly close the Postgres connection. /// See [`Self::close_by_ref`] for usage with references. pub fn close(self) -> Result<(), DbErr> { self.close_by_ref() } /// Explicitly close the Postgres connection pub fn close_by_ref(&self) -> Result<(), DbErr> { self.pool.close(); Ok(()) } } impl From for QueryResult { fn from(row: PgRow) -> QueryResult { QueryResult { row: QueryResultRow::SqlxPostgres(row), } } } impl From for ExecResult { fn from(result: PgQueryResult) -> ExecResult { ExecResult { result: ExecResultHolder::SqlxPostgres(result), } } } pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Postgres, SqlxValues> { let values = stmt .values .as_ref() .map_or(Values(Vec::new()), |values| values.clone()); sqlx::query_with(&stmt.sql, SqlxValues(values)) } pub(crate) fn set_transaction_config( conn: &mut PoolConnection, isolation_level: Option, access_mode: Option, ) -> Result<(), DbErr> { let mut settings = Vec::new(); if let Some(isolation_level) = isolation_level { settings.push(format!("ISOLATION LEVEL {isolation_level}")); } if let Some(access_mode) = access_mode { settings.push(access_mode.to_string()); } if !settings.is_empty() { let sql = format!("SET TRANSACTION {}", settings.join(" ")); sqlx::query(&sql) .execute(&mut **conn) .map_err(sqlx_error_to_exec_err)?; } Ok(()) } impl From<( PoolConnection, Statement, Option, )> for crate::QueryStream { fn from( (conn, stmt, metric_callback): ( PoolConnection, Statement, Option, ), ) -> Self { crate::QueryStream::build( stmt, crate::InnerConnection::Postgres(conn), metric_callback, ) } } impl crate::DatabaseTransaction { pub(crate) fn new_postgres( inner: PoolConnection, metric_callback: Option, isolation_level: Option, access_mode: Option, ) -> Result { Self::begin( Arc::new(Mutex::new(crate::InnerConnection::Postgres(inner))), crate::DbBackend::Postgres, metric_callback, isolation_level, access_mode, None, ) } } #[cfg(feature = "proxy")] pub(crate) fn from_sqlx_postgres_row_to_proxy_row(row: &sqlx::postgres::PgRow) -> crate::ProxyRow { // https://docs.rs/sqlx-postgres/0.7.2/src/sqlx_postgres/type_info.rs.html // https://docs.rs/sqlx-postgres/0.7.2/sqlx_postgres/types/index.html use sea_query::Value; use sqlx::{Column, Row, TypeInfo}; crate::ProxyRow { values: row .columns() .iter() .map(|c| { ( c.name().to_string(), match c.type_info().name() { "BOOL" => { Value::Bool(row.try_get(c.ordinal()).expect("Failed to get boolean")) } #[cfg(feature = "postgres-array")] "BOOL[]" => Value::Array( sea_query::ArrayType::Bool, row.try_get::>, _>(c.ordinal()) .expect("Failed to get boolean array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::Bool(Some(val))) .collect(), ) }), ), "\"CHAR\"" => Value::TinyInt( row.try_get(c.ordinal()) .expect("Failed to get small integer"), ), #[cfg(feature = "postgres-array")] "\"CHAR\"[]" => Value::Array( sea_query::ArrayType::TinyInt, row.try_get::>, _>(c.ordinal()) .expect("Failed to get small integer array") .map(|vals: Vec| { Box::new( vals.into_iter() .map(|val| Value::TinyInt(Some(val))) .collect(), ) }), ), "SMALLINT" | "SMALLSERIAL" | "INT2" => Value::SmallInt( row.try_get(c.ordinal()) .expect("Failed to get small integer"), ), #[cfg(feature = "postgres-array")] "SMALLINT[]" | "SMALLSERIAL[]" | "INT2[]" => Value::Array( sea_query::ArrayType::SmallInt, row.try_get::>, _>(c.ordinal()) .expect("Failed to get small integer array") .map(|vals: Vec| { Box::new( vals.into_iter() .map(|val| Value::SmallInt(Some(val))) .collect(), ) }), ), "INT" | "SERIAL" | "INT4" => { Value::Int(row.try_get(c.ordinal()).expect("Failed to get integer")) } #[cfg(feature = "postgres-array")] "INT[]" | "SERIAL[]" | "INT4[]" => Value::Array( sea_query::ArrayType::Int, row.try_get::>, _>(c.ordinal()) .expect("Failed to get integer array") .map(|vals: Vec| { Box::new( vals.into_iter().map(|val| Value::Int(Some(val))).collect(), ) }), ), "BIGINT" | "BIGSERIAL" | "INT8" => Value::BigInt( row.try_get(c.ordinal()).expect("Failed to get big integer"), ), #[cfg(feature = "postgres-array")] "BIGINT[]" | "BIGSERIAL[]" | "INT8[]" => Value::Array( sea_query::ArrayType::BigInt, row.try_get::>, _>(c.ordinal()) .expect("Failed to get big integer array") .map(|vals: Vec| { Box::new( vals.into_iter() .map(|val| Value::BigInt(Some(val))) .collect(), ) }), ), "FLOAT4" | "REAL" => { Value::Float(row.try_get(c.ordinal()).expect("Failed to get float")) } #[cfg(feature = "postgres-array")] "FLOAT4[]" | "REAL[]" => Value::Array( sea_query::ArrayType::Float, row.try_get::>, _>(c.ordinal()) .expect("Failed to get float array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::Float(Some(val))) .collect(), ) }), ), "FLOAT8" | "DOUBLE PRECISION" => { Value::Double(row.try_get(c.ordinal()).expect("Failed to get double")) } #[cfg(feature = "postgres-array")] "FLOAT8[]" | "DOUBLE PRECISION[]" => Value::Array( sea_query::ArrayType::Double, row.try_get::>, _>(c.ordinal()) .expect("Failed to get double array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::Double(Some(val))) .collect(), ) }), ), "VARCHAR" | "CHAR" | "TEXT" | "NAME" => Value::String( row.try_get::, _>(c.ordinal()) .expect("Failed to get string"), ), #[cfg(feature = "postgres-array")] "VARCHAR[]" | "CHAR[]" | "TEXT[]" | "NAME[]" => Value::Array( sea_query::ArrayType::String, row.try_get::>, _>(c.ordinal()) .expect("Failed to get string array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::String(Some(val))) .collect(), ) }), ), "BYTEA" => Value::Bytes( row.try_get::>, _>(c.ordinal()) .expect("Failed to get bytes"), ), #[cfg(feature = "postgres-array")] "BYTEA[]" => Value::Array( sea_query::ArrayType::Bytes, row.try_get::>>, _>(c.ordinal()) .expect("Failed to get bytes array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::Bytes(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-bigdecimal")] "NUMERIC" => Value::BigDecimal( row.try_get::, _>(c.ordinal()) .expect("Failed to get numeric"), ), #[cfg(all( feature = "with-rust_decimal", not(feature = "with-bigdecimal") ))] "NUMERIC" => { Value::Decimal(row.try_get(c.ordinal()).expect("Failed to get numeric")) } #[cfg(all(feature = "with-bigdecimal", feature = "postgres-array"))] "NUMERIC[]" => Value::Array( sea_query::ArrayType::BigDecimal, row.try_get::>, _>(c.ordinal()) .expect("Failed to get numeric array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::BigDecimal(Some(val))) .collect(), ) }), ), #[cfg(all( feature = "with-rust_decimal", not(feature = "with-bigdecimal"), feature = "postgres-array" ))] "NUMERIC[]" => Value::Array( sea_query::ArrayType::Decimal, row.try_get::>, _>(c.ordinal()) .expect("Failed to get numeric array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::Decimal(Some(val))) .collect(), ) }), ), "OID" => { Value::BigInt(row.try_get(c.ordinal()).expect("Failed to get oid")) } #[cfg(feature = "postgres-array")] "OID[]" => Value::Array( sea_query::ArrayType::BigInt, row.try_get::>, _>(c.ordinal()) .expect("Failed to get oid array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::BigInt(Some(val))) .collect(), ) }), ), "JSON" | "JSONB" => Value::Json( row.try_get::, _>(c.ordinal()) .expect("Failed to get json") .map(Box::new), ), #[cfg(any(feature = "json-array", feature = "postgres-array"))] "JSON[]" | "JSONB[]" => Value::Array( sea_query::ArrayType::Json, row.try_get::>, _>(c.ordinal()) .expect("Failed to get json array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::Json(Some(Box::new(val)))) .collect(), ) }), ), #[cfg(feature = "with-ipnetwork")] "INET" | "CIDR" => Value::IpNetwork( row.try_get::, _>(c.ordinal()) .expect("Failed to get ip address"), ), #[cfg(feature = "with-ipnetwork")] "INET[]" | "CIDR[]" => Value::Array( sea_query::ArrayType::IpNetwork, row.try_get::>, _>(c.ordinal()) .expect("Failed to get ip address array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::IpNetwork(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-mac_address")] "MACADDR" | "MACADDR8" => Value::MacAddress( row.try_get::, _>(c.ordinal()) .expect("Failed to get mac address"), ), #[cfg(all(feature = "with-mac_address", feature = "postgres-array"))] "MACADDR[]" | "MACADDR8[]" => Value::Array( sea_query::ArrayType::MacAddress, row.try_get::>, _>(c.ordinal()) .expect("Failed to get mac address array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::MacAddress(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-chrono")] "TIMESTAMP" => Value::ChronoDateTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get timestamp"), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIMESTAMP" => Value::TimeDateTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get timestamp"), ), #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] "TIMESTAMP[]" => Value::Array( sea_query::ArrayType::ChronoDateTime, row.try_get::>, _>(c.ordinal()) .expect("Failed to get timestamp array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::ChronoDateTime(Some(val))) .collect(), ) }), ), #[cfg(all( feature = "with-time", not(feature = "with-chrono"), feature = "postgres-array" ))] "TIMESTAMP[]" => Value::Array( sea_query::ArrayType::TimeDateTime, row.try_get::>, _>(c.ordinal()) .expect("Failed to get timestamp array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::TimeDateTime(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-chrono")] "DATE" => Value::ChronoDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get date"), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "DATE" => Value::TimeDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get date"), ), #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] "DATE[]" => Value::Array( sea_query::ArrayType::ChronoDate, row.try_get::>, _>(c.ordinal()) .expect("Failed to get date array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::ChronoDate(Some(val))) .collect(), ) }), ), #[cfg(all( feature = "with-time", not(feature = "with-chrono"), feature = "postgres-array" ))] "DATE[]" => Value::Array( sea_query::ArrayType::TimeDate, row.try_get::>, _>(c.ordinal()) .expect("Failed to get date array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::TimeDate(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-chrono")] "TIME" => Value::ChronoTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get time"), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIME" => Value::TimeTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get time"), ), #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] "TIME[]" => Value::Array( sea_query::ArrayType::ChronoTime, row.try_get::>, _>(c.ordinal()) .expect("Failed to get time array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::ChronoTime(Some(val))) .collect(), ) }), ), #[cfg(all( feature = "with-time", not(feature = "with-chrono"), feature = "postgres-array" ))] "TIME[]" => Value::Array( sea_query::ArrayType::TimeTime, row.try_get::>, _>(c.ordinal()) .expect("Failed to get time array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::TimeTime(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-chrono")] "TIMESTAMPTZ" => Value::ChronoDateTimeUtc( row.try_get::>, _>(c.ordinal()) .expect("Failed to get timestamptz"), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIMESTAMPTZ" => Value::TimeDateTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get timestamptz"), ), #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] "TIMESTAMPTZ[]" => Value::Array( sea_query::ArrayType::ChronoDateTimeUtc, row.try_get::>>, _>( c.ordinal(), ) .expect("Failed to get timestamptz array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::ChronoDateTimeUtc(Some(val))) .collect(), ) }), ), #[cfg(all( feature = "with-time", not(feature = "with-chrono"), feature = "postgres-array" ))] "TIMESTAMPTZ[]" => Value::Array( sea_query::ArrayType::TimeDateTime, row.try_get::>, _>(c.ordinal()) .expect("Failed to get timestamptz array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::TimeDateTime(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-chrono")] "TIMETZ" => Value::ChronoTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get timetz"), ), #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIMETZ" => { Value::TimeTime(row.try_get(c.ordinal()).expect("Failed to get timetz")) } #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] "TIMETZ[]" => Value::Array( sea_query::ArrayType::ChronoTime, row.try_get::>, _>(c.ordinal()) .expect("Failed to get timetz array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::ChronoTime(Some(val))) .collect(), ) }), ), #[cfg(all( feature = "with-time", not(feature = "with-chrono"), feature = "postgres-array" ))] "TIMETZ[]" => Value::Array( sea_query::ArrayType::TimeTime, row.try_get::>, _>(c.ordinal()) .expect("Failed to get timetz array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::TimeTime(Some(val))) .collect(), ) }), ), #[cfg(feature = "with-uuid")] "UUID" => Value::Uuid( row.try_get::, _>(c.ordinal()) .expect("Failed to get uuid"), ), #[cfg(all(feature = "with-uuid", feature = "postgres-array"))] "UUID[]" => Value::Array( sea_query::ArrayType::Uuid, row.try_get::>, _>(c.ordinal()) .expect("Failed to get uuid array") .map(|vals| { Box::new( vals.into_iter() .map(|val| Value::Uuid(Some(val))) .collect(), ) }), ), _ => unreachable!("Unknown column type: {}", c.type_info().name()), }, ) }) .collect(), } } ================================================ FILE: sea-orm-sync/src/driver/sqlx_sqlite.rs ================================================ use log::LevelFilter; use sea_query::Values; use std::sync::Arc; use std::sync::Mutex; use sqlx::{ Connection, Executor, Sqlite, SqlitePool, pool::PoolConnection, sqlite::{SqliteConnectOptions, SqliteQueryResult, SqliteRow}, }; use sea_query_sqlx::SqlxValues; use tracing::{instrument, warn}; use crate::{ AccessMode, ConnectOptions, DatabaseConnection, DatabaseConnectionType, DatabaseTransaction, IsolationLevel, QueryStream, SqliteTransactionMode, Statement, TransactionError, debug_print, error::*, executor::*, sqlx_error_to_exec_err, }; use super::sqlx_common::*; /// Defines the [sqlx::sqlite] connector #[derive(Debug)] pub struct SqlxSqliteConnector; /// Defines a sqlx SQLite pool #[derive(Clone)] pub struct SqlxSqlitePoolConnection { pub(crate) pool: SqlitePool, metric_callback: Option, } impl std::fmt::Debug for SqlxSqlitePoolConnection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SqlxSqlitePoolConnection {{ pool: {:?} }}", self.pool) } } impl From for SqlxSqlitePoolConnection { fn from(pool: SqlitePool) -> Self { SqlxSqlitePoolConnection { pool, metric_callback: None, } } } impl From for DatabaseConnection { fn from(pool: SqlitePool) -> Self { DatabaseConnectionType::SqlxSqlitePoolConnection(pool.into()).into() } } impl SqlxSqliteConnector { /// Check if the URI provided corresponds to `sqlite:` for a SQLite database pub fn accepts(string: &str) -> bool { string.starts_with("sqlite:") && string.parse::().is_ok() } /// Add configuration options for the SQLite database #[instrument(level = "trace")] pub fn connect(options: ConnectOptions) -> Result { let mut options = options; let mut sqlx_opts = options .url .parse::() .map_err(sqlx_error_to_conn_err)?; if let Some(sqlcipher_key) = &options.sqlcipher_key { sqlx_opts = sqlx_opts.pragma("key", sqlcipher_key.clone()); } use sqlx::ConnectOptions; if !options.sqlx_logging { sqlx_opts = sqlx_opts.disable_statement_logging(); } else { sqlx_opts = sqlx_opts.log_statements(options.sqlx_logging_level); if options.sqlx_slow_statements_logging_level != LevelFilter::Off { sqlx_opts = sqlx_opts.log_slow_statements( options.sqlx_slow_statements_logging_level, options.sqlx_slow_statements_logging_threshold, ); } } if options.get_max_connections().is_none() { options.max_connections(1); } if let Some(f) = &options.sqlite_opts_fn { sqlx_opts = f(sqlx_opts); } let after_conn = options.after_connect.clone(); let pool = if options.connect_lazy { options.sqlx_pool_options().connect_lazy_with(sqlx_opts) } else { options .sqlx_pool_options() .connect_with(sqlx_opts) .map_err(sqlx_error_to_conn_err)? }; let pool = SqlxSqlitePoolConnection { pool, metric_callback: None, }; #[cfg(feature = "sqlite-use-returning-for-3_35")] { let version = get_version(&pool)?; super::sqlite::ensure_returning_version(&version)?; } let conn: DatabaseConnection = DatabaseConnectionType::SqlxSqlitePoolConnection(pool).into(); if let Some(cb) = after_conn { cb(conn.clone())?; } Ok(conn) } } impl SqlxSqliteConnector { /// Instantiate a sqlx pool connection to a [DatabaseConnection] pub fn from_sqlx_sqlite_pool(pool: SqlitePool) -> DatabaseConnection { DatabaseConnectionType::SqlxSqlitePoolConnection(SqlxSqlitePoolConnection { pool, metric_callback: None, }) .into() } } impl SqlxSqlitePoolConnection { /// Execute a [Statement] on a SQLite backend #[instrument(level = "trace")] pub fn execute(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.execute(&mut *conn) { Ok(res) => Ok(res.into()), Err(err) => Err(sqlx_error_to_exec_err(err)), } }) } /// Execute an unprepared SQL statement on a SQLite backend #[instrument(level = "trace")] pub fn execute_unprepared(&self, sql: &str) -> Result { debug_print!("{}", sql); let conn = &mut self.pool.acquire().map_err(sqlx_conn_acquire_err)?; match conn.execute(sql) { Ok(res) => Ok(res.into()), Err(err) => Err(sqlx_error_to_exec_err(err)), } } /// Get one result from a SQL query. Returns [Option::None] if no match was found #[instrument(level = "trace")] pub fn query_one(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.fetch_one(&mut *conn) { Ok(row) => Ok(Some(row.into())), Err(err) => match err { sqlx::Error::RowNotFound => Ok(None), _ => Err(sqlx_error_to_query_err(err)), }, } }) } /// Get the results of a query returning them as a Vec<[QueryResult]> #[instrument(level = "trace")] pub fn query_all(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); let mut conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; crate::metric::metric!(self.metric_callback, &stmt, { match query.fetch_all(&mut *conn) { Ok(rows) => Ok(rows.into_iter().map(|r| r.into()).collect()), Err(err) => Err(sqlx_error_to_query_err(err)), } }) } /// Stream the results of executing a SQL query #[instrument(level = "trace")] pub fn stream(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; Ok(QueryStream::from(( conn, stmt, self.metric_callback.clone(), ))) } /// Bundle a set of SQL statements that execute together. #[instrument(level = "trace")] pub fn begin( &self, isolation_level: Option, access_mode: Option, sqlite_transaction_mode: Option, ) -> Result { let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; DatabaseTransaction::new_sqlite( conn, self.metric_callback.clone(), isolation_level, access_mode, sqlite_transaction_mode, ) } /// Create a SQLite transaction #[instrument(level = "trace", skip(callback))] pub fn transaction( &self, callback: F, isolation_level: Option, access_mode: Option, ) -> Result> where F: for<'b> FnOnce(&'b DatabaseTransaction) -> Result, E: std::fmt::Display + std::fmt::Debug, { let conn = self.pool.acquire().map_err(sqlx_conn_acquire_err)?; let transaction = DatabaseTransaction::new_sqlite( conn, self.metric_callback.clone(), isolation_level, access_mode, None, ) .map_err(|e| TransactionError::Connection(e))?; transaction.run(callback) } pub(crate) fn set_metric_callback(&mut self, callback: F) where F: Fn(&crate::metric::Info<'_>) + 'static, { self.metric_callback = Some(Arc::new(callback)); } /// Checks if a connection to the database is still valid. pub fn ping(&self) -> Result<(), DbErr> { let conn = &mut self.pool.acquire().map_err(sqlx_conn_acquire_err)?; match conn.ping() { Ok(_) => Ok(()), Err(err) => Err(sqlx_error_to_conn_err(err)), } } /// Explicitly close the SQLite connection. /// See [`Self::close_by_ref`] for usage with references. pub fn close(self) -> Result<(), DbErr> { self.close_by_ref() } /// Explicitly close the SQLite connection pub fn close_by_ref(&self) -> Result<(), DbErr> { self.pool.close(); Ok(()) } } impl From for QueryResult { fn from(row: SqliteRow) -> QueryResult { QueryResult { row: QueryResultRow::SqlxSqlite(row), } } } impl From for ExecResult { fn from(result: SqliteQueryResult) -> ExecResult { ExecResult { result: ExecResultHolder::SqlxSqlite(result), } } } pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Sqlite, SqlxValues> { let values = stmt .values .as_ref() .map_or(Values(Vec::new()), |values| values.clone()); sqlx::query_with(&stmt.sql, SqlxValues(values)) } pub(crate) fn set_transaction_config( _conn: &mut PoolConnection, isolation_level: Option, access_mode: Option, ) -> Result<(), DbErr> { if isolation_level.is_some() { warn!("Setting isolation level in a SQLite transaction isn't supported"); } if access_mode.is_some() { warn!("Setting access mode in a SQLite transaction isn't supported"); } Ok(()) } #[cfg(feature = "sqlite-use-returning-for-3_35")] fn get_version(conn: &SqlxSqlitePoolConnection) -> Result { let stmt = Statement { sql: "SELECT sqlite_version()".to_string(), values: None, db_backend: crate::DbBackend::Sqlite, }; conn.query_one(stmt)? .ok_or_else(|| { DbErr::Conn(RuntimeErr::Internal( "Error reading SQLite version".to_string(), )) })? .try_get_by(0) } impl From<( PoolConnection, Statement, Option, )> for crate::QueryStream { fn from( (conn, stmt, metric_callback): ( PoolConnection, Statement, Option, ), ) -> Self { crate::QueryStream::build(stmt, crate::InnerConnection::Sqlite(conn), metric_callback) } } impl crate::DatabaseTransaction { pub(crate) fn new_sqlite( inner: PoolConnection, metric_callback: Option, isolation_level: Option, access_mode: Option, sqlite_transaction_mode: Option, ) -> Result { Self::begin( Arc::new(Mutex::new(crate::InnerConnection::Sqlite(inner))), crate::DbBackend::Sqlite, metric_callback, isolation_level, access_mode, sqlite_transaction_mode, ) } } #[cfg(feature = "proxy")] pub(crate) fn from_sqlx_sqlite_row_to_proxy_row(row: &sqlx::sqlite::SqliteRow) -> crate::ProxyRow { // https://docs.rs/sqlx-sqlite/0.7.2/src/sqlx_sqlite/type_info.rs.html // https://docs.rs/sqlx-sqlite/0.7.2/sqlx_sqlite/types/index.html use sea_query::Value; use sqlx::{Column, Row, TypeInfo}; crate::ProxyRow { values: row .columns() .iter() .map(|c| { ( c.name().to_string(), match c.type_info().name() { "BOOLEAN" => { Value::Bool(row.try_get(c.ordinal()).expect("Failed to get boolean")) } "INTEGER" => { Value::Int(row.try_get(c.ordinal()).expect("Failed to get integer")) } "BIGINT" | "INT8" => Value::BigInt( row.try_get(c.ordinal()).expect("Failed to get big integer"), ), "REAL" => { Value::Double(row.try_get(c.ordinal()).expect("Failed to get double")) } "TEXT" => Value::String( row.try_get::, _>(c.ordinal()) .expect("Failed to get string") .map(Box::new), ), "BLOB" => Value::Bytes( row.try_get::>, _>(c.ordinal()) .expect("Failed to get bytes") .map(Box::new), ), #[cfg(feature = "with-chrono")] "DATETIME" => { use chrono::{DateTime, Utc}; Value::ChronoDateTimeUtc( row.try_get::>, _>(c.ordinal()) .expect("Failed to get timestamp") .map(Box::new), ) } #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "DATETIME" => { use time::OffsetDateTime; Value::TimeDateTimeWithTimeZone( row.try_get::, _>(c.ordinal()) .expect("Failed to get timestamp") .map(Box::new), ) } #[cfg(feature = "with-chrono")] "DATE" => { use chrono::NaiveDate; Value::ChronoDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get date") .map(Box::new), ) } #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "DATE" => { use time::Date; Value::TimeDate( row.try_get::, _>(c.ordinal()) .expect("Failed to get date") .map(Box::new), ) } #[cfg(feature = "with-chrono")] "TIME" => { use chrono::NaiveTime; Value::ChronoTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get time") .map(Box::new), ) } #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIME" => { use time::Time; Value::TimeTime( row.try_get::, _>(c.ordinal()) .expect("Failed to get time") .map(Box::new), ) } _ => unreachable!("Unknown column type: {}", c.type_info().name()), }, ) }) .collect(), } } ================================================ FILE: sea-orm-sync/src/dynamic/entity.rs ================================================ use super::{FieldType, ModelType}; use crate::{ColumnDef, ColumnTrait, DbBackend, EntityTrait, Iterable, ModelTrait, Value}; use sea_query::{ ArrayType, BinOper, DynIden, Expr, ExprTrait, IntoColumnRef, IntoIden, IntoLikeExpr, IntoTableRef, SelectStatement, SimpleExpr, TableRef, }; use std::sync::Arc; #[derive(Debug)] pub struct Entity { schema_name: Option>, table_name: Arc, columns: Vec, } #[derive(Debug)] pub struct Column { table_name: Arc, column_name: Arc, column_def: ColumnDef, value_type: ArrayType, enum_type_name: Option>, } impl Entity { pub fn schema_name(&self) -> Option<&str> { self.schema_name.as_deref() } pub fn table_name(&self) -> &str { &self.table_name } pub fn table_ref(&self) -> TableRef { match self.schema_name() { Some(schema) => (schema.to_owned(), self.table_name().to_owned()).into_table_ref(), None => self.table_name().to_owned().into_table_ref(), } } pub fn iter_columns(&self) -> impl Iterator { self.columns.iter() } } impl Entity { pub fn from_entity(entity: E) -> Self { Self { schema_name: entity.schema_name().map(Arc::from), table_name: Arc::from(entity.table_name()), columns: ::iter() .map(|c| { let (tbl, col) = c.as_column_ref(); Column { table_name: Arc::from(tbl.inner()), column_name: Arc::from(col.inner()), column_def: c.def(), value_type: ::get_value_type(c), enum_type_name: c.enum_type_name().map(Arc::from), } }) .collect(), } } pub fn to_model_type(&self) -> ModelType { ModelType { fields: self .columns .iter() .map(|c| FieldType { field: c.column_name.clone(), type_: c.value_type.clone(), }) .collect(), } } } impl Column { pub fn def(&self) -> ColumnDef { self.column_def.clone() } pub fn column_name(&self) -> &str { &self.column_name } pub fn enum_type_name(&self) -> Option<&str> { self.enum_type_name.as_deref() } pub fn entity_name(&self) -> DynIden { self.table_name.to_string().into_iden() } pub fn as_column_ref(&self) -> (DynIden, DynIden) { ( self.entity_name(), self.column_name().to_owned().into_iden(), ) } crate::entity::column::macros::bind_oper!(pub eq, Equal); crate::entity::column::macros::bind_oper!(pub ne, NotEqual); crate::entity::column::macros::bind_oper!(pub gt, GreaterThan); crate::entity::column::macros::bind_oper!(pub gte, GreaterThanOrEqual); crate::entity::column::macros::bind_oper!(pub lt, SmallerThan); crate::entity::column::macros::bind_oper!(pub lte, SmallerThanOrEqual); pub fn between(&self, a: V, b: V) -> SimpleExpr where V: Into, { Expr::col(self.as_column_ref()).between(a, b) } pub fn not_between(&self, a: V, b: V) -> SimpleExpr where V: Into, { Expr::col(self.as_column_ref()).not_between(a, b) } pub fn like(&self, s: T) -> SimpleExpr where T: IntoLikeExpr, { Expr::col(self.as_column_ref()).like(s) } pub fn not_like(&self, s: T) -> SimpleExpr where T: IntoLikeExpr, { Expr::col(self.as_column_ref()).not_like(s) } pub fn starts_with(&self, s: T) -> SimpleExpr where T: Into, { let pattern = format!("{}%", s.into()); Expr::col(self.as_column_ref()).like(pattern) } pub fn ends_with(&self, s: T) -> SimpleExpr where T: Into, { let pattern = format!("%{}", s.into()); Expr::col(self.as_column_ref()).like(pattern) } pub fn contains(&self, s: T) -> SimpleExpr where T: Into, { let pattern = format!("%{}%", s.into()); Expr::col(self.as_column_ref()).like(pattern) } crate::entity::column::macros::bind_func_no_params!(pub max); crate::entity::column::macros::bind_func_no_params!(pub min); crate::entity::column::macros::bind_func_no_params!(pub sum); crate::entity::column::macros::bind_func_no_params!(pub count); crate::entity::column::macros::bind_func_no_params!(pub is_null); crate::entity::column::macros::bind_func_no_params!(pub is_not_null); pub fn if_null(&self, v: V) -> SimpleExpr where V: Into, { Expr::col(self.as_column_ref()).if_null(v) } crate::entity::column::macros::bind_vec_func!(pub is_in); crate::entity::column::macros::bind_vec_func!(pub is_not_in); crate::entity::column::macros::bind_subquery_func!(pub in_subquery); crate::entity::column::macros::bind_subquery_func!(pub not_in_subquery); pub fn into_expr(self) -> Expr { SimpleExpr::Column(self.as_column_ref().into_column_ref()) } #[allow(clippy::match_single_binding)] pub fn into_returning_expr(self, db_backend: DbBackend) -> Expr { match db_backend { _ => Expr::col(self.column_name().to_owned()), } } pub fn select_as(&self, expr: Expr) -> SimpleExpr { crate::entity::column::cast_enum_as( expr, &self.def(), crate::entity::column::select_enum_as, ) } pub fn save_as(&self, val: Expr) -> SimpleExpr { crate::entity::column::cast_enum_as(val, &self.def(), crate::entity::column::save_enum_as) } } ================================================ FILE: sea-orm-sync/src/dynamic/execute.rs ================================================ use crate::{ ConnectionTrait, DbBackend, DbErr, EntityTrait, FromQueryResult, QueryResult, Select, Statement, dynamic, }; use itertools::Itertools; use sea_query::{DynIden, Expr, IntoIden, SelectStatement}; use std::marker::PhantomData; #[derive(Debug)] pub struct SelectModelAndDynModel where M: FromQueryResult, { model: PhantomData, dyn_model: dynamic::ModelType, } #[derive(Clone, Debug)] pub struct DynSelector where S: DynSelectorTrait, { pub(crate) query: SelectStatement, selector: S, } pub trait DynSelectorTrait { type Item: Sized; #[allow(clippy::wrong_self_convention)] fn from_raw_query_result(&self, res: QueryResult) -> Result; } impl DynSelectorTrait for SelectModelAndDynModel where M: FromQueryResult + Sized, { type Item = (M, dynamic::Model); fn from_raw_query_result(&self, res: QueryResult) -> Result { Ok(( M::from_query_result(&res, "")?, self.dyn_model.from_query_result(&res, "")?, )) } } impl Select where E: EntityTrait, { pub fn select_also_dyn_model( mut self, table: DynIden, dyn_model: dynamic::ModelType, ) -> DynSelector> { for field in dyn_model.fields.iter() { self.query.expr(Expr::col(( table.clone(), field.field().to_owned().into_iden(), ))); } DynSelector { query: self.query, selector: SelectModelAndDynModel { model: PhantomData, dyn_model, }, } } } impl DynSelector where S: DynSelectorTrait, { /// Get the SQL statement pub fn into_statement(self, builder: DbBackend) -> Statement { builder.build(&self.query) } /// Get an item from the Select query pub fn one(mut self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.query.limit(1); let row = db.query_one(&self.query)?; match row { Some(row) => Ok(Some(self.selector.from_raw_query_result(row)?)), None => Ok(None), } } /// Get all items from the Select query pub fn all(self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { db.query_all(&self.query)? .into_iter() .map(|row| self.selector.from_raw_query_result(row)) .try_collect() } } ================================================ FILE: sea-orm-sync/src/dynamic/mod.rs ================================================ //! The API of this module is not yet stable, and may have breaking changes between minor versions. #![allow(missing_docs)] mod entity; mod execute; mod model; pub use entity::*; pub use execute::*; pub use model::*; ================================================ FILE: sea-orm-sync/src/dynamic/model.rs ================================================ use crate::{DbErr, QueryResult}; use sea_query::{ArrayType, DynIden, Value}; use std::sync::Arc; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ModelType { pub fields: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FieldType { pub(super) field: Arc, pub(super) type_: ArrayType, } #[derive(Debug, Clone, PartialEq)] pub struct Model { pub fields: Vec, } #[derive(Debug, Clone, PartialEq)] pub struct FieldValue { pub(super) field: Arc, pub value: Value, } impl FieldType { pub fn new(iden: DynIden, type_: ArrayType) -> Self { Self { field: Arc::from(iden.inner()), type_, } } pub fn field(&self) -> &str { &self.field } } impl FieldValue { pub fn field(&self) -> &str { &self.field } } impl ModelType { pub fn from_query_result(&self, res: &QueryResult, pre: &str) -> Result { let mut fields = Vec::new(); for f in self.fields.iter() { fields.push(FieldValue { field: f.field.clone(), value: try_get(res, pre, f.field(), &f.type_)?, }); } Ok(Model { fields }) } } impl Model { pub fn try_get(&self, col: &str) -> Result<&Value, DbErr> { for field in &self.fields { if field.field() == col { return Ok(&field.value); } } Err(DbErr::Type(format!("{col} not exist"))) } } fn try_get(res: &QueryResult, pre: &str, col: &str, ty: &ArrayType) -> Result { // how to handle postgres-array? Ok(match ty { ArrayType::Bool => Value::Bool(res.try_get(pre, col)?), ArrayType::TinyInt => Value::TinyInt(res.try_get(pre, col)?), ArrayType::SmallInt => Value::SmallInt(res.try_get(pre, col)?), ArrayType::Int => Value::Int(res.try_get(pre, col)?), ArrayType::BigInt => Value::BigInt(res.try_get(pre, col)?), ArrayType::TinyUnsigned => Value::TinyUnsigned(res.try_get(pre, col)?), ArrayType::SmallUnsigned => Value::SmallUnsigned(res.try_get(pre, col)?), ArrayType::Unsigned => Value::Unsigned(res.try_get(pre, col)?), ArrayType::BigUnsigned => Value::BigUnsigned(res.try_get(pre, col)?), ArrayType::Float => Value::Float(res.try_get(pre, col)?), ArrayType::Double => Value::Double(res.try_get(pre, col)?), ArrayType::String => Value::String(res.try_get(pre, col)?), ArrayType::Char => return Err(DbErr::Type("Unsupported type: char".into())), ArrayType::Bytes => Value::Bytes(res.try_get(pre, col)?), #[cfg(feature = "with-json")] ArrayType::Json => Value::Json(res.try_get::>(pre, col)?.map(Box::new)), #[cfg(feature = "with-chrono")] ArrayType::ChronoDate => Value::ChronoDate(res.try_get(pre, col)?), #[cfg(feature = "with-chrono")] ArrayType::ChronoTime => Value::ChronoTime(res.try_get(pre, col)?), #[cfg(feature = "with-chrono")] ArrayType::ChronoDateTime => Value::ChronoDateTime(res.try_get(pre, col)?), #[cfg(feature = "with-chrono")] ArrayType::ChronoDateTimeUtc => Value::ChronoDateTimeUtc(res.try_get(pre, col)?), #[cfg(feature = "with-chrono")] ArrayType::ChronoDateTimeLocal => Value::ChronoDateTimeLocal(res.try_get(pre, col)?), #[cfg(feature = "with-chrono")] ArrayType::ChronoDateTimeWithTimeZone => { Value::ChronoDateTimeWithTimeZone(res.try_get(pre, col)?) } #[cfg(feature = "with-time")] ArrayType::TimeDate => Value::TimeDate(res.try_get(pre, col)?), #[cfg(feature = "with-time")] ArrayType::TimeTime => Value::TimeTime(res.try_get(pre, col)?), #[cfg(feature = "with-time")] ArrayType::TimeDateTime => Value::TimeDateTime(res.try_get(pre, col)?), #[cfg(feature = "with-time")] ArrayType::TimeDateTimeWithTimeZone => { Value::TimeDateTimeWithTimeZone(res.try_get(pre, col)?) } #[cfg(feature = "with-uuid")] ArrayType::Uuid => Value::Uuid(res.try_get(pre, col)?), #[cfg(feature = "with-rust_decimal")] ArrayType::Decimal => Value::Decimal(res.try_get(pre, col)?), #[cfg(feature = "with-bigdecimal")] ArrayType::BigDecimal => { Value::BigDecimal(res.try_get::>(pre, col)?.map(Box::new)) } #[cfg(feature = "with-ipnetwork")] ArrayType::IpNetwork => Value::IpNetwork(res.try_get(pre, col)?), }) } #[cfg(test)] mod test { use super::*; use crate::{QueryResultRow, database::IntoMockRow, dynamic::Entity}; #[test] fn test_from_query_result() { let result = QueryResult { row: QueryResultRow::Mock( crate::tests_cfg::cake::Model { id: 12, name: "hello".into(), } .into_mock_row(), ), }; let model_ty = Entity::from_entity(crate::tests_cfg::cake::Entity).to_model_type(); assert_eq!( model_ty, ModelType { fields: vec![ FieldType { field: Arc::from("id"), type_: ArrayType::Int, }, FieldType { field: Arc::from("name"), type_: ArrayType::String, }, ], } ); assert_eq!( model_ty.from_query_result(&result, "").unwrap(), Model { fields: vec![ FieldValue { field: Arc::from("id"), value: 12i32.into(), }, FieldValue { field: Arc::from("name"), value: "hello".into(), } ], } ); } } ================================================ FILE: sea-orm-sync/src/entity/ARROW.md ================================================ # Apache Arrow Type Support in SeaORM This document explains Apache Arrow's decimal and timestamp formats and their integration with SeaORM. ## Arrow Decimal Types Overview Apache Arrow provides fixed-point decimal types for representing precise numeric values with a specific precision and scale: ### 1. **Decimal64** - **Storage**: 64-bit (8 bytes) fixed-point decimal - **Precision**: Up to 18 decimal digits - **Format**: `DataType::Decimal64(precision, scale)` - `precision` (u8): Total number of decimal digits (1-18) - `scale` (i8): Number of digits after decimal point (can be negative) - **Use case**: Compact precision decimals that fit in an i64 (e.g., prices, exchange rates) - **Example**: `Decimal64(10, 2)` represents numbers like `12345678.90` ### 2. **Decimal128** - **Storage**: 128-bit (16 bytes) fixed-point decimal - **Precision**: Up to 38 decimal digits - **Format**: `DataType::Decimal128(precision, scale)` - `precision` (u8): Total number of decimal digits (1-38) - `scale` (i8): Number of digits after decimal point (can be negative) - **Use case**: Standard precision decimals (e.g., financial calculations requiring > 18 digits) - **Example**: `Decimal128(20, 4)` represents numbers like `9999999999999999.9999` ### 3. **Decimal256** - **Storage**: 256-bit (32 bytes) fixed-point decimal - **Precision**: Up to 76 decimal digits - **Format**: `DataType::Decimal256(precision, scale)` - `precision` (u8): Total number of decimal digits (1-76) - `scale` (i8): Number of digits after decimal point (can be negative) - **Use case**: High-precision scientific calculations, very large numbers - **Example**: `Decimal256(76, 20)` for extreme precision requirements ### Precision vs Scale - **Precision**: Total number of significant digits (before + after decimal point) - **Scale**: Number of digits after the decimal point - Positive scale: digits after decimal (e.g., scale=2 → `123.45`) - Zero scale: integer (e.g., scale=0 → `12345`) - Negative scale: multiplier (e.g., scale=-2 → `12300` stored as `123`) **Examples**: ``` Decimal64(10, 2) → Can store: -99999999.99 to 99999999.99 (compact, i64) Decimal64(5, 0) → Can store: -99999 to 99999 (integers, compact) Decimal128(20, 4) → Can store: -9999999999999999.9999 to 9999999999999999.9999 Decimal128(10, 4) → Can store: -999999.9999 to 999999.9999 Decimal256(38, 10) → High precision: up to 28 digits before, 10 after decimal ``` ## SeaORM Decimal Support SeaORM supports two Rust decimal libraries: ### 1. **rust_decimal** (feature: `with-rust_decimal`) - Type: `rust_decimal::Decimal` - Precision: 28-29 significant digits - Scale: 0-28 - Storage: 128-bit - Value type: `Value::Decimal` - Best for: Most business applications, financial calculations ### 2. **bigdecimal** (feature: `with-bigdecimal`) - Type: `bigdecimal::BigDecimal` - Precision: Arbitrary (limited by memory) - Scale: Arbitrary - Storage: Variable (uses BigInt internally) - Value type: `Value::BigDecimal` - Best for: Arbitrary precision requirements, scientific computing ## Arrow → SeaORM Mapping ### Decimal64Array → rust_decimal::Decimal - **When**: Feature `with-rust_decimal` is enabled - **Limitation**: Precision ≤ 18 (fits in i64) - **Conversion**: Cast i64 to i128, then `Decimal::from_i128_with_scale()` - **Column Type**: `ColumnType::Decimal(Some((precision, scale)))` ### Decimal64Array → bigdecimal::BigDecimal - **When**: Feature `with-bigdecimal` is enabled (fallback if rust_decimal not available) - **Conversion**: Convert i64 via BigInt - **Column Type**: `ColumnType::Decimal(Some((precision, scale)))` ### Decimal128Array → rust_decimal::Decimal - **When**: Feature `with-rust_decimal` is enabled - **Limitation**: Precision ≤ 28, Scale ≤ 28 - **Conversion**: Direct mapping using `i128` to `Decimal::from_i128_with_scale()` - **Column Type**: `ColumnType::Decimal(Some((precision, scale)))` ### Decimal128Array → bigdecimal::BigDecimal - **When**: Feature `with-bigdecimal` is enabled (fallback if rust_decimal fails or not available) - **Limitation**: None (arbitrary precision) - **Conversion**: Convert via BigInt - **Column Type**: `ColumnType::Decimal(Some((precision, scale)))` or `ColumnType::Money(_)` ### Decimal256Array → bigdecimal::BigDecimal - **When**: Feature `with-bigdecimal` is enabled - **Required**: BigDecimal for precision > 38 - **Conversion**: Convert via byte array to BigInt, then apply scale - **Column Type**: `ColumnType::Decimal(Some((precision, scale)))` ## Implementation Strategy 1. **Decimal64Array**: - Try `with-rust_decimal` first (always fits, precision ≤ 18) - Fallback to `with-bigdecimal` if needed - Return type error if neither feature is enabled 2. **Decimal128Array**: - Try `with-rust_decimal` first (if precision/scale fit) - Fallback to `with-bigdecimal` if needed - Return type error if neither feature is enabled 3. **Decimal256Array**: - Requires `with-bigdecimal` (rust_decimal can't handle precision > 28) - Convert byte representation to BigInt - Apply scale to create BigDecimal 4. **Null Handling**: - Return `Value::Decimal(None)` or `Value::BigDecimal(None)` for null values --- # Apache Arrow Timestamp Types Support Arrow provides several temporal types for representing dates, times, and timestamps with varying precision. ## Arrow Temporal Types Overview ### Date Types #### 1. **Date32** - **Storage**: 32-bit signed integer - **Unit**: Days since Unix epoch (1970-01-01) - **Format**: `DataType::Date32` - **Range**: Approximately ±5.8 million years from epoch - **Use case**: Calendar dates without time component #### 2. **Date64** - **Storage**: 64-bit signed integer - **Unit**: Milliseconds since Unix epoch - **Format**: `DataType::Date64` - **Range**: Much larger than Date32 - **Use case**: Dates with millisecond precision (though time is typically zeroed) ### Time Types #### 1. **Time32** - **Storage**: 32-bit signed integer - **Units**: Second or Millisecond - **Variants**: - `Time32(TimeUnit::Second)` - seconds since midnight - `Time32(TimeUnit::Millisecond)` - milliseconds since midnight - **Range**: 0 to 86,399 seconds (00:00:00 to 23:59:59) - **Use case**: Time of day without date #### 2. **Time64** - **Storage**: 64-bit signed integer - **Units**: Microsecond or Nanosecond - **Variants**: - `Time64(TimeUnit::Microsecond)` - microseconds since midnight - `Time64(TimeUnit::Nanosecond)` - nanoseconds since midnight - **Range**: 0 to 86,399,999,999,999 nanoseconds - **Use case**: High-precision time of day ### Timestamp Types **Timestamp** types represent absolute points in time with optional timezone. - **Storage**: 64-bit signed integer - **Units**: Second, Millisecond, Microsecond, or Nanosecond - **Timezone**: Optional timezone string (e.g., "UTC", "America/New_York") - **Format**: `Timestamp(TimeUnit, Option)` **Variants**: ```rust DataType::Timestamp(TimeUnit::Second, None) // No timezone DataType::Timestamp(TimeUnit::Millisecond, None) // No timezone DataType::Timestamp(TimeUnit::Microsecond, None) // No timezone DataType::Timestamp(TimeUnit::Nanosecond, None) // No timezone DataType::Timestamp(TimeUnit::Second, Some("UTC".into())) // With timezone DataType::Timestamp(TimeUnit::Microsecond, Some("UTC".into())) // With timezone DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into())) // With timezone ``` **TimeUnit Precision**: - **Second**: 1 second precision (1,000,000,000 ns) - **Millisecond**: 1 millisecond precision (1,000,000 ns) - **Microsecond**: 1 microsecond precision (1,000 ns) - **Nanosecond**: 1 nanosecond precision (highest) ## SeaORM Temporal Type Support SeaORM supports two Rust datetime libraries for temporal types: ### 1. **chrono** (feature: `with-chrono`) - Preferred - Type Mappings: - `chrono::NaiveDate` - Date without timezone - `chrono::NaiveTime` - Time without date/timezone - `chrono::NaiveDateTime` - DateTime without timezone - `chrono::DateTime` - DateTime with UTC timezone - Value types: - `Value::ChronoDate(Option)` - `Value::ChronoTime(Option)` - `Value::ChronoDateTime(Option)` - `Value::ChronoDateTimeUtc(Option>)` - Best for: Most applications needing date/time support ### 2. **time** (feature: `with-time`) - Alternative - Type Mappings: - `time::Date` - Date without timezone - `time::Time` - Time without date/timezone - `time::PrimitiveDateTime` - DateTime without timezone - `time::OffsetDateTime` - DateTime with timezone offset - Value types: - `Value::TimeDate(Option)` - `Value::TimeTime(Option