feat(rust): add litellm-db and litellm-db-testing workspace scaffolding (#43504)

* feat(rust): add litellm-db and litellm-db-testing workspace scaffolding

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(db-testing): apply the real Prisma migrations in a test and drop the sort mutation

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-09-27 18:48:45 -07:00 • committed by GitHub
parent cc30d93984
commit 9b08112ed2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 914 additions and 9 deletions

View file

@ -17,3 +17,6 @@ rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
[env]
SQLX_OFFLINE = "true"

View file

@ -4,7 +4,7 @@
.PHONY: help test test-unit test-unit-llms test-unit-proxy-guardrails test-unit-proxy-core test-unit-proxy-misc \
test-unit-integrations test-unit-core-utils test-unit-other test-unit-root \
test-proxy-unit-a test-proxy-unit-b test-integration test-unit-helm \
test-rust-extension \
test-rust-extension rust-sqlx-prepare \
info lint lint-inner lint-dev lint-checks format \
lint-basedpyright lint-e2e-basedpyright lint-basedpyright-budget-update lint-type-discipline lint-type-discipline-budget-update \
lint-ruff-budget lint-ruff-budget-update lint-budget-update lint-gate \
@ -56,6 +56,7 @@ help:
@echo " make test-integration - Run integration tests"
@echo " make test-unit-helm - Run helm unit tests"
@echo " make test-rust-extension - Build the Rust extension and run its public Python tests"
@echo " make rust-sqlx-prepare - Refresh litellm-rust/crates/db/.sqlx against a migrated Postgres container"
@echo ""
@echo "Heavy targets (check, lint) queue for LITELLM_GATE_SLOTS machine-wide"
@echo "slots (default 2; 0 disables) so parallel sessions don't thrash one machine."
@ -306,6 +307,9 @@ test-rust-extension:
LITELLM_RUST=1 LITELLM_LOCAL_MODEL_COST_MAP=True \
"$$temporary/venv/bin/python" -I -m pytest --import-mode=importlib -m requires_rust_extension tests/test_litellm_rust
rust-sqlx-prepare:
cd litellm-rust && cargo run -p litellm-db-testing --bin sqlx-prepare
test: install-test-deps
$(UV_RUN) pytest tests/

626
litellm-rust/Cargo.lock generated
View file

@ -146,6 +146,22 @@ dependencies = [
"serde_json",
]
[[package]]
name = "astral-tokio-tar"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b18457efd137254e016bbde5e1d88df61c4e1a5ae2223746e56123bac6af2463"
dependencies = [
"futures-core",
"libc",
"portable-atomic",
"rustc-hash",
"rustix",
"tokio",
"tokio-stream",
"xattr",
]
[[package]]
name = "async-compression"
version = "0.4.46"
@ -202,6 +218,15 @@ dependencies = [
"syn 3.0.6",
]
[[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"
@ -893,6 +918,9 @@ name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
dependencies = [
"serde_core",
]
[[package]]
name = "block-buffer"
@ -912,6 +940,80 @@ dependencies = [
"hybrid-array",
]
[[package]]
name = "bollard"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee04c4c84f1f811b017f2fbb7dd8815c976e7ca98593de9c1e2afad0f636bff4"
dependencies = [
"async-stream",
"base64 0.22.1",
"bitflags 2.13.1",
"bollard-buildkit-proto",
"bollard-stubs",
"bytes",
"futures-core",
"futures-util",
"hex",
"home",
"http 1.4.2",
"http-body-util",
"hyper 1.10.1",
"hyper-named-pipe",
"hyper-rustls 0.27.9",
"hyper-util",
"hyperlocal",
"log",
"num",
"pin-project-lite",
"rand 0.9.5",
"rustls 0.23.42",
"rustls-native-certs",
"rustls-pki-types",
"serde",
"serde_derive",
"serde_json",
"serde_urlencoded",
"thiserror 2.0.19",
"time",
"tokio",
"tokio-stream",
"tokio-util",
"tonic",
"tower-service",
"url",
"winapi",
]
[[package]]
name = "bollard-buildkit-proto"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85a885520bf6249ab931a764ffdb87b0ceef48e6e7d807cfdb21b751e086e1ad"
dependencies = [
"prost",
"prost-types",
"tonic",
"tonic-prost",
"ureq",
]
[[package]]
name = "bollard-stubs"
version = "1.52.1-rc.29.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0a8ca8799131c1837d1282c3f81f31e76ceb0ce426e04a7fe1ccee3287c066"
dependencies = [
"base64 0.22.1",
"bollard-buildkit-proto",
"bytes",
"prost",
"serde",
"serde_json",
"serde_repr",
"time",
]
[[package]]
name = "borrow-or-share"
version = "0.2.4"
@ -1179,6 +1281,21 @@ dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
[[package]]
name = "crc-fast"
version = "1.10.0"
@ -1276,6 +1393,15 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.22"
@ -1560,6 +1686,23 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "docker_credential"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29547a1dc60885a552306986316bc9701ba120c1a8db6769fa68691529ad373d"
dependencies = [
"base64 0.22.1",
"serde",
"serde_json",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dunce"
version = "1.0.5"
@ -1577,6 +1720,9 @@ name = "either"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
dependencies = [
"serde",
]
[[package]]
name = "email_address"
@ -1618,6 +1764,16 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6"
[[package]]
name = "etcetera"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
dependencies = [
"cfg-if",
"windows-sys 0.61.2",
]
[[package]]
name = "event-listener"
version = "5.4.2"
@ -1678,6 +1834,17 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "ferroid"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee93edf3c501f0035bbeffeccfed0b79e14c311f12195ec0e661e114a0f60da4"
dependencies = [
"portable-atomic",
"rand 0.10.2",
"web-time",
]
[[package]]
name = "filetime"
version = "0.2.29"
@ -1716,6 +1883,17 @@ dependencies = [
"serde",
]
[[package]]
name = "flume"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
dependencies = [
"futures-core",
"futures-sink",
"spin 0.9.9",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -1795,6 +1973,17 @@ dependencies = [
"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.33"
@ -2210,6 +2399,8 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
@ -2226,11 +2417,11 @@ dependencies = [
[[package]]
name = "hashlink"
version = "0.12.2"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb"
checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
dependencies = [
"hashbrown 0.17.1",
"hashbrown 0.16.1",
]
[[package]]
@ -2251,6 +2442,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.13.0"
@ -2260,6 +2460,15 @@ dependencies = [
"digest 0.11.3",
]
[[package]]
name = "home"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "http"
version = "0.2.12"
@ -2382,6 +2591,20 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-named-pipe"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fab3637d6b04a8037af8a266fdf6cf92ea957e8c53981a2bf6136572531025bf"
dependencies = [
"hex",
"hyper 1.10.1",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "hyper-rustls"
version = "0.24.2"
@ -2450,6 +2673,21 @@ dependencies = [
"tracing",
]
[[package]]
name = "hyperlocal"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7"
dependencies = [
"hex",
"http-body-util",
"hyper 1.10.1",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@ -2816,9 +3054,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libsqlite3-sys"
version = "0.38.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8"
checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
dependencies = [
"cc",
"pkg-config",
@ -3197,6 +3435,26 @@ dependencies = [
"rstest",
]
[[package]]
name = "litellm-db"
version = "0.1.0"
dependencies = [
"serde",
"sqlx",
]
[[package]]
name = "litellm-db-testing"
version = "0.1.0"
dependencies = [
"rstest",
"sqlx",
"tempfile",
"testcontainers-modules",
"thiserror 2.0.19",
"tokio",
]
[[package]]
name = "litellm-framing"
version = "0.1.0"
@ -4151,6 +4409,31 @@ dependencies = [
"windows-link",
]
[[package]]
name = "parse-display"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a"
dependencies = [
"parse-display-derive",
"regex",
"regex-syntax",
]
[[package]]
name = "parse-display-derive"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281"
dependencies = [
"proc-macro2",
"quote",
"regex",
"regex-syntax",
"structmeta",
"syn 2.0.119",
]
[[package]]
name = "paste"
version = "1.0.15"
@ -5025,9 +5308,9 @@ dependencies = [
[[package]]
name = "rusqlite"
version = "0.40.2"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3"
checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
dependencies = [
"bitflags 2.13.1",
"fallible-iterator",
@ -5420,6 +5703,17 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_repr"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
]
[[package]]
name = "serde_spanned"
version = "1.1.1"
@ -5593,6 +5887,9 @@ name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
dependencies = [
"serde",
]
[[package]]
name = "socket2"
@ -5619,6 +5916,9 @@ name = "spin"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
dependencies = [
"lock_api",
]
[[package]]
name = "spin"
@ -5650,6 +5950,183 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "sqlx"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71"
dependencies = [
"sqlx-core",
"sqlx-macros",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
]
[[package]]
name = "sqlx-core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb"
dependencies = [
"base64 0.22.1",
"bytes",
"cfg-if",
"chrono",
"crc",
"crossbeam-queue",
"either",
"event-listener",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.16.1",
"hashlink",
"indexmap 2.14.0",
"log",
"memchr",
"percent-encoding",
"rustls 0.23.42",
"rustls-native-certs",
"serde",
"serde_json",
"sha2 0.10.9",
"smallvec",
"thiserror 2.0.19",
"tokio",
"tokio-stream",
"tracing",
"url",
]
[[package]]
name = "sqlx-macros"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf"
dependencies = [
"proc-macro2",
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 2.0.119",
]
[[package]]
name = "sqlx-macros-core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3"
dependencies = [
"cfg-if",
"dotenvy",
"either",
"heck",
"hex",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2 0.10.9",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.119",
"thiserror 2.0.19",
"tokio",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27"
dependencies = [
"bitflags 2.13.1",
"byteorder",
"bytes",
"chrono",
"crc",
"digest 0.11.3",
"dotenvy",
"either",
"futures-core",
"futures-util",
"generic-array",
"log",
"percent-encoding",
"serde",
"sha1 0.11.0",
"sha2 0.11.0",
"sqlx-core",
"thiserror 2.0.19",
"tracing",
]
[[package]]
name = "sqlx-postgres"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.13.1",
"byteorder",
"chrono",
"crc",
"dotenvy",
"etcetera",
"futures-channel",
"futures-core",
"futures-util",
"hex",
"hkdf",
"hmac",
"itoa",
"log",
"md-5",
"memchr",
"rand 0.10.2",
"serde",
"serde_json",
"sha2 0.11.0",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.19",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-sqlite"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
dependencies = [
"atoi",
"chrono",
"flume",
"form_urlencoded",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"libsqlite3-sys",
"log",
"percent-encoding",
"serde",
"sqlx-core",
"thiserror 2.0.19",
"tracing",
"url",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@ -5662,12 +6139,46 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[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 = "structmeta"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329"
dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.119",
]
[[package]]
name = "structmeta-derive"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "strum"
version = "0.28.0"
@ -5796,6 +6307,46 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "testcontainers"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd5785b5483672915ed5fe3cddf9f546802779fc1eceff0a6fb7321fac81c1e"
dependencies = [
"astral-tokio-tar",
"async-trait",
"bollard",
"bytes",
"docker_credential",
"either",
"etcetera",
"ferroid",
"futures",
"http 1.4.2",
"itertools 0.14.0",
"log",
"memchr",
"parse-display",
"pin-project-lite",
"serde",
"serde_json",
"serde_with",
"thiserror 2.0.19",
"tokio",
"tokio-stream",
"tokio-util",
"url",
]
[[package]]
name = "testcontainers-modules"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5985fde5befe4ffa77a052e035e16c2da86e8bae301baa9f9904ad3c494d357"
dependencies = [
"testcontainers",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@ -6373,6 +6924,12 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-general-category"
version = "1.1.0"
@ -6385,6 +6942,15 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-normalization"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-normalization-alignments"
version = "0.1.12"
@ -6394,6 +6960,12 @@ dependencies = [
"smallvec",
]
[[package]]
name = "unicode-properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "unicode-segmentation"
version = "1.13.3"
@ -6424,6 +6996,33 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc"
dependencies = [
"base64 0.23.1",
"log",
"percent-encoding",
"rustls 0.23.42",
"rustls-pki-types",
"ureq-proto",
"utf8-zero",
]
[[package]]
name = "ureq-proto"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be"
dependencies = [
"base64 0.23.1",
"http 1.4.2",
"httparse",
"log",
]
[[package]]
name = "url"
version = "2.5.8"
@ -6434,6 +7033,7 @@ dependencies = [
"idna",
"percent-encoding",
"serde",
"serde_derive",
]
[[package]]
@ -6448,6 +7048,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8-zero"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@ -6701,6 +7307,12 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "whoami"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c"
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -37,6 +37,8 @@ litellm-http = { path = "crates/http" }
litellm-llms = { path = "crates/llms" }
litellm-types = { path = "crates/types" }
litellm-core-utils = { path = "crates/core-utils" }
litellm-db = { path = "crates/db" }
litellm-db-testing = { path = "crates/db-testing" }
litellm-cache = { path = "crates/cache" }
litellm-cache-azure-blob = { path = "crates/cache-azure-blob" }
litellm-cache-memory = { path = "crates/cache-memory" }
@ -81,6 +83,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["float_roundtrip"] }
serde_with = { version = "=3.16.1", default-features = false, features = ["std", "macros"] }
sha2 = "0.10"
sqlx = { version = "0.9.0", default-features = false, features = ["json", "macros", "postgres", "runtime-tokio", "chrono", "tls-rustls-ring-native-roots"] }
subtle = "2"
thiserror = "2.0"
tokenizers = { version = "0.23.1", default-features = false, features = ["onig"] }

View file

@ -12,6 +12,13 @@ disallowed-methods = [
{ path = "reqwest::ClientBuilder::danger_accept_invalid_certs", reason = "set HttpClientConfig::verify instead" },
{ path = "reqwest::ClientBuilder::identity", reason = "set HttpClientConfig::client_certificate instead" },
{ path = "reqwest::ClientBuilder::use_preconfigured_tls", reason = "HttpClientConfig owns the TLS configuration" },
{ path = "sqlx::query", reason = "use sqlx::query! or query_file! so the SQL is checked against the migrated schema" },
{ path = "sqlx::query_as", reason = "use sqlx::query_as! or query_file_as! so the SQL is checked against the migrated schema" },
{ path = "sqlx::query_scalar", reason = "use sqlx::query_scalar! so the SQL is checked against the migrated schema" },
{ path = "sqlx::query_with", reason = "use sqlx::query! or query_file! so the SQL is checked against the migrated schema" },
{ path = "sqlx::query_as_with", reason = "use sqlx::query_as! or query_file_as! so the SQL is checked against the migrated schema" },
{ path = "sqlx::query_scalar_with", reason = "use sqlx::query_scalar! so the SQL is checked against the migrated schema" },
{ path = "sqlx::raw_sql", reason = "raw_sql is unchecked; use the checked query macros" },
]
# Every outbound client comes from litellm_http::HttpClientPool so it honors the host's TLS,

View file

@ -9,7 +9,7 @@ repository.workspace = true
litellm-cache.workspace = true
py_literal = "0.4.0"
rand.workspace = true
rusqlite = { version = "0.40", features = ["bundled"] }
rusqlite = { version = "0.39", features = ["bundled"] }
serde-pickle = "1.2"
serde_json.workspace = true
tokio.workspace = true

View file

@ -0,0 +1,16 @@
[package]
name = "litellm-db-testing"
version = "0.1.0"
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
sqlx = { workspace = true, features = ["migrate"] }
testcontainers-modules = { version = "0.15.0", features = ["postgres"] }
thiserror.workspace = true
tokio.workspace = true
[dev-dependencies]
rstest.workspace = true
tempfile.workspace = true

View file

@ -0,0 +1,26 @@
use std::{
env,
process::{Command, ExitCode},
};
use litellm_db_testing::{Error, MigratedPostgres};
#[tokio::main]
async fn main() -> Result<ExitCode, Error> {
let postgres = MigratedPostgres::start().await?;
let status = Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()))
.args(["sqlx", "prepare"])
.args(env::args().skip(1))
.args(["--", "--all-targets", "--all-features"])
.current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/../db"))
.env("DATABASE_URL", postgres.url())
.env("SQLX_OFFLINE", "false")
.status()
.map_err(Error::Prepare)?;
Ok(if status.success() {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
})
}

View file

@ -0,0 +1,19 @@
use std::{io, path::PathBuf};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("reading {path}")]
Read {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("starting the Postgres container")]
Container(#[from] testcontainers_modules::testcontainers::TestcontainersError),
#[error("connecting to the Postgres container")]
Connect(#[from] sqlx::Error),
#[error("applying the Prisma migrations")]
Migrate(#[from] sqlx::migrate::MigrateError),
#[error("running cargo sqlx prepare")]
Prepare(#[source] io::Error),
}

View file

@ -0,0 +1,7 @@
mod error;
mod migrations;
mod postgres;
pub use error::Error;
pub use migrations::{PRISMA_MIGRATIONS_DIR, prisma_migrations};
pub use postgres::MigratedPostgres;

View file

@ -0,0 +1,50 @@
use std::{collections::BTreeSet, fs, path::Path};
use sqlx::{
AssertSqlSafe, SqlSafeStr,
migrate::{Migration, MigrationType},
};
use crate::Error;
pub const PRISMA_MIGRATIONS_DIR: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../../litellm-proxy-extras/litellm_proxy_extras/migrations"
);
const MIGRATION_FILENAME: &str = "migration.sql";
pub fn prisma_migrations(directory: &Path) -> Result<Vec<Migration>, Error> {
migration_names(directory)?
.into_iter()
.zip(1..)
.map(|(name, version)| {
let path = directory.join(&name).join(MIGRATION_FILENAME);
let sql = fs::read_to_string(&path).map_err(|source| Error::Read { path, source })?;
Ok(Migration::new(
version,
name.into(),
MigrationType::Simple,
AssertSqlSafe(sql).into_sql_str(),
true,
))
})
.collect()
}
fn migration_names(directory: &Path) -> Result<BTreeSet<String>, Error> {
let read_error = |source| Error::Read {
path: directory.to_owned(),
source,
};
fs::read_dir(directory)
.map_err(read_error)?
.map(|entry| {
let entry = entry.map_err(read_error)?;
let is_dir = entry.file_type().map_err(read_error)?.is_dir();
Ok(is_dir.then(|| entry.file_name().to_string_lossy().into_owned()))
})
.filter_map(Result::transpose)
.collect()
}

View file

@ -0,0 +1,46 @@
use std::path::Path;
use sqlx::{PgPool, migrate::Migrator};
use testcontainers_modules::{
postgres::Postgres,
testcontainers::{ContainerAsync, ImageExt, runners::AsyncRunner},
};
use crate::{Error, PRISMA_MIGRATIONS_DIR, prisma_migrations};
const POSTGRES_TAG: &str =
"16@sha256:e17e86066e5ef83e0952a9347f5c792b7ece00972e2aa787a6986f471b3dd3d5";
pub struct MigratedPostgres {
_container: ContainerAsync<Postgres>,
url: String,
pool: PgPool,
}
impl MigratedPostgres {
pub async fn start() -> Result<Self, Error> {
let container = Postgres::default().with_tag(POSTGRES_TAG).start().await?;
let url = format!(
"postgres://postgres:postgres@{}:{}/postgres",
container.get_host().await?,
container.get_host_port_ipv4(5432).await?,
);
let pool = PgPool::connect(&url).await?;
Migrator::with_migrations(prisma_migrations(Path::new(PRISMA_MIGRATIONS_DIR))?)
.run(&pool)
.await?;
Ok(Self {
_container: container,
url,
pool,
})
}
pub fn url(&self) -> &str {
&self.url
}
pub fn pool(&self) -> &PgPool {
&self.pool
}
}

View file

@ -0,0 +1,65 @@
use std::fs;
use litellm_db_testing::prisma_migrations;
use rstest::rstest;
use tempfile::TempDir;
fn prisma_folder(migrations: &[(&str, &str)]) -> TempDir {
let directory = tempfile::tempdir().unwrap();
for (name, sql) in migrations {
fs::create_dir(directory.path().join(name)).unwrap();
fs::write(directory.path().join(name).join("migration.sql"), sql).unwrap();
}
fs::write(
directory.path().join("migration_lock.toml"),
"provider = \"postgresql\"",
)
.unwrap();
directory
}
#[rstest]
fn folders_apply_in_name_order_numbered_from_one_outside_a_transaction() {
let directory = prisma_folder(&[
("20260305000000_b", "SELECT 2;"),
("20260108_short_prefix", "SELECT 3;"),
("20260305000000_a", "BEGIN; SELECT 1; COMMIT;"),
("20260108000000_first", "SELECT 0;"),
]);
let migrations = prisma_migrations(directory.path()).unwrap();
let applied: Vec<(i64, &str, &str, bool)> = migrations
.iter()
.map(|migration| {
(
migration.version,
migration.description.as_ref(),
migration.sql.as_str(),
migration.no_tx,
)
})
.collect();
assert_eq!(
applied,
vec![
(1, "20260108000000_first", "SELECT 0;", true),
(2, "20260108_short_prefix", "SELECT 3;", true),
(3, "20260305000000_a", "BEGIN; SELECT 1; COMMIT;", true),
(4, "20260305000000_b", "SELECT 2;", true),
]
);
}
#[rstest]
fn a_folder_without_migration_sql_is_an_error() {
let directory = prisma_folder(&[]);
fs::create_dir(directory.path().join("20260101000000_empty")).unwrap();
let error = prisma_migrations(directory.path()).unwrap_err();
assert!(
error.to_string().contains("20260101000000_empty"),
"{error}"
);
}

View file

@ -0,0 +1,24 @@
use std::path::Path;
use litellm_db_testing::{MigratedPostgres, PRISMA_MIGRATIONS_DIR, prisma_migrations};
use rstest::rstest;
#[rstest]
#[tokio::test]
async fn real_prisma_migrations_apply_to_a_fresh_postgres() {
let postgres = MigratedPostgres::start().await.unwrap();
#[expect(
clippy::disallowed_methods,
reason = "the test schema has no offline query data"
)]
let applied: i64 = sqlx::query_scalar("SELECT count(*) FROM _sqlx_migrations WHERE success")
.fetch_one(postgres.pool())
.await
.unwrap();
let expected = prisma_migrations(Path::new(PRISMA_MIGRATIONS_DIR))
.unwrap()
.len();
assert_eq!(applied as usize, expected);
}

View file

@ -0,0 +1,10 @@
[package]
name = "litellm-db"
version = "0.1.0"
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
serde.workspace = true
sqlx.workspace = true

View file

@ -0,0 +1,10 @@
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum DbFailure {
UniqueViolation,
ForeignKeyViolation,
WriteConflict,
NotFound,
}

View file

@ -0,0 +1,3 @@
mod failure;
pub use failure::DbFailure;