diff --git a/Cargo.lock b/Cargo.lock index b629b78..a8417bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,15 @@ version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-compression" version = "0.4.17" @@ -131,19 +140,20 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", "axum-macros", - "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "hyper", + "hyper-util", "itoa", "matchit", "memchr", @@ -155,37 +165,41 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-macros" -version = "0.3.8" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ - "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -212,18 +226,21 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[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 = "brotli" version = "7.0.0" @@ -245,6 +262,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -312,7 +335,7 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn", @@ -330,6 +353,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -339,6 +371,54 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[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 = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -352,6 +432,12 @@ dependencies = [ "termcolor", ] +[[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.9" @@ -482,6 +568,16 @@ dependencies = [ "slab", ] +[[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 = "getrandom" version = "0.2.15" @@ -516,8 +612,11 @@ dependencies = [ "systemd-journal-logger", "tempfile", "tokio", - "tower", + "tower 0.4.13", "tower-http", + "utoipa", + "utoipa-axum", + "utoipa-swagger-ui", ] [[package]] @@ -526,6 +625,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hdrhistogram" version = "7.5.4" @@ -536,12 +641,6 @@ dependencies = [ "num-traits", ] -[[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" @@ -571,6 +670,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -578,7 +688,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[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 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -608,25 +741,47 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.31" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper", + "pin-project-lite", "tokio", "tower-service", - "tracing", - "want", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -636,7 +791,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", + "serde", ] [[package]] @@ -703,6 +869,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" @@ -820,6 +992,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -927,7 +1105,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -959,6 +1137,40 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -971,7 +1183,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -990,6 +1202,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[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" @@ -1056,6 +1277,17 @@ dependencies = [ "serde", ] +[[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 = "shlex" version = "1.3.0" @@ -1071,6 +1303,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -1119,6 +1357,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "systemd-journal-logger" version = "2.2.0" @@ -1171,6 +1415,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +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.40.0" @@ -1234,7 +1493,7 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap", + "indexmap 1.9.3", "pin-project", "pin-project-lite", "rand", @@ -1246,6 +1505,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-http" version = "0.4.4" @@ -1254,12 +1529,12 @@ checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "async-compression", "base64", - "bitflags 2.6.0", + "bitflags", "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "http-range-header", "httpdate", "iri-string", @@ -1269,7 +1544,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -1309,10 +1584,10 @@ dependencies = [ ] [[package]] -name = "try-lock" -version = "0.2.5" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" @@ -1320,18 +1595,105 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utoipa" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9ba0ade4e2f024cd1842dfbaf9dbc540639fc082299acf7649d71bd14eaca3" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-axum" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1370cc4a8eee751c4d2a729566d83d1568212320a20581c7c72c2d76ab80ed37" +dependencies = [ + "axum", + "paste", + "tower-layer", + "tower-service", + "utoipa", +] + +[[package]] +name = "utoipa-gen" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf390d6503c9c9eac988447c38ba934a707b0b768b14511a493b4fc0e8ecb00" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "8.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5c80b4dd79ea382e8374d67dcce22b5c6663fa13a82ad3886441d1bbede5e35" +dependencies = [ + "axum", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "utoipa-swagger-ui-vendored", + "zip", +] + +[[package]] +name = "utoipa-swagger-ui-vendored" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d" + [[package]] name = "uuid" version = "1.11.0" @@ -1342,12 +1704,19 @@ dependencies = [ ] [[package]] -name = "want" -version = "0.3.1" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ - "try-lock", + "same-file", + "winapi-util", ] [[package]] @@ -1468,6 +1837,37 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.6.0", + "memchr", + "thiserror", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index d0f71a4..011a2a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" [dependencies] anyhow = "1.0.82" -axum = { version = "0.6.20", features = ["macros"] } +axum = { version = "0.7.7", features = ["macros"] } clap = { version = "4.4.1", features = ["derive"] } clap-verbosity-flag = "2.2.2" env_logger = "0.10.0" @@ -24,6 +24,9 @@ tempfile = "3.11.0" tokio = { version = "1.32.0", features = ["full"] } tower = { version = "0.4.13", features = ["full"] } tower-http = { version = "0.4.3", features = ["full"] } +utoipa = { version = "5.1.3", features = ["axum_extras"] } +utoipa-axum = "0.1.2" +utoipa-swagger-ui = { version = "8.0.3", features = ["axum", "vendored"] } [profile.release] strip = true diff --git a/src/api.rs b/src/api.rs index 1c16944..f1efe10 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,4 @@ mod base; mod rest_wrapper_v1; -pub use rest_wrapper_v1::rest_api_routes; +pub use rest_wrapper_v1::{rest_api_routes, rest_api_docs}; diff --git a/src/api/rest_wrapper_v1.rs b/src/api/rest_wrapper_v1.rs index 0e178cb..f8f4715 100644 --- a/src/api/rest_wrapper_v1.rs +++ b/src/api/rest_wrapper_v1.rs @@ -8,6 +8,10 @@ use axum::{ use mpvipc_async::Mpv; use serde_json::{json, Value}; +use utoipa::OpenApi; +use utoipa_axum::{router::OpenApiRouter, routes}; +use utoipa_swagger_ui::SwaggerUi; + use super::base; pub fn rest_api_routes(mpv: Mpv) -> Router { @@ -31,6 +35,69 @@ pub fn rest_api_routes(mpv: Mpv) -> Router { .with_state(mpv) } +pub fn rest_api_docs(mpv: Mpv) -> Router { + let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi()) + .routes(routes!(loadfile)) + .routes(routes!(play_get, play_set)) + .routes(routes!(volume_get, volume_set)) + .routes(routes!(time_get, time_set)) + .routes(routes!( + playlist_get, + playlist_remove_or_clear + )) + .routes(routes!(playlist_next)) + .routes(routes!(playlist_previous)) + .routes(routes!(playlist_goto)) + .routes(routes!(playlist_move)) + .routes(routes!(playlist_get_looping, playlist_set_looping)) + .routes(routes!(shuffle)) + .with_state(mpv) + .split_for_parts(); + + router.merge(SwaggerUi::new("/docs").url("/docs/openapi.json", api)) +} + +// NOTE: the openapi stuff is very heavily duplicated and introduces +// a lot of maintenance overhead and boilerplate. It should theoretically +// be possible to infer a lot of this from axum, but I haven't found a +// good library that does this and works properly yet (I have tried some +// but they all had issues). Feel free to replace this with a better solution. + +#[derive(OpenApi)] +#[openapi( + info( + description = "The legacy Grzegorz Brzeczyszczykiewicz API, used to control a running mpv instance", + version = "1.0.0", + ), +)] +struct ApiDoc; + +#[derive(serde::Serialize, utoipa::ToSchema)] +struct EmptySuccessResponse { + success: bool, + error: bool, +} + +#[derive(serde::Serialize, utoipa::ToSchema)] +struct SuccessResponse { + #[schema(example = true)] + success: bool, + #[schema(example = false)] + error: bool, + #[schema(example = json!({ some: "arbitrary json value" }))] + value: Value, +} + +#[derive(serde::Serialize, utoipa::ToSchema)] +struct ErrorResponse { + #[schema(example = "error....")] + error: String, + #[schema(example = "error....")] + errortext: String, + #[schema(example = false)] + success: bool, +} + pub struct RestResponse(anyhow::Result); impl From> for RestResponse { @@ -64,73 +131,178 @@ impl IntoResponse for RestResponse { // TODO: These could possibly be generated with a proc macro -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct LoadFileArgs { path: String, } + +/// Add item to playlist +#[utoipa::path( + post, + path = "/load", + params(LoadFileArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn loadfile(State(mpv): State, Query(query): Query) -> RestResponse { base::loadfile(mpv, &query.path).await.into() } +/// Check whether the player is paused or playing +#[utoipa::path( + get, + path = "/play", + responses( + (status = 200, description = "Success", body = SuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn play_get(State(mpv): State) -> RestResponse { base::play_get(mpv).await.into() } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct PlaySetArgs { play: String, } +/// Set whether the player is paused or playing +#[utoipa::path( + post, + path = "/play", + params(PlaySetArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn play_set(State(mpv): State, Query(query): Query) -> RestResponse { let play = query.play.to_lowercase() == "true"; base::play_set(mpv, play).await.into() } +/// Get the current player volume +#[utoipa::path( + get, + path = "/volume", + responses( + (status = 200, description = "Success", body = SuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn volume_get(State(mpv): State) -> RestResponse { base::volume_get(mpv).await.into() } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct VolumeSetArgs { volume: f64, } +/// Set the player volume +#[utoipa::path( + post, + path = "/volume", + params(VolumeSetArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn volume_set(State(mpv): State, Query(query): Query) -> RestResponse { base::volume_set(mpv, query.volume).await.into() } +/// Get current playback position +#[utoipa::path( + get, + path = "/time", + responses( + (status = 200, description = "Success", body = SuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn time_get(State(mpv): State) -> RestResponse { base::time_get(mpv).await.into() } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct TimeSetArgs { pos: Option, percent: Option, } +/// Set playback position +#[utoipa::path( + post, + path = "/time", + params(TimeSetArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn time_set(State(mpv): State, Query(query): Query) -> RestResponse { base::time_set(mpv, query.pos, query.percent).await.into() } +/// Get the current playlist +#[utoipa::path( + get, + path = "/playlist", + responses( + (status = 200, description = "Success", body = SuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_get(State(mpv): State) -> RestResponse { base::playlist_get(mpv).await.into() } +/// Go to the next item in the playlist +#[utoipa::path( + post, + path = "/playlist/next", + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_next(State(mpv): State) -> RestResponse { base::playlist_next(mpv).await.into() } +/// Go back to the previous item in the playlist +#[utoipa::path( + post, + path = "/playlist/previous", + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_previous(State(mpv): State) -> RestResponse { base::playlist_previous(mpv).await.into() } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct PlaylistGotoArgs { index: usize, } +/// Go to a specific item in the playlist +#[utoipa::path( + post, + path = "/playlist/goto", + params(PlaylistGotoArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_goto( State(mpv): State, Query(query): Query, @@ -138,11 +310,21 @@ async fn playlist_goto( base::playlist_goto(mpv, query.index).await.into() } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct PlaylistRemoveOrClearArgs { index: Option, } +/// Clears a single item or the entire playlist +#[utoipa::path( + delete, + path = "/playlist", + params(PlaylistRemoveOrClearArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_remove_or_clear( State(mpv): State, Query(query): Query, @@ -153,12 +335,22 @@ async fn playlist_remove_or_clear( } } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct PlaylistMoveArgs { index1: usize, index2: usize, } +/// Move a playlist item to a different position +#[utoipa::path( + post, + path = "/playlist/move", + params(PlaylistMoveArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_move( State(mpv): State, Query(query): Query, @@ -168,19 +360,47 @@ async fn playlist_move( .into() } +/// Shuffle the playlist +#[utoipa::path( + post, + path = "/playlist/shuffle", + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn shuffle(State(mpv): State) -> RestResponse { base::shuffle(mpv).await.into() } +/// Check whether the playlist is looping +#[utoipa::path( + get, + path = "/playlist/loop", + responses( + (status = 200, description = "Success", body = SuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_get_looping(State(mpv): State) -> RestResponse { base::playlist_get_looping(mpv).await.into() } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, utoipa::IntoParams)] struct PlaylistSetLoopingArgs { r#loop: bool, } +/// Set whether the playlist should loop +#[utoipa::path( + post, + path = "/playlist/loop", + params(PlaylistSetLoopingArgs), + responses( + (status = 200, description = "Success", body = EmptySuccessResponse), + (status = 500, description = "Internal server error", body = ErrorResponse), + ) +)] async fn playlist_set_looping( State(mpv): State, Query(query): Query, diff --git a/src/main.rs b/src/main.rs index 26468df..8256bf4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use axum::{Router, Server}; +use axum::Router; use clap::Parser; use clap_verbosity_flag::Verbosity; use mpv_setup::{connect_to_mpv, create_mpv_config_file, show_grzegorz_image}; @@ -89,8 +89,12 @@ async fn setup_systemd_watchdog_thread() -> anyhow::Result<()> { async fn shutdown(mpv: Mpv, proc: Option) { log::info!("Shutting down"); - sd_notify::notify(false, &[sd_notify::NotifyState::Stopping]) - .unwrap_or_else(|e| log::warn!("Failed to notify systemd that the service is stopping: {}", e)); + sd_notify::notify(false, &[sd_notify::NotifyState::Stopping]).unwrap_or_else(|e| { + log::warn!( + "Failed to notify systemd that the service is stopping: {}", + e + ) + }); mpv.disconnect() .await @@ -156,11 +160,15 @@ async fn main() -> anyhow::Result<()> { let socket_addr = SocketAddr::new(addr, args.port); log::info!("Starting API on {}", socket_addr); - let app = Router::new().nest("/api", api::rest_api_routes(mpv.clone())); - let server = match Server::try_bind(&socket_addr.clone()) + let app = Router::new() + .nest("/api", api::rest_api_routes(mpv.clone())) + .merge(api::rest_api_docs(mpv.clone())); + + let listener = match tokio::net::TcpListener::bind(&socket_addr) + .await .context(format!("Failed to bind API server to '{}'", &socket_addr)) { - Ok(server) => server, + Ok(listener) => listener, Err(e) => { log::error!("{}", e); shutdown(mpv, proc).await; @@ -191,7 +199,7 @@ async fn main() -> anyhow::Result<()> { log::info!("Received Ctrl-C, exiting"); shutdown(mpv, Some(proc)).await; } - result = server.serve(app.into_make_service()) => { + result = axum::serve(listener, app.into_make_service()) => { log::info!("API server exited"); shutdown(mpv, Some(proc)).await; result?; @@ -203,7 +211,7 @@ async fn main() -> anyhow::Result<()> { log::info!("Received Ctrl-C, exiting"); shutdown(mpv.clone(), None).await; } - result = server.serve(app.into_make_service()) => { + result = axum::serve(listener, app.into_make_service()) => { log::info!("API server exited"); shutdown(mpv.clone(), None).await; result?;