diff --git a/Cargo.lock b/Cargo.lock index 70b752fd..5b7341bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -49,7 +49,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", ] @@ -70,9 +70,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -157,12 +157,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.8" @@ -405,13 +399,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -504,9 +498,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "base64ct" @@ -534,16 +528,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.5" +version = "1.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" dependencies = [ - "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", ] [[package]] @@ -566,9 +559,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" dependencies = [ "async-channel 2.5.0", "async-task", @@ -579,9 +572,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ "memchr", "regex-automata", @@ -634,9 +627,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.4.0" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "shlex", @@ -650,12 +643,12 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "rand_core 0.10.1", ] @@ -712,9 +705,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -732,9 +725,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -745,9 +738,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.8" +version = "4.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f84a88507dbd05c695f2cb5e8558e747179134005e9893882dec964190ed89" +checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19" dependencies = [ "clap", ] @@ -761,7 +754,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -772,9 +765,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clap_mangen" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82842b45bf9f6a3be090dd860095ac30728042c08e0d6261ca7259b5d850f07" +checksum = "211d617eaa4b735c96c9e0228fcbdb5120ef623f2b8cb67ffb84c3e02dbc28a4" dependencies = [ "clap", "roff", @@ -800,9 +793,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "futures-core", @@ -876,9 +869,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" dependencies = [ "percent-encoding", "time", @@ -967,7 +960,7 @@ dependencies = [ "subtle", "swagger-ui-redist", "tempfile", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", "tokio", "toml", @@ -995,7 +988,7 @@ dependencies = [ "cot", "cot-cli", "cot_codegen", - "darling 0.24.0", + "darling 0.24.1", "glob", "heck", "hex", @@ -1006,7 +999,7 @@ dependencies = [ "proc-macro2", "quote", "rand 0.10.2", - "syn 3.0.3", + "syn 3.0.4", "tempfile", "tracing", "tracing-subscriber", @@ -1022,18 +1015,20 @@ dependencies = [ "cot-cli", "glob", "libtest-mimic", - "thiserror 2.0.19", + "schemars", + "serde_json", + "thiserror 2.0.20", ] [[package]] name = "cot_codegen" version = "0.7.0" dependencies = [ - "darling 0.24.0", + "darling 0.24.1", "heck", "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", "tracing", ] @@ -1064,7 +1059,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "sync_wrapper", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tower", "tower-sessions", @@ -1077,13 +1072,13 @@ dependencies = [ "askama_derive", "cot", "cot_codegen", - "darling 0.24.0", + "darling 0.24.1", "heck", "proc-macro-crate", "proc-macro2", "quote", "rustversion", - "syn 3.0.3", + "syn 3.0.4", "trybuild", ] @@ -1098,9 +1093,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -1236,12 +1231,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "darling_core 0.24.0", - "darling_macro 0.24.0", + "darling_core 0.24.1", + "darling_macro 0.24.1", ] [[package]] @@ -1260,15 +1255,15 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1284,31 +1279,30 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ - "darling_core 0.24.0", + "darling_core 0.24.1", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] name = "deadpool" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883466cb8db62725aee5f4a6011e8a5d42912b42632df32aad57fc91127c6e04" +checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e" dependencies = [ "deadpool-runtime", - "num_cpus", "tokio", ] [[package]] name = "deadpool-redis" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafa30c49dafe086d10116074e422ad7fc1c3cf554697e744a3ab112599ebd09" +checksum = "243852fff546d8c5aac6ceaac7587f7137915b102c3fbec6256a64ce52452bd2" dependencies = [ "deadpool", "redis", @@ -1428,7 +1422,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1469,9 +1463,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" dependencies = [ "serde", ] @@ -1482,7 +1476,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd" dependencies = [ - "base64 0.23.0", + "base64 0.23.1", "memchr", ] @@ -1602,7 +1596,7 @@ dependencies = [ name = "example-file-upload" version = "0.1.0" dependencies = [ - "base64 0.23.0", + "base64 0.23.1", "cot", ] @@ -1664,7 +1658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7737298823a6f9ca743e372e8cb03658d55354fbab843424f575706ba9563046" dependencies = [ "base64 0.22.1", - "cookie 0.18.1", + "cookie 0.18.2", "http 1.5.0", "http-body-util", "hyper", @@ -1688,9 +1682,9 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "finl_unicode" @@ -1777,9 +1771,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1791,9 +1785,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1801,15 +1795,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1829,9 +1823,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-lite" @@ -1848,32 +1842,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-io", @@ -2094,9 +2088,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -2134,9 +2128,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -2230,29 +2224,29 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", "utf8_iter", "yoke 0.8.3", "zerofrom", - "zerovec 0.11.6", + "zerovec 0.11.8", ] [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", - "litemap 0.8.2", - "tinystr 0.8.3", - "writeable 0.6.3", - "zerovec 0.11.6", + "litemap 0.8.3", + "tinystr 0.8.4", + "writeable 0.6.4", + "zerovec 0.11.8", ] [[package]] @@ -2269,43 +2263,44 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "icu_collections 2.2.0", + "icu_collections 2.3.0", "icu_normalizer_data", "icu_properties", - "icu_provider 2.2.0", + "icu_provider 2.3.1", "smallvec", - "zerovec 0.11.6", + "zerovec 0.11.8", ] [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ - "icu_collections 2.2.0", + "displaydoc", + "icu_collections 2.3.0", "icu_locale_core", "icu_properties_data", - "icu_provider 2.2.0", + "icu_provider 2.3.1", "zerotrie", - "zerovec 0.11.6", + "zerovec 0.11.8", ] [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" @@ -2326,17 +2321,17 @@ dependencies = [ [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", - "writeable 0.6.3", + "writeable 0.6.4", "yoke 0.8.3", "zerofrom", "zerotrie", - "zerovec 0.11.6", + "zerovec 0.11.8", ] [[package]] @@ -2401,9 +2396,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2439,9 +2434,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "is_terminal_polyfill" @@ -2482,7 +2477,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -2521,9 +2516,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -2556,13 +2551,13 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lettre" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349" +checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae" dependencies = [ "async-std", "async-trait", - "base64 0.22.1", + "base64 0.23.1", "email-encoding", "email_address", "fastrand", @@ -2631,9 +2626,9 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lock_api" @@ -2646,9 +2641,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" dependencies = [ "value-bag", ] @@ -2813,16 +2808,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.37.3" @@ -3018,7 +3003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.7", + "rand 0.8.8", ] [[package]] @@ -3096,9 +3081,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "plotters" @@ -3144,11 +3129,11 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ - "zerovec 0.11.6", + "zerovec 0.11.8", ] [[package]] @@ -3200,7 +3185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" dependencies = [ "proc-macro2", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3250,9 +3235,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "rand_core 0.6.4", ] @@ -3334,9 +3319,9 @@ dependencies = [ [[package]] name = "redis" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3257df217f7eab0044627a268c9cc6cdb60c0c421c88f83ac41c4e31520b6b84" +checksum = "e37a4ca5c6ca42aa3e6df2fd32b987a65d32a4c2159a6f3fe0fd1df306a2658f" dependencies = [ "arcstr", "async-lock", @@ -3366,22 +3351,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3398,9 +3383,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -3563,9 +3548,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -3625,7 +3610,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3636,9 +3621,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sea-query" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d190cfb3bcceb8a8d7d04dee5a0c77f60c7627979cdcb47fdcb8934f009badf" +checksum = "546040c653a705e60ec65ecd3191a809603734bebbc225775916dea9ae409b31" dependencies = [ "chrono", ] @@ -3724,7 +3709,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3735,7 +3720,7 @@ checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3755,6 +3740,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ + "indexmap", "itoa", "memchr", "serde", @@ -3801,7 +3787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -3823,7 +3809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -3955,7 +3941,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tokio-stream", "tracing", @@ -4023,7 +4009,7 @@ dependencies = [ "sha1", "sha2 0.11.0", "sqlx-core", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", ] @@ -4058,7 +4044,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", "whoami", ] @@ -4083,7 +4069,7 @@ dependencies = [ "percent-encoding", "serde", "sqlx-core", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", "url", ] @@ -4149,9 +4135,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -4233,11 +4219,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -4253,13 +4239,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4312,12 +4298,12 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", - "zerovec 0.11.6", + "zerovec 0.11.8", ] [[package]] @@ -4369,7 +4355,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4490,7 +4476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36" dependencies = [ "axum-core", - "cookie 0.18.1", + "cookie 0.18.2", "futures-util", "http 1.5.0", "parking_lot", @@ -4575,7 +4561,7 @@ dependencies = [ "rand 0.9.5", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", "tokio", "tracing", @@ -4684,9 +4670,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.118" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06649c6f63d86604ba0c8950d5a1829fc9a17afd70fc6629f481d75b6a624c78" +checksum = "1e605bf6b39357663d8ba4e984f8be8da8df6bb32e81031d6889024ea8fd68e4" dependencies = [ "dissimilar", "glob", @@ -4864,9 +4850,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -4877,9 +4863,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -4887,9 +4873,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4897,9 +4883,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -4910,18 +4896,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -4958,9 +4944,9 @@ dependencies = [ [[package]] name = "whoami" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "winapi" @@ -5157,9 +5143,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "xxhash-rust" @@ -5216,18 +5202,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -5263,9 +5249,9 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke 0.8.3", @@ -5280,25 +5266,25 @@ checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke 0.7.5", "zerofrom", - "zerovec-derive 0.10.3", + "zerovec-derive 0.10.4", ] [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke 0.8.3", "zerofrom", - "zerovec-derive 0.11.3", + "zerovec-derive 0.11.6", ] [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "3e3c6377872d72510393f688a555d7097b0f741995c7a00f0407f786dd486b2d" dependencies = [ "proc-macro2", "quote", @@ -5307,13 +5293,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] diff --git a/cot-test/Cargo.toml b/cot-test/Cargo.toml index e8ceebcb..a70c1bd7 100644 --- a/cot-test/Cargo.toml +++ b/cot-test/Cargo.toml @@ -18,8 +18,14 @@ cot-cli = { workspace = true, features = ["test_utils"] } cot.workspace = true glob.workspace = true libtest-mimic.workspace = true +schemars = { workspace = true, features = ["std"] } +serde_json.workspace = true thiserror.workspace = true +[features] +# Enables generating the configuration file reference (docs/configuration.md). +config-docs = ["cot/full", "cot/_internal_config-docs"] + [lints] workspace = true @@ -27,3 +33,13 @@ workspace = true name = "doc_code_blocks" path = "tests/doc_code_blocks.rs" harness = false + +[[test]] +name = "config_reference" +path = "tests/config_reference.rs" +required-features = ["config-docs"] + +[[bin]] +name = "generate_config_docs" +path = "src/bin/generate_config_docs.rs" +required-features = ["config-docs"] diff --git a/cot-test/src/bin/generate_config_docs.rs b/cot-test/src/bin/generate_config_docs.rs new file mode 100644 index 00000000..02da03bf --- /dev/null +++ b/cot-test/src/bin/generate_config_docs.rs @@ -0,0 +1,24 @@ +//! Regenerates `docs/configuration.md` from `cot::config::ProjectConfig`'s type +//! definition. +//! +//! Run via `just generate-config-docs`. + +use std::path::PathBuf; + +fn main() { + let content = cot_test::config_reference::generate_config_reference(); + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let docs_path = manifest_dir + .parent() + .expect("failed to get workspace path") + .join("docs") + .join("configuration.md"); + + std::fs::write(&docs_path, content).expect("failed to write docs/configuration.md"); + println!("Wrote {}", docs_path.display()); +} + +// TODO: make table nicer +// TODO: better defaults for enums +// TODO: links for "see below" diff --git a/cot-test/src/config_reference.rs b/cot-test/src/config_reference.rs new file mode 100644 index 00000000..36d0ecd0 --- /dev/null +++ b/cot-test/src/config_reference.rs @@ -0,0 +1,462 @@ +//! Generates the configuration file reference (`docs/configuration.md`) from +//! `cot::config::ProjectConfig`'s JSON schema (via `schemars`), so the +//! reference can never drift from the actual set of tables/keys the type +//! accepts: every field is picked up automatically because the schema is +//! derived directly from the struct/enum definitions, not hand-copied. + +use std::fmt::Write as _; + +use serde_json::{Map, Value}; + +/// Generates the Markdown configuration reference. +/// +/// # Panics +/// +/// Panics if `cot::config::ProjectConfig`'s JSON schema doesn't have the shape +/// this generator expects (e.g. missing `properties`) - this would indicate a +/// schemars version change or a fundamentally different config type shape that +/// the generator needs to be updated for. +#[must_use] +pub fn generate_config_reference() -> String { + let schema = schemars::schema_for!(cot::config::ProjectConfig); + let root = schema + .as_value() + .as_object() + .expect("root schema must be a JSON object"); + let defs: Map = root + .get("$defs") + .and_then(Value::as_object) + .cloned() + .unwrap_or_default(); + let properties = root + .get("properties") + .and_then(Value::as_object) + .expect("ProjectConfig schema must declare properties"); + + let mut md = String::new(); + md.push_str("---\ntitle: Configuration\n---\n\n"); + md.push_str( + "\n\n", + ); + if let Some(desc) = root.get("description").and_then(Value::as_str) { + md.push_str(&first_paragraph(desc)); + md.push_str("\n\n"); + } + md.push_str( + "Cot projects are configured via a TOML file (typically `config/dev.toml` and \ + `config/prod.toml`, loaded with\n\ + [`ProjectConfig::from_toml`](https://docs.rs/cot/latest/cot/config/struct.ProjectConfig.html#method.from_toml)).\n\ + This page lists every table and key that `ProjectConfig` understands.\n\n", + ); + md.push_str( + "Any top-level table not listed below is preserved as-is and made available to your \ + application through `ProjectConfig::extra`, for app-specific configuration.\n\n", + ); + + md.push_str("## Top-level keys\n\n"); + let mut default_toml = String::new(); + render_fields(&[], 1, properties, &defs, &mut md, &mut default_toml); + + md.push_str("## Full default configuration\n\n"); + md.push_str( + "This is a complete example with every key set explicitly to its default value \ + (fields without a well-defined default, like `secret_key`, are omitted):\n\n", + ); + md.push_str("```toml\n"); + md.push_str(default_toml.trim()); + md.push('\n'); + md.push_str("```\n"); + + md +} + +enum PendingChild<'a> { + Table(&'a Map), + Tagged(&'a Vec, Option<&'a Value>), +} + +/// Renders a field table (`| Key | Type | Default | Description |`) for the +/// given `properties` into `md`, followed by a section (and, recursively, its +/// own field table) for every property that turns out to be a nested table. +/// Each table's own scalar defaults are written to `default_toml` under a +/// `[path]` header - but only if it actually has any, so a table that only +/// contains further nested tables (e.g. `middlewares`) doesn't get an empty, +/// redundant header. +fn render_fields( + path: &[String], + level: usize, + properties: &Map, + defs: &Map, + md: &mut String, + default_toml: &mut String, +) { + md.push_str("| Key | Type | Default | Description |\n|---|---|---|---|\n"); + let mut own_toml = String::new(); + let mut pending: Vec<(String, PendingChild<'_>)> = Vec::new(); + + for (key, prop_value) in properties { + let prop = prop_value + .as_object() + .expect("property schema must be an object"); + let description = escape_table_cell(&first_paragraph( + prop.get("description") + .and_then(Value::as_str) + .unwrap_or(""), + )); + let default_val = prop.get("default"); + let resolved = deref(prop_value, defs); + + match classify(resolved) { + Kind::Table(props) => { + let _ = writeln!(md, "| `{key}` | table | *(see below)* | {description} |"); + pending.push((key.clone(), PendingChild::Table(props))); + } + Kind::TaggedTable(variants) => { + let _ = writeln!(md, "| `{key}` | table | *(see below)* | {description} |"); + pending.push((key.clone(), PendingChild::Tagged(variants, default_val))); + } + Kind::LeafEnum(variants) => { + let ty = leaf_enum_type_name(variants); + let _ = writeln!( + md, + "| `{key}` | {ty} | {} | {description} |", + default_cell(default_val, ScalarKind::String) + ); + if let Some(line) = default_line(key, default_val, ScalarKind::String) { + own_toml.push_str(&line); + } + } + Kind::Scalar(kind) => { + let ty = scalar_type_name(kind); + let _ = writeln!( + md, + "| `{key}` | {ty} | {} | {description} |", + default_cell(default_val, kind) + ); + if let Some(line) = default_line(key, default_val, kind) { + own_toml.push_str(&line); + } + } + } + } + md.push('\n'); + + if !own_toml.is_empty() { + if !path.is_empty() { + let _ = writeln!(default_toml, "\n[{}]", path.join(".")); + } + default_toml.push_str(&own_toml); + } + + for (key, child) in pending { + let child_path = extend(path, &key); + match child { + PendingChild::Table(props) => { + render_object(&child_path, level + 1, props, defs, md, default_toml); + } + PendingChild::Tagged(variants, default_val) => { + render_tagged( + &child_path, + level + 1, + variants, + default_val, + defs, + md, + default_toml, + ); + } + } + } +} + +/// Renders a `## [path]` (or deeper) section for a plain nested table. +fn render_object( + path: &[String], + level: usize, + properties: &Map, + defs: &Map, + md: &mut String, + default_toml: &mut String, +) { + let _ = writeln!(md, "{} `[{}]`\n", heading_hashes(level), path.join(".")); + render_fields(path, level, properties, defs, md, default_toml); +} + +/// Renders a `## [path]` section for an internally-tagged enum (selected via a +/// `type` key), with one sub-section per variant. +fn render_tagged( + path: &[String], + level: usize, + variants: &[Value], + default_val: Option<&Value>, + defs: &Map, + md: &mut String, + default_toml: &mut String, +) { + let _ = writeln!(md, "{} `[{}]`\n", heading_hashes(level), path.join(".")); + md.push_str("Select the variant with the `type` key:\n\n"); + + for variant in variants { + let variant = variant + .as_object() + .expect("tagged enum variant schema must be an object"); + let type_const = variant + .get("properties") + .and_then(|p| p.get("type")) + .and_then(|t| t.get("const")) + .and_then(Value::as_str) + .expect("tagged enum variant must declare a `type` const"); + let _ = writeln!( + md, + "{} `type = \"{type_const}\"`\n", + heading_hashes(level + 1) + ); + if let Some(desc) = variant.get("description").and_then(Value::as_str) { + md.push_str(&first_paragraph(desc)); + md.push_str("\n\n"); + } + + let other_props: Vec<(&String, &Value)> = variant + .get("properties") + .and_then(Value::as_object) + .into_iter() + .flatten() + .filter(|(k, _)| k.as_str() != "type") + .collect(); + if other_props.is_empty() { + continue; + } + md.push_str("| Key | Type | Default | Description |\n|---|---|---|---|\n"); + for (key, prop_value) in other_props { + let prop = prop_value + .as_object() + .expect("property schema must be an object"); + let description = escape_table_cell(&first_paragraph( + prop.get("description") + .and_then(Value::as_str) + .unwrap_or(""), + )); + let default_val = prop.get("default"); + let resolved = deref(prop_value, defs); + match classify(resolved) { + Kind::Scalar(kind) => { + let ty = scalar_type_name(kind); + let _ = writeln!( + md, + "| `{key}` | {ty} | {} | {description} |", + default_cell(default_val, kind) + ); + } + Kind::LeafEnum(variants) => { + let ty = leaf_enum_type_name(variants); + let _ = writeln!( + md, + "| `{key}` | {ty} | {} | {description} |", + default_cell(default_val, ScalarKind::String) + ); + } + // Not exercised by the current config tree (no tagged-enum variant has a + // nested-table field), but handled so the generator degrades gracefully + // rather than panicking if one is ever added. + Kind::Table(_) | Kind::TaggedTable(_) => { + let _ = writeln!( + md, + "| `{key}` | table | *(see type documentation)* | {description} |" + ); + } + } + } + md.push('\n'); + } + + let mut own_toml = String::new(); + if let Some(Value::Object(default_obj)) = default_val { + for (key, value) in default_obj { + if let Some(line) = json_scalar_to_toml_line(key, value) { + own_toml.push_str(&line); + } + } + } + if !own_toml.is_empty() { + let _ = writeln!(default_toml, "\n[{}]", path.join(".")); + default_toml.push_str(&own_toml); + } +} + +enum Kind<'a> { + Table(&'a Map), + TaggedTable(&'a Vec), + LeafEnum(&'a Vec), + Scalar(ScalarKind), +} + +#[derive(Clone, Copy)] +enum ScalarKind { + Bool, + Integer, + String, + Array, +} + +/// Classifies an already-dereferenced schema node. +fn classify(resolved: &Value) -> Kind<'_> { + let Some(obj) = resolved.as_object() else { + return Kind::Scalar(ScalarKind::String); + }; + if let Some(Value::Array(one_of)) = obj.get("oneOf") { + let tagged = !one_of.is_empty() && one_of.iter().all(|v| v.get("properties").is_some()); + return if tagged { + Kind::TaggedTable(one_of) + } else { + Kind::LeafEnum(one_of) + }; + } + if let Some(Value::Object(props)) = obj.get("properties") { + return Kind::Table(props); + } + let ty_str = match obj.get("type") { + Some(Value::String(s)) => s.as_str(), + Some(Value::Array(arr)) => arr + .iter() + .filter_map(Value::as_str) + .find(|s| *s != "null") + .unwrap_or("string"), + _ => "string", + }; + Kind::Scalar(match ty_str { + "boolean" => ScalarKind::Bool, + "integer" | "number" => ScalarKind::Integer, + "array" => ScalarKind::Array, + _ => ScalarKind::String, + }) +} + +/// Follows a `$ref` (one level - this schema never nests them further) or picks +/// the non-null branch of an `anyOf` (produced by `Option` fields), +/// returning the schema node that actually describes the field's shape. +fn deref<'a>(prop: &'a Value, defs: &'a Map) -> &'a Value { + let Some(obj) = prop.as_object() else { + return prop; + }; + if let Some(Value::String(r)) = obj.get("$ref") { + let name = r.rsplit('/').next().unwrap_or(r); + if let Some(target) = defs.get(name) { + return target; + } + } + if let Some(Value::Array(any_of)) = obj.get("anyOf") { + for branch in any_of { + if branch.get("type").and_then(Value::as_str) != Some("null") { + return deref(branch, defs); + } + } + } + prop +} + +fn scalar_type_name(kind: ScalarKind) -> &'static str { + match kind { + ScalarKind::Bool => "boolean", + ScalarKind::Integer => "integer", + ScalarKind::String => "string", + ScalarKind::Array => "array of strings", + } +} + +fn leaf_enum_type_name(variants: &[Value]) -> String { + let values: Vec = variants + .iter() + .filter_map(|v| v.get("const").and_then(Value::as_str)) + .map(|s| format!("`\"{s}\"`")) + .collect(); + values.join(", ") +} + +fn default_cell(default_val: Option<&Value>, kind: ScalarKind) -> String { + match json_scalar_repr(default_val, kind) { + Some(s) => format!("`{s}`"), + None => "—".to_string(), + } +} + +fn default_line(key: &str, default_val: Option<&Value>, kind: ScalarKind) -> Option { + json_scalar_repr(default_val, kind).map(|v| format!("{key} = {v}\n")) +} + +/// Renders a schema `default` value as a TOML-literal string, but only when it +/// actually matches the field's declared scalar type. A couple of fields +/// (`secret_key`, `fallback_secret_keys`) wrap [`cot::config::SecretKey`], +/// whose real `Serialize` impl emits a byte array rather than the string this +/// schema declares (see the `schemars(with = "String")` override on that type), +/// so schemars' computed default for them doesn't match the declared type. +/// Rather than print a misleading example, such mismatches are treated as "no +/// representable default" and omitted. +fn json_scalar_repr(default_val: Option<&Value>, kind: ScalarKind) -> Option { + match (kind, default_val?) { + (ScalarKind::Bool, Value::Bool(b)) => Some(b.to_string()), + (ScalarKind::Integer, Value::Number(n)) => Some(n.to_string()), + (ScalarKind::String, Value::String(s)) => Some(toml_quote(s)), + (ScalarKind::Array, Value::Array(items)) => { + let mut rendered = Vec::with_capacity(items.len()); + for item in items { + match item { + Value::String(s) => rendered.push(toml_quote(s)), + _ => return None, + } + } + Some(format!("[{}]", rendered.join(", "))) + } + _ => None, + } +} + +fn json_scalar_to_toml_line(key: &str, value: &Value) -> Option { + match value { + Value::Bool(b) => Some(format!("{key} = {b}\n")), + Value::Number(n) => Some(format!("{key} = {n}\n")), + Value::String(s) => Some(format!("{key} = {}\n", toml_quote(s))), + _ => None, + } +} + +fn toml_quote(s: &str) -> String { + format!("{s:?}") +} + +fn heading_hashes(level: usize) -> String { + "#".repeat(level.clamp(2, 6)) +} + +fn extend(path: &[String], key: &str) -> Vec { + let mut v = path.to_vec(); + v.push(key.to_string()); + v +} + +fn escape_table_cell(s: &str) -> String { + s.replace('|', "\\|") +} + +/// Extracts the first paragraph of a rustdoc description, stopping at the first +/// blank line, heading (`#`), or code fence (some doc comments in `config.rs` +/// are missing the blank line before `# Examples`, so a heading/fence also ends +/// the paragraph). Multiple lines within the paragraph are joined with spaces +/// so they read as flowing prose in a table cell. +fn first_paragraph(desc: &str) -> String { + let mut lines = Vec::new(); + for line in desc.lines() { + let trimmed = line.trim(); + if trimmed.starts_with('#') || trimmed.starts_with("```") { + break; + } + if trimmed.is_empty() { + if lines.is_empty() { + continue; + } + break; + } + lines.push(trimmed); + } + lines.join(" ") +} diff --git a/cot-test/src/lib.rs b/cot-test/src/lib.rs index 7224b240..d09783aa 100644 --- a/cot-test/src/lib.rs +++ b/cot-test/src/lib.rs @@ -6,6 +6,9 @@ use cot_cli::new_project::{CotSource, new_project}; use libtest_mimic::Failed; use thiserror::Error; +#[cfg(feature = "config-docs")] +pub mod config_reference; + pub const COMMON_IMPORTS: &[&str] = &[ "cot::db::*", "cot::request::extractors::*", diff --git a/cot-test/tests/config_reference.rs b/cot-test/tests/config_reference.rs new file mode 100644 index 00000000..5eb82184 --- /dev/null +++ b/cot-test/tests/config_reference.rs @@ -0,0 +1,25 @@ +//! Verifies that `docs/configuration.md` is up to date with +//! `cot::config::ProjectConfig`'s current type definition. If this fails, run +//! `just generate-config-docs` and commit the result. + +use std::path::PathBuf; + +#[test] +fn config_reference_is_up_to_date() { + let generated = cot_test::config_reference::generate_config_reference(); + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let docs_path = manifest_dir + .parent() + .expect("failed to get workspace path") + .join("docs") + .join("configuration.md"); + let committed = std::fs::read_to_string(&docs_path) + .unwrap_or_else(|e| panic!("failed to read {}: {e}", docs_path.display())); + + assert_eq!( + generated, committed, + "docs/configuration.md is out of date with cot::config::ProjectConfig; \ + run `just generate-config-docs` and commit the result" + ); +} diff --git a/cot/Cargo.toml b/cot/Cargo.toml index 345dac81..baeccc85 100644 --- a/cot/Cargo.toml +++ b/cot/Cargo.toml @@ -119,6 +119,8 @@ swagger-ui = ["openapi", "dep:swagger-ui-redist"] live-reload = ["dep:tower-livereload"] cache = ["json"] test = [] +# Internal feature used to generate the configuration file reference (docs/configuration.md). +_internal_config-docs = ["dep:schemars", "schemars/std", "schemars/preserve_order", "full"] [lib] bench = false diff --git a/cot/src/config.rs b/cot/src/config.rs index 24260d12..2ebe2d9c 100644 --- a/cot/src/config.rs +++ b/cot/src/config.rs @@ -35,6 +35,7 @@ use crate::utils::chrono::DateTimeWithOffsetAdapter; /// This is all the project-specific configuration data that can (and makes /// sense to) be expressed in a TOML configuration file. #[derive(Debug, Clone, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] @@ -150,7 +151,7 @@ pub struct ProjectConfig { /// # Ok::<(), cot::Error>(()) /// ``` pub auth_backend: AuthBackendConfig, - /// Configuration related to the database. + /// Database configuration. /// /// # Examples /// @@ -172,7 +173,7 @@ pub struct ProjectConfig { /// ``` #[cfg(feature = "db")] pub database: DatabaseConfig, - /// Configuration related to the cache. + /// Cache subsystem configuration. /// /// # Examples /// @@ -204,7 +205,7 @@ pub struct ProjectConfig { /// ``` #[cfg(feature = "cache")] pub cache: CacheConfig, - /// Configuration related to the static files. + /// Static files configuration. /// /// # Examples /// @@ -234,7 +235,7 @@ pub struct ProjectConfig { /// # Ok::<(), cot::Error>(()) /// ``` pub static_files: StaticFilesConfig, - /// Configuration related to the middlewares. + /// Middleware configuration. /// /// # Examples /// @@ -252,7 +253,7 @@ pub struct ProjectConfig { /// # Ok::<(), cot::Error>(()) /// ``` pub middlewares: MiddlewareConfig, - /// Configuration related to the email backend. + /// Email backend configuration. /// /// # Examples /// @@ -304,6 +305,7 @@ pub struct ProjectConfig { /// # Ok::<(), Box>(()) /// ``` #[serde(flatten)] + #[cfg_attr(feature = "_internal_config-docs", schemars(skip))] pub extra: toml::Table, } @@ -430,6 +432,7 @@ impl ProjectConfigBuilder { /// let config = AuthBackendConfig::Database; /// ``` #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(tag = "type", rename_all = "snake_case")] #[non_exhaustive] pub enum AuthBackendConfig { @@ -461,6 +464,7 @@ pub enum AuthBackendConfig { /// ``` #[cfg(feature = "db")] #[derive(Debug, Default, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] @@ -605,6 +609,7 @@ const MAX_RETRIES_DEFAULT: u32 = 3; #[cfg(feature = "cache")] #[derive(Debug, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] @@ -674,6 +679,8 @@ pub struct CacheConfig { /// timeout = "2h" /// ``` #[serde(with = "crate::serializers::cache_timeout")] + #[cfg_attr(feature = "_internal_config-docs", schemars(with = "Option"))] + // TODO: Option is wrong pub timeout: Timeout, /// Prefix for cache keys. @@ -792,6 +799,7 @@ impl CacheConfig { #[cfg(feature = "cache")] #[derive(Debug, Default, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] /// Configuration for the cache store backend. @@ -903,6 +911,7 @@ const fn is_default_redis_pool_size(size: &usize) -> bool { /// assert_eq!(mem, CacheStoreTypeConfig::Memory); /// ``` #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(tag = "type", rename_all = "snake_case")] #[non_exhaustive] #[cfg(feature = "cache")] @@ -927,6 +936,8 @@ pub enum CacheStoreTypeConfig { /// must be specified, and additional Redis-specific options can be /// configured. Redis { + /// The URL of the Redis server. + /// /// # Examples /// /// ``` @@ -937,7 +948,6 @@ pub enum CacheStoreTypeConfig { /// pool_size: 20, /// }; /// ``` - /// The URL of the Redis server. url: CacheUrl, /// Connection pool size for Redis connections. @@ -954,6 +964,8 @@ pub enum CacheStoreTypeConfig { /// This stores cache data in files on the local filesystem. The path to /// the directory where the cache files will be stored must be specified. File { + /// The path to the directory where cache files will be stored. + /// /// # Examples /// /// ``` @@ -965,7 +977,6 @@ pub enum CacheStoreTypeConfig { /// path: PathBuf::from("/tmp/cache"), /// }; /// ``` - /// The path to the directory where cache files will be stored. path: PathBuf, }, } @@ -1013,18 +1024,21 @@ pub enum CacheStoreTypeConfig { /// .build(); /// ``` #[derive(Debug, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] pub struct StaticFilesConfig { /// The URL prefix for the static files to be served at (which should - /// typically end with a slash). The default is `/static/`. + /// typically end with a slash). /// /// This prefix is used to determine which requests should be handled by the /// static files middleware. For example, if set to `/assets/`, then /// requests to `/assets/style.css` will be served from the static files /// directory. /// + /// The default is `/static/`. + /// /// # Examples /// /// ``` @@ -1104,6 +1118,7 @@ pub struct StaticFilesConfig { /// # Ok::<(), cot::Error>(()) /// ``` #[serde(with = "crate::serializers::humantime")] + #[cfg_attr(feature = "_internal_config-docs", schemars(with = "Option"))] #[builder(setter(strip_option), default)] pub cache_timeout: Option, } @@ -1112,6 +1127,7 @@ pub struct StaticFilesConfig { /// /// This is used as part of the [`StaticFilesConfig`] struct. #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] #[non_exhaustive] pub enum StaticFilesPathRewriteMode { @@ -1193,6 +1209,7 @@ impl StaticFilesConfig { /// .build(); /// ``` #[derive(Debug, Default, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] @@ -1254,6 +1271,7 @@ impl MiddlewareConfigBuilder { /// let config = LiveReloadMiddlewareConfig::builder().enabled(true).build(); /// ``` #[derive(Debug, Default, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] @@ -1333,6 +1351,7 @@ impl LiveReloadMiddlewareConfigBuilder { /// }; /// ``` #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case", tag = "type")] pub enum SessionStoreTypeConfig { /// In-memory session storage. @@ -1413,6 +1432,7 @@ pub enum SessionStoreTypeConfig { /// ``` #[derive(Debug, Default, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] pub struct SessionStoreConfig { @@ -1489,6 +1509,7 @@ impl SessionStoreConfigBuilder { /// /// [`SameSite`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#controlling_third-party_cookies_with_samesite #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] #[non_exhaustive] pub enum SameSite { @@ -1599,14 +1620,15 @@ impl From for tower_sessions::Expiry { /// let config = SessionMiddlewareConfig::builder().secure(false).build(); /// ``` #[derive(Debug, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] pub struct SessionMiddlewareConfig { - /// The [`Secure`] of the cookie determines whether the session middleware - /// is secure. + /// The + /// [`Secure`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies) + /// of the cookie determines whether the session middleware is secure. /// - /// [`Secure`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies /// # Examples /// /// ``` @@ -1615,10 +1637,9 @@ pub struct SessionMiddlewareConfig { /// let config = SessionMiddlewareConfig::builder().secure(false).build(); /// ``` pub secure: bool, - /// The [`HttpOnly`] of the cookie used for the session. It is set to `true` - /// by default. - /// - /// [`HttpOnly`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies + /// The + /// [`HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies) + /// of the cookie used for the session. It is set to `true` by default. /// /// # Examples /// @@ -1628,10 +1649,11 @@ pub struct SessionMiddlewareConfig { /// let config = SessionMiddlewareConfig::builder().http_only(true).build(); /// ``` pub http_only: bool, - /// The [`SameSite`] attribute of the cookie used for the session. - /// The default value is [`SameSite::Strict`] + /// The + /// [`SameSite`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#controlling_third-party_cookies_with_samesite) + /// attribute of the cookie used for the session. /// - /// [`SameSite`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#controlling_third-party_cookies_with_samesite + /// The default value is [`SameSite::Strict`]. /// /// # Examples /// @@ -1644,10 +1666,11 @@ pub struct SessionMiddlewareConfig { /// ``` pub same_site: SameSite, - /// The [`Domain`] attribute of the cookie used for the session. When not - /// explicitly configured, it is set to `None` by default. + /// The + /// [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#define_where_cookies_are_sent) + /// attribute of the cookie used for the session. /// - /// [`Domain`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#define_where_cookies_are_sent + /// When not explicitly configured, it is set to [`None`] by default. /// /// # Examples /// @@ -1660,10 +1683,11 @@ pub struct SessionMiddlewareConfig { /// ``` #[builder(setter(strip_option), default)] pub domain: Option, - /// The [`Path`] attribute of the cookie used for the session. It is set to - /// `/` by default. + /// The + /// [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#define_where_cookies_are_sent) + /// attribute of the cookie used for the session. /// - /// [`Path`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#define_where_cookies_are_sent + /// It is set to `/` by default. /// /// # Examples /// @@ -1677,8 +1701,9 @@ pub struct SessionMiddlewareConfig { /// .build(); /// ``` pub path: String, - /// The name of the cookie used for the session. It is set to "id" by - /// default. + /// The name of the cookie used for the session. + /// + /// It is set to "id" by default. /// /// # Examples /// @@ -1692,7 +1717,9 @@ pub struct SessionMiddlewareConfig { pub name: String, /// Whether the unmodified session should be saved on read or not. /// If set to `true`, the session will be saved even if it was not modified. + /// /// It is set to `false` by default. + /// /// # Examples /// /// ``` @@ -1701,7 +1728,9 @@ pub struct SessionMiddlewareConfig { /// let config = SessionMiddlewareConfig::builder().always_save(true).build(); /// ``` pub always_save: bool, - /// The [`Expiry`] behavior for session cookies. + /// The + /// [`Expiry`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#removal_defining_the_lifetime_of_a_cookie) + /// behavior for session cookies. /// /// This controls when the session cookie expires and how long it remains /// valid. The expiry behavior affects how the cookie's `max-age` and @@ -1727,8 +1756,6 @@ pub struct SessionMiddlewareConfig { /// - For `AtDateTime`: Use a valid RFC 3339/ISO 8601 formatted timestamp /// (e.g., `"2025-12-31T23:59:59+00:00"`). /// - /// [`Expiry`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#removal_defining_the_lifetime_of_a_cookie - /// /// # Examples /// /// ``` @@ -1777,6 +1804,7 @@ pub struct SessionMiddlewareConfig { /// ); /// ``` #[serde(with = "crate::serializers::session_expiry_time")] + #[cfg_attr(feature = "_internal_config-docs", schemars(with = "Option"))] pub expiry: Expiry, /// What session store to use. @@ -1863,10 +1891,12 @@ impl Default for SessionMiddlewareConfig { /// The default backend if not specified is `console`. #[cfg(feature = "email")] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(tag = "type", rename_all = "snake_case")] #[non_exhaustive] pub enum EmailTransportTypeConfig { - /// Console email transport backend. + /// Console email transport backend that prints the contents to the standard + /// output. /// /// This is a convenient transport backend for development and testing that /// simply prints the email contents to the console instead of actually @@ -1920,6 +1950,7 @@ pub enum EmailTransportTypeConfig { /// ``` url: EmailUrl, /// The authentication mechanism to use. + /// /// Supported mechanisms are `plain`, `login`, and `xoauth2`. /// /// # TOML Configuration @@ -1940,6 +1971,7 @@ pub enum EmailTransportTypeConfig { /// configuration. #[cfg(feature = "email")] #[derive(Debug, Default, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] pub struct EmailTransportConfig { @@ -2018,6 +2050,7 @@ impl EmailTransportConfigBuilder { /// ``` #[cfg(feature = "email")] #[derive(Debug, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] pub struct EmailConfig { @@ -2132,7 +2165,9 @@ impl Default for EmailConfig { /// ``` #[repr(transparent)] #[derive(Clone, Deserialize, PartialEq, Eq)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(from = "String")] +#[cfg_attr(feature = "_internal_config-docs", schemars(with = "String"))] pub struct SecretKey(SecureBytes); impl Serialize for SecretKey { @@ -2239,7 +2274,9 @@ impl From<&str> for SecretKey { /// let url = DatabaseUrl::from("postgres://user:password@localhost:5432/database"); /// ``` #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(transparent)] +#[cfg_attr(feature = "_internal_config-docs", schemars(with = "String"))] #[cfg(feature = "db")] pub struct DatabaseUrl(url::Url); @@ -2348,7 +2385,9 @@ impl std::str::FromStr for CacheType { /// let url = CacheUrl::from("redis://user:password@localhost:6379/0"); /// ``` #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(transparent)] +#[cfg_attr(feature = "_internal_config-docs", schemars(with = "String"))] #[cfg(feature = "cache")] pub struct CacheUrl(url::Url); @@ -2457,7 +2496,9 @@ impl std::fmt::Display for CacheUrl { /// let url = EmailUrl::from("smtp://user:pass@hostname:587"); /// ``` #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(transparent)] +#[cfg_attr(feature = "_internal_config-docs", schemars(with = "String"))] #[cfg(feature = "email")] pub struct EmailUrl(url::Url); diff --git a/cot/src/email/transport/smtp.rs b/cot/src/email/transport/smtp.rs index 9fadbd45..f9a2299e 100644 --- a/cot/src/email/transport/smtp.rs +++ b/cot/src/email/transport/smtp.rs @@ -70,6 +70,7 @@ impl From for TransportError { /// /// The default is `Plain`. #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "_internal_config-docs", derive(schemars::JsonSchema))] #[serde(rename_all = "lowercase")] #[non_exhaustive] pub enum Mechanism { diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..8b56ac10 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,210 @@ +--- +title: Configuration +--- + + + +The configuration for a project. + +Cot projects are configured via a TOML file (typically `config/dev.toml` and `config/prod.toml`, loaded with +[`ProjectConfig::from_toml`](https://docs.rs/cot/latest/cot/config/struct.ProjectConfig.html#method.from_toml)). +This page lists every table and key that `ProjectConfig` understands. + +Any top-level table not listed below is preserved as-is and made available to your application through `ProjectConfig::extra`, for app-specific configuration. + +## Top-level keys + +| Key | Type | Default | Description | +|---|---|---|---| +| `debug` | boolean | `true` | Debug mode flag. | +| `register_panic_hook` | boolean | `true` | Whether to register a panic hook. | +| `secret_key` | string | — | The secret key used for signing cookies and other sensitive data. This is a cryptographic key, should be kept secret, and should be set to a random and unique value for each project. | +| `fallback_secret_keys` | array of strings | `[]` | Fallback secret keys that can be used to verify old sessions. | +| `auth_backend` | table | *(see below)* | The authentication backend to use. | +| `database` | table | *(see below)* | Database configuration. | +| `cache` | table | *(see below)* | Cache subsystem configuration. | +| `static_files` | table | *(see below)* | Static files configuration. | +| `middlewares` | table | *(see below)* | Middleware configuration. | +| `email` | table | *(see below)* | Email backend configuration. | + +## `[auth_backend]` + +Select the variant with the `type` key: + +### `type = "none"` + +No authentication backend. + +### `type = "database"` + +Database authentication backend. + +## `[database]` + +| Key | Type | Default | Description | +|---|---|---|---| +| `url` | string | — | The URL of the database, possibly with username, password, and other options. | + +## `[cache]` + +| Key | Type | Default | Description | +|---|---|---|---| +| `max_retries` | integer | `3` | Maximum number of retries for cache operations. | +| `timeout` | string | `"5m"` | Timeout for cache operations. | +| `prefix` | string | — | Prefix for cache keys. | +| `store` | table | *(see below)* | The cache store configuration. | + +### `[cache.store]` + +Select the variant with the `type` key: + +#### `type = "memory"` + +In-memory cache store. + +#### `type = "redis"` + +Redis cache store. This stores cache data in a Redis instance. The URL to the Redis server must be specified, and additional Redis-specific options can be configured. + +| Key | Type | Default | Description | +|---|---|---|---| +| `url` | string | — | The URL of the Redis server. | +| `pool_size` | integer | — | Connection pool size for Redis connections. This controls how many connections to maintain in the connection pool. When not specified, a default pool size of `10` is used. | + +#### `type = "file"` + +File-based cache store. This stores cache data in files on the local filesystem. The path to the directory where the cache files will be stored must be specified. + +| Key | Type | Default | Description | +|---|---|---|---| +| `path` | string | — | The path to the directory where cache files will be stored. | + +## `[static_files]` + +| Key | Type | Default | Description | +|---|---|---|---| +| `url` | string | `"/static/"` | The URL prefix for the static files to be served at (which should typically end with a slash). | +| `rewrite` | `"none"`, `"query_param"` | `"none"` | The URL rewriting mode for the static files. This is useful to allow long-lived caching of static files, while still allowing to invalidate the cache when the file changes. | +| `cache_timeout` | string | — | The duration for which static files should be cached by browsers. | + +## `[middlewares]` + +| Key | Type | Default | Description | +|---|---|---|---| +| `live_reload` | table | *(see below)* | The configuration for the live reload middleware. | +| `session` | table | *(see below)* | The configuration for the session middleware. | + +### `[middlewares.live_reload]` + +| Key | Type | Default | Description | +|---|---|---|---| +| `enabled` | boolean | `false` | Whether the live reload middleware is enabled. | + +### `[middlewares.session]` + +| Key | Type | Default | Description | +|---|---|---|---| +| `secure` | boolean | `true` | The [`Secure`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies) of the cookie determines whether the session middleware is secure. | +| `http_only` | boolean | `true` | The [`HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies) of the cookie used for the session. It is set to `true` by default. | +| `same_site` | `"strict"`, `"lax"`, `"none"` | `"strict"` | The [`SameSite`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#controlling_third-party_cookies_with_samesite) attribute of the cookie used for the session. | +| `domain` | string | — | The [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#define_where_cookies_are_sent) attribute of the cookie used for the session. | +| `path` | string | `"/"` | The [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#define_where_cookies_are_sent) attribute of the cookie used for the session. | +| `name` | string | `"id"` | The name of the cookie used for the session. | +| `always_save` | boolean | `false` | Whether the unmodified session should be saved on read or not. If set to `true`, the session will be saved even if it was not modified. | +| `expiry` | string | — | The [`Expiry`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#removal_defining_the_lifetime_of_a_cookie) behavior for session cookies. | +| `store` | table | *(see below)* | What session store to use. | + +#### `[middlewares.session.store]` + +Select the variant with the `type` key: + +##### `type = "memory"` + +In-memory session storage. + +##### `type = "database"` + +Database-backed session storage. + +##### `type = "file"` + +File-based session storage. + +| Key | Type | Default | Description | +|---|---|---|---| +| `path` | string | — | The path to the directory where session files will be stored. | + +##### `type = "cache"` + +Cache-based session storage. + +| Key | Type | Default | Description | +|---|---|---|---| +| `uri` | string | — | The URI to the cache service. | + +## `[email]` + +| Key | Type | Default | Description | +|---|---|---|---| +| `transport` | table | *(see below)* | The type of email transport backend to use. | + +### `[email.transport]` + +Select the variant with the `type` key: + +#### `type = "console"` + +Console email transport backend that prints the contents to the standard output. + +#### `type = "smtp"` + +SMTP email transport backend. + +| Key | Type | Default | Description | +|---|---|---|---| +| `url` | string | — | The SMTP connection URL. | +| `mechanism` | `"plain"`, `"login"`, `"xoauth2"` | — | The authentication mechanism to use. | + +## Full default configuration + +This is a complete example with every key set explicitly to its default value (fields without a well-defined default, like `secret_key`, are omitted): + +```toml +debug = true +register_panic_hook = true +fallback_secret_keys = [] + +[auth_backend] +type = "none" + +[cache] +max_retries = 3 +timeout = "5m" + +[cache.store] +type = "memory" + +[static_files] +url = "/static/" +rewrite = "none" + +[middlewares.live_reload] +enabled = false + +[middlewares.session] +secure = true +http_only = true +same_site = "strict" +path = "/" +name = "id" +always_save = false + +[middlewares.session.store] +type = "memory" + +[email.transport] +type = "console" +``` diff --git a/docs/site/src/main.rs b/docs/site/src/main.rs index 12f65243..8465f609 100644 --- a/docs/site/src/main.rs +++ b/docs/site/src/main.rs @@ -35,6 +35,7 @@ impl Project for CotSiteProject { "Getting started", vec![ GuideItem::Page(md_page!("introduction")), + GuideItem::Page(md_page!("configuration")), GuideItem::Page(md_page!("templates")), GuideItem::Page(md_page!("forms")), GuideItem::SubCategory { diff --git a/justfile b/justfile index ab5239fa..80e7c9c8 100644 --- a/justfile +++ b/justfile @@ -78,3 +78,7 @@ alias td := test-docs test-docs: cargo nextest run -p cot-test + +generate-config-docs: + # regenerates docs/configuration.md from `cot::config::ProjectConfig` + cargo run -p cot-test --features config-docs --bin generate_config_docs