diff --git a/.cargo/deny.toml b/.cargo/deny.toml index 44e84bbb..5d15c5cb 100644 --- a/.cargo/deny.toml +++ b/.cargo/deny.toml @@ -24,5 +24,6 @@ allow = [ "ISC", "MIT", "Unicode-3.0", + "Unicode-DFS-2016", "Zlib", ] diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 00000000..aacfd574 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,9 @@ +[test-groups] +# Guide doc tests build Rust project (which already uses all available CPU +# cores) and reuse the target directory (which is shared across all tests), +# so we limit the number of concurrently running guide doc tests to 1 +doc-code-blocks = { max-threads = 1 } + +[[profile.default.overrides]] +filter = 'package(cot-test)' +test-group = 'doc-code-blocks' diff --git a/Cargo.lock b/Cargo.lock index fedd2672..0c5f6175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,6 +601,15 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] +[[package]] +name = "caseless" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + [[package]] name = "cast" version = "0.3.0" @@ -787,6 +796,23 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "comrak" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07383e7799d964bf7ffa6fc4457d177c54a44614661c7458bb0bd91b108e32" +dependencies = [ + "caseless", + "entities", + "finl_unicode", + "jetscii", + "phf 0.13.1", + "phf_codegen", + "rustc-hash", + "smallvec", + "typed-arena", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -974,6 +1000,17 @@ dependencies = [ "trybuild", ] +[[package]] +name = "cot-test" +version = "0.1.0" +dependencies = [ + "comrak", + "cot", + "cot-cli", + "glob", + "libtest-mimic", +] + [[package]] name = "cot_codegen" version = "0.6.0" @@ -1442,6 +1479,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + [[package]] name = "equivalent" version = "1.0.2" @@ -1455,9 +1498,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + [[package]] name = "etcetera" version = "0.8.0" @@ -1614,6 +1663,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -2390,6 +2445,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jetscii" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" + [[package]] name = "jni" version = "0.21.1" @@ -2542,6 +2603,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libtest-mimic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2963,6 +3036,26 @@ dependencies = [ "phf_shared 0.12.1", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + [[package]] name = "phf_generator" version = "0.11.3" @@ -2973,13 +3066,23 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator", + "phf_generator 0.11.3", "phf_shared 0.11.3", "proc-macro2", "quote", @@ -3004,6 +3107,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -3496,7 +3608,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3553,7 +3665,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4170,10 +4282,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4701,6 +4813,12 @@ dependencies = [ "toml 1.1.0+spec-1.1.0", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.19.0" @@ -5025,7 +5143,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index be8f4523..b789ce9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "cot-codegen", "cot-core", "cot-macros", + "cot-test", # Examples "examples/admin", "examples/custom-error-pages", @@ -81,9 +82,11 @@ clap = { version = "4.5.60", features = ["deprecated"] } clap-verbosity-flag = { version = "3", default-features = false } clap_complete = "4" clap_mangen = "0.3.0" +comrak = { version = "0.51", default-features = false } cot = { version = "0.6.0", path = "cot" } -cot_core = { version = "0.6.0", path = "cot-core" } cot_codegen = { version = "0.6.0", path = "cot-codegen" } +cot-cli = { version = "0.6.0", path = "cot-cli" } +cot_core = { version = "0.6.0", path = "cot-core" } cot_macros = { version = "0.6.0", path = "cot-macros" } criterion = "0.8" darling = "0.23" @@ -105,11 +108,12 @@ http = "1.4" http-body = "1" http-body-util = "0.1.3" humantime = "2" +idna = { version = "1.1", default-features = false } indexmap = "2" insta = { version = "1", features = ["filters"] } insta-cmd = "0.6" lettre = { version = "0.11.20", default-features = false } -idna = { version = "1.1", default-features = false } +libtest-mimic = "0.8" mime = "0.3" mime_guess = { version = "2", default-features = false } mockall = "0.14" diff --git a/cot-test/Cargo.toml b/cot-test/Cargo.toml new file mode 100644 index 00000000..0da08e16 --- /dev/null +++ b/cot-test/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cot-test" +description = "Internal crate containing various integrations tests and test utilities." +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +readme.workspace = true +authors.workspace = true + +[dependencies] +comrak.workspace = true +cot-cli = { workspace = true, features = ["test_utils"] } +cot.workspace = true +glob.workspace = true +libtest-mimic.workspace = true + +[lints] +workspace = true + +[[test]] +name = "doc_code_blocks" +path = "tests/doc_code_blocks.rs" +harness = false diff --git a/cot-test/src/lib.rs b/cot-test/src/lib.rs new file mode 100644 index 00000000..5fb2002f --- /dev/null +++ b/cot-test/src/lib.rs @@ -0,0 +1,261 @@ +use std::fs; +use std::path::PathBuf; +use std::sync::{Mutex, MutexGuard, OnceLock}; + +use cot_cli::new_project::{CotSource, new_project}; +use libtest_mimic::Failed; + +pub const COMMON_IMPORTS: &[&str] = &[ + "cot::db::*", + "cot::request::extractors::*", + "cot::response::*", + "cot::html::*", + "cot::router::*", + "cot::test::*", + "cot::project::*", + "cot::static_files::*", + "cot::middleware::*", + "cot::form::*", + "cot::json::*", + "cot::cli::*", + "cot::request::Request", + "cot::response::Response", + "cot::*", + "std::collections::HashMap", + "std::fmt::Display", + "serde::{Deserialize, Serialize}", + "schemars::JsonSchema", + "askama::filters::HtmlSafe", + "cot::static_files::StaticFilesMiddleware", + "cot::project::RootHandler", + "cot::admin::AdminModel", + "cot::form::Form", + "cot::db::Model", + "cot::project::App", + "cot::project::Project", + "cot::cli::CliMetadata", +]; + +const HTML_MAIN_RS: &str = include_str!("../templates/html_main.rs"); +const BASE_HTML: &str = include_str!("../templates/base.html"); + +pub const RUST_HAS_MAIN_TEST_TYPE: &str = "has_main"; + +#[derive(Debug)] +pub struct DocTestProject { + path: PathBuf, + temp_dir: PathBuf, +} + +impl DocTestProject { + /// Creates a new `DocTestProject` instance. + /// + /// # Panics + /// + /// Panics if it fails to create the project directory or write files. + #[must_use] + pub fn new(temp_dir: PathBuf) -> Self { + let project_path = temp_dir.join("doc_test"); + + if project_path.exists() { + fs::remove_dir_all(&project_path) + .expect("failed to clean up existing project directory"); + } + + let cot_test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let cot_workspace_path = cot_test_path + .parent() + .expect("failed to get workspace path"); + let cot_path = cot_workspace_path.join("cot"); + + new_project(&project_path, "doc_test", &CotSource::Path(&cot_path)) + .expect("failed to create doc test project"); + + // Add extra dependencies and features for tests + let cargo_toml_path = project_path.join("Cargo.toml"); + let mut cargo_toml = + fs::read_to_string(&cargo_toml_path).expect("failed to read Cargo.toml"); + cargo_toml = cargo_toml.replace( + "features = [\"full\"]", + "features = [\"full\", \"openapi\", \"swagger-ui\"]", + ); + cargo_toml.push_str("serde = { version = \"1\", features = [\"derive\"] }\n"); + cargo_toml.push_str("schemars = \"0.9\"\n"); + cargo_toml.push_str("askama = \"0.15\"\n"); + cargo_toml.push_str("async-trait = \"0.1\"\n"); + + // Add empty workspace info to prevent Cargo from trying to build the entire + // workspace when running tests + cargo_toml.push_str("[workspace]\n"); + + fs::write(cargo_toml_path, cargo_toml).expect("failed to write Cargo.toml"); + + Self { + path: project_path, + temp_dir, + } + } + + /// Checks the given Rust code block. + /// + /// # Errors + /// + /// Returns an error if the code fails to compile. + /// + /// # Panics + /// + /// Panics if it fails to write the code to a file. + pub fn check_rust(&self, code: &str, test_type: &str) -> Result<(), Failed> { + self.cleanup_project()?; + + let mut preamble = String::new(); + for &symbol_part in COMMON_IMPORTS { + let import = format!("use {symbol_part};"); + let symbol_name = symbol_part + .rsplit_once("::") + .map_or(symbol_part, |(_, s)| s); + + if symbol_name == "*" { + // For wildcards, only add if not already present as a wildcard from the same + // path + if !code.contains(symbol_part) { + preamble.push_str(&import); + } + } else { + // For specific symbols, only add if the symbol is NOT already imported + let mut found = false; + for line in code.lines() { + if line.starts_with("use ") && line.contains(symbol_name) { + found = true; + break; + } + } + if !found { + preamble.push_str(&import); + } + } + } + + let final_code = match test_type { + RUST_HAS_MAIN_TEST_TYPE => code.to_string(), + _ => Self::wrap_in_main(&preamble, code), + }; + + let main_rs_path = self.path.join("src/main.rs"); + fs::write(main_rs_path, &final_code).map_err(|e| Failed::from(e.to_string()))?; + + self.create_dummy_files()?; + self.run_cargo_check() + } + + fn wrap_in_main(preamble: &str, code: &str) -> String { + format!( + r" +#![allow(unused_imports, dead_code, unused_variables, unused_mut)] +{preamble} +fn main() {{ + let _ = async {{ + let _result: cot::Result<()> = async {{ + {code} + Ok::<(), cot::Error>(()) + }}.await; + }}; +}} +" + ) + } + + /// Checks the given HTML template. + /// + /// # Errors + /// + /// Returns an error if the template fails to compile. + /// + /// # Panics + /// + /// Panics if it fails to write the template to a file. + pub fn check_html(&self, html: &str) -> Result<(), Failed> { + self.cleanup_project()?; + let template_path = self.path.join("templates/index.html"); + fs::write(template_path, html).map_err(|e| Failed::from(e.to_string()))?; + + self.create_dummy_files()?; + + let main_rs_path = self.path.join("src/main.rs"); + fs::write(main_rs_path, HTML_MAIN_RS).map_err(|e| Failed::from(e.to_string()))?; + + self.run_cargo_check() + } + + fn cleanup_project(&self) -> Result<(), Failed> { + let templates_dir = self.path.join("templates"); + if templates_dir.exists() { + fs::remove_dir_all(&templates_dir).ok(); + } + fs::create_dir_all(&templates_dir).map_err(|e| Failed::from(e.to_string()))?; + + let static_dir = self.path.join("static"); + if static_dir.exists() { + fs::remove_dir_all(&static_dir).ok(); + } + fs::create_dir_all(&static_dir).map_err(|e| Failed::from(e.to_string()))?; + + Ok(()) + } + + fn create_dummy_files(&self) -> Result<(), Failed> { + let templates_dir = self.path.join("templates"); + fs::write(templates_dir.join("base.html"), BASE_HTML) + .map_err(|e| Failed::from(e.to_string()))?; + + let dummy_templates = ["hello.html", "error.html", "500.html", "index.html"]; + for t in dummy_templates { + let p = templates_dir.join(t); + if !p.exists() { + fs::write(p, "dummy content").map_err(|e| Failed::from(e.to_string()))?; + } + } + + let static_dir = self.path.join("static"); + fs::create_dir_all(static_dir.join("css")).ok(); + fs::create_dir_all(static_dir.join("images")).ok(); + fs::write(static_dir.join("css/main.css"), "").ok(); + fs::write(static_dir.join("images/logo.png"), "").ok(); + + Ok(()) + } + + fn run_cargo_check(&self) -> Result<(), Failed> { + let target_dir = self.temp_dir.join("doc_test_target"); + fs::create_dir_all(&target_dir).ok(); + + let output = cot_cli::test_utils::project_cargo(&self.path) + .arg("check") + .env("CARGO_TARGET_DIR", target_dir) + .output() + .map_err(|e| Failed::from(e.to_string()))?; + + if !output.status.success() { + return Err(Failed::from(format!( + "cargo check failed:\nSTDOUT:\n{}\nSTDERR:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ))); + } + Ok(()) + } +} + +static TEST_PROJECT: OnceLock> = OnceLock::new(); + +/// Returns a reference to the global `DocTestProject` instance. +/// +/// # Panics +/// +/// Panics if it fails to initialize the project. +pub fn get_test_project(temp_dir: PathBuf) -> MutexGuard<'static, DocTestProject> { + TEST_PROJECT + .get_or_init(|| Mutex::new(DocTestProject::new(temp_dir))) + .lock() + .expect("failed to lock test project") +} diff --git a/cot-test/templates/base.html b/cot-test/templates/base.html new file mode 100644 index 00000000..4e9d14b2 --- /dev/null +++ b/cot-test/templates/base.html @@ -0,0 +1,13 @@ + + + + + {% block title %} + {% endblock title %} + + + + {% block content %} + {% endblock content %} + + diff --git a/cot-test/templates/html_main.rs b/cot-test/templates/html_main.rs new file mode 100644 index 00000000..9b0a4488 --- /dev/null +++ b/cot-test/templates/html_main.rs @@ -0,0 +1,49 @@ +#![allow(unused_imports, dead_code)] +use std::collections::HashMap; +use std::fmt::Display; + +use cot::Template; +use cot::form::{Form, FormContext, FormErrorTarget}; +use cot::html::Html; +use cot::request::Request; +use cot::request::extractors::StaticFiles; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ::schemars::JsonSchema)] +struct Item { + title: String, +} +impl Display for Item { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.title) + } +} +impl askama::filters::HtmlSafe for Item {} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ::schemars::JsonSchema)] +struct User { + is_admin: bool, + is_logged_in: bool, + role: Option, +} + +#[derive(cot::form::Form)] +struct DummyForm { + name: String, +} + +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate<'a> { + static_files: &'a StaticFiles, + request: &'a Request, + form: &'a ::Context, + form_context: &'a ::Context, + name: String, + items: Vec, + user: User, + item: Item, + urls: &'a cot::router::Urls, + error: cot::Error, +} + +fn main() {} diff --git a/cot-test/tests/doc_code_blocks.rs b/cot-test/tests/doc_code_blocks.rs new file mode 100644 index 00000000..7552be3f --- /dev/null +++ b/cot-test/tests/doc_code_blocks.rs @@ -0,0 +1,124 @@ +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; +use std::sync::OnceLock; + +use comrak::arena_tree::NodeEdge; +use comrak::nodes::NodeValue; +use comrak::{Arena, parse_document}; +use cot_test::{RUST_HAS_MAIN_TEST_TYPE, get_test_project}; +use libtest_mimic::{Arguments, Failed, Trial}; + +type TestRunner = fn(&str, &str) -> Result<(), Failed>; + +static TEST_RUNNERS: OnceLock> = OnceLock::new(); + +fn main() { + let args = Arguments::from_args(); + + let mut test_runners: HashMap<(&str, &str), TestRunner> = HashMap::new(); + test_runners.insert(("rust", DEFAULT_TEST_NAME), test_rust); + test_runners.insert(("rust", RUST_HAS_MAIN_TEST_TYPE), test_rust); + test_runners.insert(("toml", DEFAULT_TEST_NAME), test_toml); + test_runners.insert(("html.j2", DEFAULT_TEST_NAME), test_html); + TEST_RUNNERS.set(test_runners).unwrap(); + + let mut trials = Vec::new(); + + let cot_test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let docs_path = cot_test_path.parent().unwrap().join("docs"); + + let md_files = glob::glob(&format!("{}/*.md", docs_path.to_str().unwrap())) + .expect("failed to glob md files"); + + for entry in md_files { + let path = entry.expect("failed to read glob entry"); + let file_name = path.file_name().unwrap().to_str().unwrap(); + if file_name == "README.md" { + continue; + } + + let contents = fs::read_to_string(&path).expect("failed to read md file"); + test_md(&mut trials, file_name, &contents); + } + + libtest_mimic::run(&args, trials).exit(); +} + +const DEFAULT_TEST_NAME: &str = "default"; + +fn test_md(trials: &mut Vec, file_name: &str, file_contents: &str) { + let arena = Arena::new(); + + let mut options = comrak::Options::default(); + options.extension.front_matter_delimiter = Some("---".to_string()); + + let root = parse_document(&arena, file_contents, &options); + + for node in root.traverse() { + if let NodeEdge::Start(node) = node { + let node_data = node.data.borrow(); + if let NodeValue::CodeBlock(code_block) = &node_data.value { + let (lang, test_config) = + if let Some((lang, test_config)) = code_block.info.split_once(',') { + (lang, test_config.trim()) + } else { + (code_block.info.as_str(), DEFAULT_TEST_NAME) + }; + + if let Some(runner) = TEST_RUNNERS.get().unwrap().get(&(lang, test_config)) { + let literal = if lang == "rust" { + clean_code(&code_block.literal) + } else { + code_block.literal.clone() + }; + + let line = node_data.sourcepos.start.line; + let runner = *runner; + let file_name = file_name.to_string(); + let test_config = test_config.to_string(); + trials.push(Trial::test( + format!("{file_name}; line {line}; {lang},{test_config}"), + move || runner(&literal, &test_config), + )); + } + } + } + } +} + +fn clean_code(code: &str) -> String { + code.lines() + .map(|line| { + if let Some(rest) = line.strip_prefix("# ") { + rest + } else if line == "#" { + "" + } else { + line + } + }) + .collect::>() + .join("\n") +} + +fn get_temp_dir() -> PathBuf { + PathBuf::from(env!("CARGO_TARGET_TMPDIR")) +} + +fn test_rust(code: &str, test_type: &str) -> Result<(), Failed> { + let project = get_test_project(get_temp_dir()); + project.check_rust(code, test_type) +} + +fn test_toml(code: &str, _test_type: &str) -> Result<(), Failed> { + cot::config::ProjectConfig::from_toml(code) + .map_err(|e| Failed::from(format!("could not parse the config: {e}")))?; + + Ok(()) +} + +fn test_html(code: &str, _test_type: &str) -> Result<(), Failed> { + let project = get_test_project(get_temp_dir()); + project.check_html(code) +} diff --git a/cot/src/project.rs b/cot/src/project.rs index 8c66bbb9..e71f5759 100644 --- a/cot/src/project.rs +++ b/cot/src/project.rs @@ -2038,7 +2038,7 @@ impl>> ProjectContext { #[track_caller] pub fn database(&self) -> &Database { self.try_database().expect( - "Database missing. Did you forget to add the database when configuring CotProject?", + "Database missing. Did you forget to add the database when configuring cot::Project?", ) } } diff --git a/cot/src/test.rs b/cot/src/test.rs index 2beee985..d5cfd770 100644 --- a/cot/src/test.rs +++ b/cot/src/test.rs @@ -649,6 +649,24 @@ impl TestRequestBuilder { self } + /// Add a cache to the request builder. + /// + /// # Examples + /// + /// ``` + /// use cot::test::{TestCache, TestRequestBuilder}; + /// + /// let test_cache = TestCache::new_memory(); + /// let request = TestRequestBuilder::get("/") + /// .cache(test_cache.cache()) + /// .build(); + /// ``` + #[cfg(feature = "cache")] + pub fn cache(&mut self, cache: Cache) -> &mut Self { + self.cache = Some(cache); + self + } + /// Add form data to the request builder. /// /// # Examples diff --git a/docs/README.md b/docs/README.md index 6672fcb8..2e590d59 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,26 @@ -## Cot Guide +# Cot Guide This directory contains the guide pages for the Cot web framework. The guide is built using Markdown files, which are rendered into HTML by the website engine located in the [cot-site repository](https://github.com/cot-rs/cot-site). To build the guide with the latest changes, you should go to the [docs-site](../docs-site/) directory and follow the instructions from the [README.md](../docs-site/README.md) file. The changes will be reflected in the website as the "master" version of Cot. + +## Testing the code snippets + +To ensure the guide remains accurate, all code snippets are automatically tested. You can run these tests using: + +```bash +cargo nextest run -p cot-test +# or using the justfile alias +just test-docs +# or its shorter version +just td +``` + +The test runner identifies snippets by their language and optional configuration (e.g., ` ```rust,has_main `). + +### Test Types + +- **`rust`**: Snippets are wrapped in an `async` block within a `main` function. Many common symbols from `cot` and `std` are automatically imported. You can use `# ` at the start of a line to include it in the test while hiding it from the rendered guide. +- **`rust,has_main`**: Used for snippets that define their own `main` function. No automatic imports are provided. +- **`toml`**: Snippets are validated by parsing them as a Cot project configuration file. +- **`html.j2`**: Snippets are compiled as Askama templates. The test environment provides dummy files (like `base.html` or `logo.png`) to satisfy common references. diff --git a/docs/admin-panel.md b/docs/admin-panel.md index da942f52..8f62e070 100644 --- a/docs/admin-panel.md +++ b/docs/admin-panel.md @@ -16,6 +16,8 @@ use cot::project::{MiddlewareContext, RegisterAppsContext, RootHandler, RootHand use cot::static_files::StaticFilesMiddleware; struct MyProject; +# struct MyApp; +# impl App for MyApp { fn name(&self) -> &'static str { "test" } } impl Project for MyProject { fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) { @@ -31,7 +33,7 @@ impl Project for MyProject { ) -> RootHandler { handler .middleware(StaticFilesMiddleware::from_context(app_context)) - .middleware(SessionMiddleware::new()) // Required for admin login + .middleware(SessionMiddleware::from_context(app_context)) // Required for admin login .build() } @@ -49,6 +51,7 @@ use cot::ProjectContext; use cot::auth::db::DatabaseUser; use cot::common_types::Password; use std::env; +# struct MyApp; // In your main.rs: #[async_trait] @@ -56,20 +59,16 @@ impl App for MyApp { async fn init(&self, context: &mut ProjectContext) -> cot::Result<()> { // Check if admin user exists let admin_username = env::var("ADMIN_USER") - .unwrap_or_else(|_| "admin".to_string()); + .unwrap_or_else(|_| "admin".to_string()); let user = DatabaseUser::get_by_username(context.database(), &admin_username).await?; if user.is_none() { let password = env::var("ADMIN_PASSWORD") - .unwrap_or_else(|_| "change_me".to_string()); - // Create admin user - DatabaseUser::create_user( - context.database(), - &admin_username, - &Password::new(&password) - ).await?; + .unwrap_or_else(|_| "change_me".to_string()); + DatabaseUser::create_user(context.database(), &admin_username, password.as_str()).await?; } Ok(()) } +# fn name(&self) -> &str { todo!() } } ``` @@ -91,11 +90,13 @@ struct BlogPost { content: String, published: bool, } +# impl Display for BlogPost { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } } ``` Note however that in order to derive the [`AdminModel`](trait@cot::admin::AdminModel) trait, you need to also derive the [`Form`](trait@cot::form::Form) and [`Model`](trait@cot::db::Model) traits (the latter is provided by the [`#[model]`](attr@cot::db::model) attribute). In addition to that, your model needs to implement the `Display` trait—for instance, in the case above, we could add it like so: ```rust +# struct BlogPost { title: String } impl Display for BlogPost { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.title) @@ -106,11 +107,18 @@ impl Display for BlogPost { After adding the [`AdminModel`](trait@cot::admin::AdminModel) trait, you can add your model to the admin panel using [`DefaultAdminModelManager`](struct@cot::admin::DefaultAdminModelManager). This is as easy as adding the following code to your [`App`](trait@cot::project::App) implementation: ```rust +# use cot::admin::{AdminModel, AdminModelManager, DefaultAdminModelManager}; +# #[derive(Debug, Form, AdminModel)] +# #[model] +# struct BlogPost { #[model(primary_key)] id: Auto } +# impl Display for BlogPost { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } } +# struct MyApp; impl App for MyApp { fn admin_model_managers(&self) -> Vec> { vec![Box::new(DefaultAdminModelManager::::new())] } +# fn name(&self) -> &'static str { "test" } // ... } ``` diff --git a/docs/caching.md b/docs/caching.md index 84a8f06c..84acbcb0 100644 --- a/docs/caching.md +++ b/docs/caching.md @@ -59,15 +59,18 @@ async fn cache_example(cache: Cache) -> cot::Result { You can set an expiration time for specific keys: ```rust -use std::time::Duration; -use cot::config::Timeout; - +# use std::time::Duration; +# use cot::config::Timeout; +# use cot::cache::Cache; +# async fn foo(cache: Cache) -> cot::Result<()> { // Cache for 60 seconds cache.insert_expiring( "temp_key", "temp_value", Timeout::After(Duration::from_secs(60)) ).await?; +# Ok(()) +# } ``` ## Advanced Topics @@ -77,10 +80,14 @@ cache.insert_expiring( You can use [`get_or_insert_with`](struct@cot::cache::Cache#method.get_or_insert_with) to lazily compute and cache values: ```rust +# use cot::cache::Cache; +# async fn foo(cache: Cache) -> cot::Result<()> { let value: String = cache.get_or_insert_with("expensive_key", || async { // Perform expensive computation Ok("expensive_result".to_string()) }).await?; +# Ok(()) +# } ``` #### Prefix diff --git a/docs/db-models.md b/docs/db-models.md index cc6d4ef2..31ce7a4f 100644 --- a/docs/db-models.md +++ b/docs/db-models.md @@ -42,9 +42,8 @@ This will create a new file in your `migrations` directory in the crate's src di In order to write a model instance to the database, you can use the [`save`](trait@cot::db::Model#method.save) method. Note that you need to have an instance of the [`Database`](struct@cot::db::Database) structure to do this – typically you can get it from the request object in your view. Here's an example of how you can save a new link to the database inside a view: ```rust -use cot::request::extractors::RequestDb; - -async fn create_link(RequestDb(db): RequestDb) -> cot::Result { +# #[model] pub struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32>, url: String } +async fn create_link(db: &Database) -> cot::Result { let mut link = Link { id: Auto::default(), slug: LimitedString::new("slug").unwrap(), @@ -53,6 +52,7 @@ async fn create_link(RequestDb(db): RequestDb) -> cot::Result { link.save(db).await?; // ... +# todo!() } ``` @@ -61,8 +61,12 @@ async fn create_link(RequestDb(db): RequestDb) -> cot::Result { Updating a model is similar to saving a new one, but you need to have an existing instance of the model that you want to update, or another instance with the same primary key. Here's an example of how you can update an existing link in the database: ```rust +# #[model] pub struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32>, url: String } +# async fn foo(db: &Database, mut link: Link) -> cot::Result<()> { link.url = "https://example.org".to_string(); link.save(db).await?; +# Ok(()) +# } ``` Note that the [`save`](trait@cot::db::Model#method.save) is a convenient method that can be used for both creating new rows and updating existing ones. If the primary key of the model is set to [`Auto`](enum@cot::db::Auto), the method will always create a new row in the database. If the primary key is set to a specific value, the method will update the row with that primary key, or create a new one if it doesn't exist. @@ -70,19 +74,27 @@ Note that the [`save`](trait@cot::db::Model#method.save) is a convenient method If you specifically want to update a row in the database for given primary key, you can use the [`update`](trait@cot::db::Model#method.update) method: ```rust +# #[model] pub struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32>, url: String } +# async fn foo(db: &Database, mut link: Link) -> cot::Result<()> { link.url = "https://example.org".to_string(); link.update(db).await?; +# Ok(()) +# } ``` Similarly, if you want to insert a new row in the database and cause an error if a row with the same primary key already exists, you can use the [`insert`](trait@cot::db::Model#method.insert) method: ```rust +# #[model] pub struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32>, url: String } +# async fn foo(db: &Database) -> cot::Result<()> { let mut link = Link { id: Auto::default(), slug: LimitedString::new("slug").unwrap(), url: "https://example.com".to_string(), }; link.insert(db).await?; +# Ok(()) +# } ``` ### Retrieving models @@ -92,11 +104,14 @@ The basis for retrieving models from the database is the [`Query`](struct@cot::d The easiest way to work with the [`Query`](struct@cot::db::query::Query) structure is the [`query!`](macro@cot::db::query) macro, which allows you to write complicated queries in readable way using Rusty syntax. For example, to retrieve the link which has slug "cot" from the database, you can write: ```rust -use cot::db::query; - +# use cot::db::{query, Database, LimitedString}; +# #[model] struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32> } +# async fn foo(db: &Database) -> cot::Result<()> { let link = query!(Link, $slug == LimitedString::new("cot").unwrap()) .get(db) .await?; +# Ok(()) +# } ``` As you can see, the [`Query`](struct@cot::db::query::Query) macro takes the model type as the first argument, followed by the filter expression. The filter expression supports many of the common comparison operators, such as `==`, `!=`, `>`, `<`, `>=`, and `<=`. You can also use logical operators like `&&` and `||` to combine multiple conditions. The `$` sign is used to access the fields of the model in the filter expression—this is needed so that the macro can differentiate between fields of the model and other variables. What's nice about the filter expression is that it's type-checked at compile time, so not only you won't be able to filter using a non-existent field, but also you won't be able to compare fields of different types. @@ -106,7 +121,12 @@ As you can see, the [`Query`](struct@cot::db::query::Query) macro takes the mode To delete a model from the database, you can use the [`delete`](struct@cot::db::query::Query#method.delete) method of the [`Query`](struct@cot::db::query::Query) object returned by the [`query!`](macro@cot::db::query) macro. Here's an example of how you can delete a link from the database: ```rust +# use cot::db::{query, Database, LimitedString, model, Auto}; +# #[model] struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32> } +# async fn foo(db: &Database) -> cot::Result<()> { query!(Link, $slug == LimitedString::new("cot").unwrap()).delete(db).await?; +# Ok(()) +# } ``` ### Bulk operations @@ -114,22 +134,27 @@ query!(Link, $slug == LimitedString::new("cot").unwrap()).delete(db).await?; If you need to insert multiple rows at once, you can use the [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) method. This is much more efficient than calling [`save`](trait@cot::db::Model#method.save) or [`insert`](trait@cot::db::Model#method.insert) for each row individually, as it performs the operation in a single database query. ```rust +# #[model] struct User { #[model(primary_key)] id: Auto } +# #[model] struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32>, url: String, user: ForeignKey } +# async fn foo(db: &Database) -> cot::Result<()> { let mut links = vec![ Link { id: Auto::default(), slug: LimitedString::new("cot").unwrap(), url: "https://cot.rs".to_string(), - user: ForeignKey::new(1), + user: ForeignKey::PrimaryKey(Auto::fixed(1)), }, Link { id: Auto::default(), slug: LimitedString::new("rust").unwrap(), url: "https://rust-lang.org".to_string(), - user: ForeignKey::new(1), + user: ForeignKey::PrimaryKey(Auto::fixed(1)), }, ]; Link::bulk_insert(db, &mut links).await?; +# Ok(()) +# } ``` Note that [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) takes a mutable slice of models, because it needs to update the primary keys of the inserted models with the values generated by the database. @@ -137,7 +162,11 @@ Note that [`bulk_insert`](trait@cot::db::Model#method.bulk_insert) takes a mutab Similarly, there is also [`bulk_insert_or_update`](trait@cot::db::Model#method.bulk_insert_or_update) method, which works like [`bulk_insert`](trait@cot::db::Model#method.bulk_insert), but updates the existing rows if they conflict with the new ones. ```rust +# #[model] struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32> } +# async fn foo(db: &Database, mut links: Vec) -> cot::Result<()> { Link::bulk_insert_or_update(db, &mut links).await?; +# Ok(()) +# } ``` ## Foreign keys @@ -170,12 +199,17 @@ When you define a foreign key relationship, Cot will automatically create a fore When you retrieve a model that has a foreign key relationship, Cot will not automatically fetch the related model and populate the foreign key field with the corresponding value. Instead, you need to explicitly fetch the related model using the [`get`](enum@cot::db::ForeignKey#method.get) method of the [`ForeignKey`](enum@cot::db::ForeignKey) object. Here's an example of how you can fetch the related user for a link: ```rust +# #[model] struct User { #[model(primary_key)] id: Auto } +# #[model] struct Link { #[model(primary_key)] id: Auto, slug: LimitedString<32>, user: ForeignKey } +# async fn foo(db: &Database) -> cot::Result<()> { let mut link = query!(Link, $slug == LimitedString::new("cot").unwrap()) .get(db) .await? .expect("Link not found"); let user = link.user.get(db).await?; +# Ok(()) +# } ``` ## Database Configuration @@ -188,10 +222,10 @@ Configure your database connection in the configuration files inside your `confi url = "sqlite://db.sqlite3?mode=rwc" # Or PostgreSQL -url = "postgresql://user:password@localhost/dbname" +# url = "postgresql://user:password@localhost/dbname" # Or MySQL -url = "mysql://user:password@localhost/dbname" +# url = "mysql://user:password@localhost/dbname" ``` Cot tries to be as consistent as possible when it comes to the database engine you are using. This means that you can use SQLite for development and testing, and then switch to PostgreSQL or MySQL for production without changing your code. The only thing you need to do is to change the [`url`](struct@cot::config::DatabaseConfig#structfield.url) value in the configuration file! diff --git a/docs/error-pages.md b/docs/error-pages.md index c540586c..582ac2b0 100644 --- a/docs/error-pages.md +++ b/docs/error-pages.md @@ -62,7 +62,7 @@ impl Project for MyProject { Create `templates/error.html`: -```html +```html.j2 diff --git a/docs/forms.md b/docs/forms.md index af38018c..dec3f075 100644 --- a/docs/forms.md +++ b/docs/forms.md @@ -23,11 +23,8 @@ struct ContactForm { And here is how you can process the form inside a request handler: ```rust -use cot::form::{Form, FormResult}; -use cot::html::Html; -use cot::request::{Request, RequestExt}; -use cot::response::{Response, ResponseExt}; - +# #[derive(Form)] struct ContactForm { name: String, email: String, message: String } +# #[derive(Template)] #[template(source = "", ext = "html")] struct ContactTemplate<'a> { request: &'a Request, form: &'a dyn FormContext } async fn contact(mut request: Request) -> cot::Result { // Handle POST request (form submission) if request.method() == Method::POST { @@ -43,19 +40,20 @@ async fn contact(mut request: Request) -> cot::Result { // Form has errors - render the template with error messages let template = ContactTemplate { request: &request, - form: context, + form: &context, }; - Ok(Html::new(template.render()?).into()) + Ok(Html::new(template.render()?).into_response()?) } } } else { // Handle GET request (display empty form) + let form_context = ContactForm::build_context(&mut request).await?; let template = ContactTemplate { request: &request, - form: ContactForm::build_context(&mut request).await?, + form: &form_context, }; - Ok(Html::new(template.render()?).into()) + Ok(Html::new(template.render()?).into_response()?) } } ``` @@ -125,6 +123,8 @@ struct ArticleForm { You can implement custom validation by handling the validation result: ```rust +# #[derive(Form)] struct ArticleForm { title: String } +# fn render_template(_: impl cot::form::FormContext) -> cot::Result { Ok("".to_string()) } async fn handle_form(mut request: Request) -> cot::Result { match ArticleForm::from_request(&mut request).await? { FormResult::Ok(form) => { @@ -137,7 +137,7 @@ async fn handle_form(mut request: Request) -> cot::Result { ); // Re-render form with error - return Ok(Html::new(render_template(context)?).into()); + return Ok(Html::new(render_template(context)?).into_response()?); } // Process valid form... @@ -145,7 +145,7 @@ async fn handle_form(mut request: Request) -> cot::Result { } FormResult::ValidationError(context) => { // Handle validation errors... - Ok(Html::new(render_template(context)?).into()) + Ok(Html::new(render_template(context)?).into_response()?) } } } diff --git a/docs/introduction.md b/docs/introduction.md index b2658a61..e63c666d 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -85,6 +85,9 @@ At the heart of any web application is the ability to handle requests and return When you open the `src/main.rs` file, you'll see the following example view that has been generated for you: ```rust +# #[derive(Template)] +# #[template(source = "", ext = "html")] +# struct IndexTemplate; async fn index() -> cot::Result { let index_template = IndexTemplate {}; let rendered = index_template.render()?; @@ -96,14 +99,15 @@ async fn index() -> cot::Result { Further in the file you can see that this view is registered in the [`App`](trait@cot::project::App) implementation: ```rust -struct CotTutorialApp; - +# struct CotTutorialApp; +# async fn index(_request: cot::request::Request) -> cot::Result { todo!() } impl App for CotTutorialApp { // ... fn router(&self) -> Router { Router::with_urls([Route::with_handler_and_name("/", index, "index")]) } +# fn name(&self) -> &str { todo!() } } ``` @@ -112,17 +116,17 @@ This is how you specify the URL the view will be available at – in this case, You can add more views by adding more routes to the [`Router`](struct@cot::router::Router) by simply defining more functions and registering them in the [`router`](trait@cot::project::App#method.router) method: ```rust -async fn hello() -> Html { - Html::new("Hello World!") -} - -// inside `impl App`: - -fn router(&self) -> Router { - Router::with_urls([ - Route::with_handler_and_name("/", index, "index"), - Route::with_handler_and_name("/hello", hello, "hello"), - ]) +# struct CotTutorialApp; +# async fn index(_request: Request) -> cot::Result { todo!() } +# async fn hello(_request: Request) -> cot::Result { todo!() } +impl App for CotTutorialApp { + fn router(&self) -> Router { + Router::with_urls([ + Route::with_handler_and_name("/", index, "index"), + Route::with_handler_and_name("/hello", hello, "hello"), + ]) + } +# fn name(&self) -> &str { todo!() } } ``` @@ -135,14 +139,15 @@ You can also define dynamic routes by using the [`Route::with_handler_and_name`] At the core of Cot's request handling are _extractors_, which allow you to extract data from the request and pass it to the handler as arguments. One of such extractors is the [`Path`](struct@cot::request::extractors::Path) extractor, which allows you to extract path parameters from the URL. In order to use it, you need to define a parameter in the handler function, passing the parameter type as the generic parameter, like so: ```rust -use cot::request::extractors::Path; - +# struct MyApp; +# async fn index() -> cot::Result { todo!() } +# async fn hello() -> cot::Result { todo!() } async fn hello_name(Path(name): Path) -> cot::Result { Ok(Html::new(format!("Hello, {}!", name))) } // inside `impl App`: - +# impl App for MyApp { fn router(&self) -> Router { Router::with_urls([ Route::with_handler_and_name("/", index, "index"), @@ -150,23 +155,28 @@ fn router(&self) -> Router { Route::with_handler_and_name("/hello/{name}", hello_name, "hello_name"), ]) } +# fn name(&self) -> &str { todo!() } +# } ``` This works for multiple parameters, too—you just need to define a tuple of parameters in the handler function: ```rust +# struct MyApp; async fn hello_name(Path((first_name, last_name)): Path<(String, String)>) -> cot::Result { Ok(Html::new(format!("Hello, {first_name} {last_name}!"))) } // inside `impl App`: - +# impl App for MyApp { fn router(&self) -> Router { Router::with_urls([ // ... Route::with_handler_and_name("/hello/{first_name}/{last_name}/", hello_name, "hello_name"), ]) } +# fn name(&self) -> &str { todo!() } +# } ``` Now, when you visit [`localhost:8000/hello/John/Smith/`](http://localhost:8000/hello/John), you should see `Hello, John Smith!` displayed on the page! @@ -184,23 +194,33 @@ impl App for CotTutorialApp { fn name(&self) -> &'static str { env!("CARGO_CRATE_NAME") } +# } ``` An app is a collection of views and other components that make up a part of your service. Typically, they represent a part of your service, like the main website, an admin panel, or an API. An app usually corresponds to a single Rust crate, hence we're just using the name of the crate as the app name. The app name is used in many places, such as in the database table names, in the admin panel, or when reversing the URLs, so it needs to be unique in your project. ```rust +# use cot::db::migrations::SyncDynMigration; +# struct CotTutorialApp; +# mod migrations { pub const MIGRATIONS: &[&'static cot::db::migrations::SyncDynMigration] = &[]; } +# impl App for CotTutorialApp { fn migrations(&self) -> Vec> { cot::db::migrations::wrap_migrations(migrations::MIGRATIONS) } +# fn name(&self) -> &str { todo!() } +# } ``` This defines the database migration list that will be applied when your server starts. You shouldn't normally need to modify this, and the migrations can be generated automatically using the Cot CLI – more on this in the chapter about database models. ```rust +# struct CotTutorialApp; +# impl App for CotTutorialApp { fn static_files(&self) -> Vec { static_files!("css/main.css") } -} +# fn name(&self) -> &str { todo!() } +# } ``` This defines a list of static files that will be served by the server. More on that will be covered in the chapter about static files. @@ -216,19 +236,28 @@ impl Project for CotTutorialProject { fn cli_metadata(&self) -> CliMetadata { cot::cli::metadata!() } +# } ``` This defines the project and sets the CLI metadata (like the name, version, and description) that will be displayed when you run `cargo run -- --help` by using the metadata from your Cargo crate. ```rust +# struct CotTutorialProject; +# struct CotTutorialApp; +# impl App for CotTutorialApp { fn name(&self) -> &str { "test" } } +# impl Project for CotTutorialProject { fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) { apps.register_with_views(CotTutorialApp, ""); } +# } ``` This registers all the apps that your project is using. ```rust +# use cot::middleware::LiveReloadMiddleware; +# struct CotTutorialProject; +# impl Project for CotTutorialProject { fn middlewares( &self, handler: RootHandlerBuilder, @@ -239,11 +268,15 @@ This registers all the apps that your project is using. .middleware(LiveReloadMiddleware::from_context(context)) .build() } +# } ``` This registers the middlewares that will be applied to all routes in the project. Note that the [`LiveReloadMiddleware`](struct@cot::middleware::LiveReloadMiddleware) may be dynamically disabled in runtime using config! -```rust +```rust,has_main +# use cot::Project; +# struct CotTutorialProject; +# impl Project for CotTutorialProject {} #[cot::main] fn main() -> impl Project { CotTutorialProject diff --git a/docs/openapi.md b/docs/openapi.md index 983dcf94..675a5a5c 100644 --- a/docs/openapi.md +++ b/docs/openapi.md @@ -55,14 +55,14 @@ Note the use of `#[derive(JsonSchema)]` which comes from the [`schemars`](https: Next, create your API handlers using Cot's extractors: ```rust -use cot::json::Json; - +# struct AddRequest { a: i32, b: i32 } +# struct AddResponse { result: i32 } async fn add(Json(add_request): Json) -> cot::Result> { let response = AddResponse { result: add_request.a + add_request.b, }; - Json(response) + Ok(Json(response)) } ``` @@ -74,6 +74,7 @@ Instead of using regular method routers, use the OpenAPI-enabled versions that a use cot::router::method::openapi::api_post; use cot::router::{Route, Router}; +# async fn add(_request: cot::request::Request) -> cot::Result { todo!() } fn create_router() -> Router { Router::with_urls([ Route::with_api_handler("/add/", api_post(add)), @@ -91,10 +92,9 @@ The key differences from standard routes are: To expose the interactive documentation UI, register the [`SwaggerUi`](struct@cot::openapi::swagger_ui::SwaggerUi) app in your project: ```rust -use cot::openapi::swagger_ui::SwaggerUi; -use cot::static_files::StaticFilesMiddleware; -use cot::{App, AppBuilder, Project}; - +# use cot::openapi::swagger_ui::SwaggerUi; +# struct MyApiApp; +# impl App for MyApiApp { fn name(&self) -> &'static str { "api" } } struct MyProject; impl Project for MyProject { @@ -102,14 +102,14 @@ impl Project for MyProject { &self, handler: RootHandlerBuilder, context: &MiddlewareContext, - ) -> BoxedHandler { + ) -> RootHandler { // StaticFilesMiddleware is required for SwaggerUI to serve its assets handler .middleware(StaticFilesMiddleware::from_context(context)) .build() } - fn register_apps(&self, apps: &mut AppBuilder, context: &RegisterAppsContext) { + fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) { // Register the Swagger UI at the /swagger path apps.register_with_views(SwaggerUi::new(), "/swagger"); @@ -125,16 +125,16 @@ Don't forget to include the [`StaticFilesMiddleware`](struct@cot::static_files:: Here's a complete example of a simple API with OpenAPI documentation: -```rust +```rust,has_main use cot::cli::CliMetadata; use cot::config::ProjectConfig; use cot::json::Json; use cot::openapi::swagger_ui::SwaggerUi; -use cot::project::{MiddlewareContext, RegisterAppsContext, RootHandlerBuilder}; +use cot::project::{MiddlewareContext, RegisterAppsContext, RootHandlerBuilder, RootHandler}; use cot::router::method::openapi::api_post; use cot::router::{Route, Router}; use cot::static_files::StaticFilesMiddleware; -use cot::{App, AppBuilder, BoxedHandler, Project}; +use cot::{App, AppBuilder, Project}; use serde::{Deserialize, Serialize}; #[derive(Deserialize, schemars::JsonSchema)] @@ -153,7 +153,7 @@ async fn add(Json(add_request): Json) -> cot::Result BoxedHandler { + ) -> RootHandler { handler .middleware(StaticFilesMiddleware::from_context(context)) .build() @@ -224,10 +224,14 @@ use cot::request::extractors::Path; async fn get_user(Path(user_id): Path) -> cot::Result { // ... +# todo!() } +# use cot::router::method::openapi::api_get; +# fn foo() { // Register the route -Route::with_api_handler("/users/{user_id}", api_get(get_user)) +Route::with_api_handler("/users/{user_id}", api_get(get_user)); +# } ``` ### URL Query Parameters @@ -245,10 +249,14 @@ struct UserQuery { async fn list_users(UrlQuery(query): UrlQuery) -> cot::Result { // ... +# todo!() } +# use cot::router::method::openapi::api_get; +# fn foo() { // Register the route -Route::with_api_handler("/users", api_get(list_users)) +Route::with_api_handler("/users", api_get(list_users)); +# } ``` ### Excluding Routes from OpenAPI Documentation @@ -256,23 +264,35 @@ Route::with_api_handler("/users", api_get(list_users)) Sometimes you might want to exclude certain routes from your API documentation. You can do this by using [`NoApi`](struct@cot::openapi::NoApi): ```rust -use cot::openapi::NoApi; - +# use cot::openapi::NoApi; +# use cot::router::method::openapi::api_get; +# async fn visible_handler(_request: cot::request::Request) -> cot::Result { todo!() } +# async fn hidden_handler(_request: cot::request::Request) -> cot::Result { todo!() } +# fn foo() { // This handler will be in the API docs -Route::with_api_handler("/visible", api_get(visible_handler)) +Route::with_api_handler("/visible", api_get(visible_handler)); // This handler will work but won't appear in the docs -Route::with_api_handler("/hidden", api_get(NoApi(hidden_handler))) +Route::with_api_handler("/hidden", api_get(NoApi(hidden_handler))); +# } ``` You can also exclude specific parameters from the OpenAPI docs: ```rust +# use cot::request::extractors::{FromRequestHead, Path}; +# use cot::request::RequestHead; +# use cot::openapi::NoApi; +# struct MyContext; +# impl FromRequestHead for MyContext { +# async fn from_request_head(_head: &RequestHead) -> cot::Result { todo!() } +# } async fn handler( Path(id): Path, // Included in OpenAPI docs NoApi(context): NoApi, // Excluded from OpenAPI docs ) -> cot::Result { // ... implementation + todo!() } ``` @@ -281,8 +301,12 @@ async fn handler( The [`ApiMethodRouter`](struct@cot::router::method::openapi::ApiMethodRouter) allows you to define multiple HTTP methods for a single route and include them all in the OpenAPI documentation: ```rust -use cot::router::method::openapi::ApiMethodRouter; - +# async fn list_items() -> cot::Result { todo!() } +# async fn create_item() -> cot::Result { todo!() } +# async fn update_item() -> cot::Result { todo!() } +# async fn delete_item() -> cot::Result { todo!() } +# use cot::router::method::openapi::ApiMethodRouter; +# fn foo() { Route::with_api_handler( "/items", ApiMethodRouter::new() @@ -290,7 +314,8 @@ Route::with_api_handler( .post(create_item) .put(update_item) .delete(delete_item) -) +); +# } ``` Each method will be properly documented in the OpenAPI specification. diff --git a/docs/site/Cargo.lock b/docs/site/Cargo.lock index c909c67c..56008fa7 100644 --- a/docs/site/Cargo.lock +++ b/docs/site/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.0", + "bitflags", "bytes", "futures-core", "futures-sink", @@ -29,9 +29,9 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags 2.11.0", + "bitflags", "bytes", - "derive_more 2.1.1", + "derive_more", "futures-core", "http-range", "log", @@ -52,27 +52,27 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "base64 0.22.1", - "bitflags 2.11.0", + "base64", + "bitflags", "brotli", "bytes", "bytestring", - "derive_more 2.1.1", + "derive_more", "encoding_rs", "flate2", - "foldhash", + "foldhash 0.1.5", "futures-core", "h2", "http 0.2.12", "httparse", "httpdate", - "itoa 1.0.17", + "itoa", "language-tags", "local-channel", "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.2", + "rand 0.9.3", "sha1", "smallvec", "tokio", @@ -172,13 +172,13 @@ dependencies = [ "bytestring", "cfg-if", "cookie 0.16.2", - "derive_more 2.1.1", + "derive_more", "encoding_rs", - "foldhash", + "foldhash 0.1.5", "futures-core", "futures-util", "impl-more", - "itoa 1.0.17", + "itoa", "language-tags", "log", "mime", @@ -350,7 +350,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] @@ -368,12 +368,12 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "askama" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08e1676b346cadfec169374f949d7490fd80a24193d37d2afce0c047cf695e57" +checksum = "9b8246bcbf8eb97abef10c2d92166449680d41d55c0fc6978a91dec2e3619608" dependencies = [ "askama_macros", - "itoa 1.0.17", + "itoa", "percent-encoding", "serde", "serde_json", @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "askama_derive" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7661ff56517787343f376f75db037426facd7c8d3049cef8911f1e75016f3a37" +checksum = "2f9670bc84a28bb3da91821ef74226949ab63f1265aff7c751634f1dd0e6f97c" dependencies = [ "askama_parser", "basic-toml", @@ -398,18 +398,18 @@ dependencies = [ [[package]] name = "askama_macros" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713ee4dbfd1eb719c2dab859465b01fa1d21cb566684614a713a6b7a99a4e47b" +checksum = "f0756b45480437dded0565dfc568af62ccce146fb6cfe902e808ba86e445f44f" dependencies = [ "askama_derive", ] [[package]] name = "askama_parser" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d62d674238a526418b30c0def480d5beadb9d8964e7f38d635b03bf639c704c" +checksum = "5d0af3691ba3af77949c0b5a3925444b85cb58a0184cc7fec16c68ba2e7be868" dependencies = [ "rustc-hash", "serde", @@ -467,7 +467,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "itoa 1.0.17", + "itoa", "matchit", "memchr", "mime", @@ -514,12 +514,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -559,15 +553,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -578,10 +563,13 @@ dependencies = [ ] [[package]] -name = "bit-vec" -version = "0.6.3" +name = "bit-set" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" +dependencies = [ + "bit-vec 0.9.1", +] [[package]] name = "bit-vec" @@ -590,10 +578,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "bitflags" -version = "1.3.2" +name = "bit-vec" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] [[package]] name = "bitflags" @@ -624,16 +615,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.3.0", ] [[package]] @@ -691,26 +682,6 @@ dependencies = [ "alloc-stdlib", ] -[[package]] -name = "brownstone" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5839ee4f953e811bfdcf223f509cb2c6a3e1447959b0bff459405575bc17f22" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata 0.1.10", -] - [[package]] name = "bumpalo" version = "3.20.2" @@ -739,12 +710,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.11.1" @@ -771,9 +736,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -890,7 +855,7 @@ dependencies = [ "bon", "caseless", "clap", - "emojis 0.8.0", + "emojis", "entities", "finl_unicode", "fmt2io", @@ -907,9 +872,9 @@ dependencies = [ [[package]] name = "config-derive" -version = "0.11.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7329955b015b82dbcf7bf217f85cbcc016a1a825bf3b074093cd39a5c071a60c" +checksum = "4c547326a30684f853601fb959cc8ecbd0d72abbdd27ba634850a918fa29afc4" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -919,15 +884,14 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", "unicode-width", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -976,12 +940,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -1000,6 +958,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.16.2" @@ -1046,7 +1013,7 @@ dependencies = [ "cot_core", "cot_macros", "derive_builder", - "derive_more 2.1.1", + "derive_more", "email_address", "form_urlencoded", "futures-core", @@ -1056,7 +1023,7 @@ dependencies = [ "http 1.4.0", "http-body-util", "humantime", - "indexmap 2.13.0", + "indexmap 2.14.0", "mime", "mime_guess", "multer", @@ -1067,7 +1034,7 @@ dependencies = [ "thiserror 2.0.18", "time", "tokio", - "toml 1.0.6+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "tower", "tower-livereload", "tower-sessions", @@ -1146,14 +1113,14 @@ dependencies = [ "backtrace", "bytes", "cot_macros", - "derive_more 2.1.1", + "derive_more", "form_urlencoded", "futures-core", "futures-util", "http 1.4.0", "http-body", "http-body-util", - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_html_form", "serde_json", @@ -1189,6 +1156,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1235,31 +1211,27 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "9be934d936a0fbed5bcdc01042b770de1398bf79d0e192f49fa7faea0e99281e" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", + "itoa", + "phf 0.11.3", "smallvec", - "syn 1.0.109", ] [[package]] name = "cssparser" -version = "0.33.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be934d936a0fbed5bcdc01042b770de1398bf79d0e192f49fa7faea0e99281e" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 1.0.17", - "phf 0.11.3", + "itoa", + "phf 0.13.1", "smallvec", ] @@ -1420,19 +1392,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -1516,15 +1475,6 @@ dependencies = [ "serde", ] -[[package]] -name = "emojis" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e1f1df1f181f2539bac8bf027d31ca5ffbf9e559e3f2d09413b9107b5c02f4" -dependencies = [ - "phf 0.11.3", -] - [[package]] name = "emojis" version = "0.8.0" @@ -1587,15 +1537,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ "bit-set 0.8.0", - "regex-automata 0.4.14", + "regex-automata", "regex-syntax", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" @@ -1637,6 +1587,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1740,15 +1696,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1759,17 +1706,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -1779,7 +1715,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -1819,7 +1755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.13.0", + "indexmap 2.14.0", "lasso", "once_cell", "phf 0.11.3", @@ -1838,7 +1774,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1856,29 +1792,32 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.12", - "serde", + "allocator-api2", ] [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "ahash 0.8.12", "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -1915,7 +1854,7 @@ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", - "itoa 1.0.17", + "itoa", ] [[package]] @@ -1925,7 +1864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "itoa 1.0.17", + "itoa", ] [[package]] @@ -1977,9 +1916,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1989,9 +1928,8 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.17", + "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", ] @@ -2037,12 +1975,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2050,9 +1989,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2063,9 +2002,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2077,15 +2016,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2097,15 +2036,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2168,12 +2107,6 @@ dependencies = [ "quote", ] -[[package]] -name = "indent_write" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" - [[package]] name = "indexmap" version = "1.9.3" @@ -2186,12 +2119,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2212,16 +2145,19 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "0.4.8" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jetscii" @@ -2239,17 +2175,11 @@ dependencies = [ "libc", ] -[[package]] -name = "joinery" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" - [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", @@ -2276,12 +2206,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lexical-core" version = "1.0.6" @@ -2341,9 +2265,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libyml" @@ -2362,15 +2286,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6314c2f0590ac93c86099b98bb7ba8abcf759bfd89604ffca906472bb54937" dependencies = [ "ahash 0.8.12", - "bitflags 2.11.0", + "bitflags", "const-str", "cssparser 0.33.0", "cssparser-color", "dashmap", "data-encoding", "getrandom 0.3.4", - "indexmap 2.13.0", - "itertools", + "indexmap 2.14.0", + "itertools 0.10.5", "lazy_static", "lightningcss-derive", "parcel_selectors", @@ -2409,9 +2333,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "local-channel" @@ -2447,22 +2371,21 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lol_html" -version = "0.4.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1610d7994d67a05bb35861cd733b069b1171de8693bc8452849c59361a1bb87b" +checksum = "5ff94cb6aef6ee52afd2c69331e9109906d855e82bd241f3110dfdf6185899ab" dependencies = [ - "bitflags 2.11.0", + "bitflags", "cfg-if", - "cssparser 0.27.2", + "cssparser 0.36.0", "encoding_rs", - "hashbrown 0.13.2", - "lazy_static", - "lazycell", + "foldhash 0.2.0", + "hashbrown 0.16.1", "memchr", "mime", - "safemem", + "precomputed-hash", "selectors", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -2521,9 +2444,12 @@ dependencies = [ [[package]] name = "minifier" -version = "0.2.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5394aa376422b4b2b6c02fd9cfcb657e4ec544ae98e43d7d5d785fd0d042fd6d" +checksum = "14f1541610994bba178cb36757e102d06a52a2d9612aa6d34c64b3b377c5d943" +dependencies = [ + "clap", +] [[package]] name = "minimal-lexical" @@ -2543,13 +2469,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] @@ -2571,10 +2497,10 @@ dependencies = [ ] [[package]] -name = "nodrop" -version = "0.1.14" +name = "new_debug_unreachable" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" @@ -2586,24 +2512,11 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom-supreme" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd3ae6c901f1959588759ff51c95d24b491ecb9ff91aa9c2ef4acc5b1dcab27" -dependencies = [ - "brownstone", - "indent_write", - "joinery", - "memchr", - "nom", -] - [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -2641,7 +2554,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.11.0", + "bitflags", "libc", "once_cell", "onig_sys", @@ -2665,24 +2578,24 @@ checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" [[package]] name = "pagefind" -version = "1.4.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1749da595766c0ac7c4f961fdb208388076dec5bedae0be95478bc5b164290" +checksum = "aa7d3c661e0e18b6c9eec1305908baf2bd9564af247727f9e7f4b4d25cd2da39" dependencies = [ "actix-files", "actix-web", "anyhow", "async-compression", - "base64 0.21.7", - "bit-set 0.5.3", + "base64", + "bit-set 0.10.0", "clap", "console", - "convert_case 0.6.0", + "convert_case 0.11.0", "either", - "emojis 0.6.4", + "emojis", "flate2", "futures", - "hashbrown 0.13.2", + "hashbrown 0.16.1", "html-escape", "include_dir", "lazy_static", @@ -2693,23 +2606,26 @@ dependencies = [ "pagefind_stem", "path-slash", "portpicker", + "rayon", "regex", "rust-patch", "serde", "serde_json", "sha-1", + "tikv-jemallocator", "tokio", "twelf", "typed-builder", + "unicode-normalization", "unicode-segmentation", "wax", ] [[package]] name = "pagefind_stem" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b9cf5d3cd867dd32e54385d85ecfda45c6f2f896a9d464426ab564e7391467" +checksum = "8dfa810b158f3ac364e5acd43ca4a6020a6e729d40c15ce1bed1d911237a52e5" [[package]] name = "parcel_selectors" @@ -2717,7 +2633,7 @@ version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54fd03f1ad26cb6b3ec1b7414fa78a3bd639e7dbb421b1a60513c96ce886a196" dependencies = [ - "bitflags 2.11.0", + "bitflags", "cssparser 0.33.0", "log", "phf 0.11.3", @@ -2811,17 +2727,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - [[package]] name = "phf" version = "0.11.3" @@ -2847,20 +2752,11 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ + "phf_macros 0.13.1", "phf_shared 0.13.1", "serde", ] -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - [[package]] name = "phf_codegen" version = "0.11.3" @@ -2881,16 +2777,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - [[package]] name = "phf_generator" version = "0.11.3" @@ -2911,20 +2797,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "phf_macros" version = "0.11.3" @@ -2939,12 +2811,16 @@ dependencies = [ ] [[package]] -name = "phf_shared" -version = "0.8.0" +name = "phf_macros" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "siphasher 0.3.11", + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2953,7 +2829,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 1.0.2", + "siphasher", ] [[package]] @@ -2962,7 +2838,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" dependencies = [ - "siphasher 1.0.2", + "siphasher", ] [[package]] @@ -2971,7 +2847,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "siphasher 1.0.2", + "siphasher", ] [[package]] @@ -2980,17 +2856,11 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" @@ -2998,8 +2868,8 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ - "base64 0.22.1", - "indexmap 2.13.0", + "base64", + "indexmap 2.14.0", "quick-xml", "serde", "time", @@ -3025,9 +2895,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -3096,12 +2966,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.106" @@ -3161,20 +3025,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - [[package]] name = "rand" version = "0.8.5" @@ -3188,24 +3038,14 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -3226,15 +3066,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -3253,24 +3084,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rayon" version = "1.11.0" @@ -3297,7 +3110,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -3308,16 +3121,10 @@ checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.14", + "regex-automata", "regex-syntax", ] -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - [[package]] name = "regex-automata" version = "0.4.14" @@ -3408,9 +3215,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -3427,7 +3234,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -3446,12 +3253,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "same-file" version = "1.0.6" @@ -3475,29 +3276,28 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "selectors" -version = "0.22.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7" dependencies = [ - "bitflags 1.3.2", - "cssparser 0.27.2", - "derive_more 0.99.20", - "fxhash", + "bitflags", + "cssparser 0.36.0", + "derive_more", "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", "precomputed-hash", + "rustc-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -3545,7 +3345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0946d52b4b7e28823148aebbeceb901012c595ad737920d504fa8634bb099e6f" dependencies = [ "form_urlencoded", - "indexmap 2.13.0", + "indexmap 2.14.0", "serde_core", ] @@ -3555,7 +3355,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "itoa 1.0.17", + "itoa", "memchr", "serde", "serde_core", @@ -3568,16 +3368,16 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ - "itoa 1.0.17", + "itoa", "serde", "serde_core", ] [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -3589,7 +3389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.17", + "itoa", "ryu", "serde", ] @@ -3612,8 +3412,8 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap 2.13.0", - "itoa 1.0.17", + "indexmap 2.14.0", + "itoa", "libyml", "memchr", "ryu", @@ -3623,11 +3423,10 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" dependencies = [ - "nodrop", "stable_deref_trait", ] @@ -3638,7 +3437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3649,7 +3448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3686,9 +3485,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simdutf8" @@ -3696,12 +3495,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.2" @@ -3833,20 +3626,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "terminal_size" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.69" @@ -3887,6 +3674,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.3.47" @@ -3894,7 +3701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", - "itoa 1.0.17", + "itoa", "num-conv", "powerfmt", "serde_core", @@ -3920,9 +3727,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3945,9 +3752,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ "bytes", "libc", @@ -3962,9 +3769,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3995,9 +3802,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "serde_core", "serde_spanned", @@ -4008,20 +3815,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "toml_datetime", "toml_parser", "winnow", @@ -4029,9 +3836,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] @@ -4118,11 +3925,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568531ec3dfcf3ffe493de1958ae5662a0284ac5d767476ecdb6a34ff8c6b06c" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "futures", "http 1.4.0", "parking_lot", - "rand 0.9.2", + "rand 0.9.3", "serde", "serde_json", "thiserror 2.0.18", @@ -4177,9 +3984,9 @@ dependencies = [ [[package]] name = "twelf" -version = "0.11.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6b76f0d5feab6eeb6a36900c5e1f6867f5061ce87917acc3d1c2d985db5212" +checksum = "16de46d08a9d3a25e0a65bb70090797b970bd1e95d72872567ba8d02c0b03bdf" dependencies = [ "clap", "config-derive", @@ -4200,18 +4007,18 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typed-builder" -version = "0.20.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9d30e3a08026c78f246b173243cf07b3696d274debd26680773b6773c2afc7" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.20.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" dependencies = [ "proc-macro2", "quote", @@ -4247,9 +4054,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -4296,9 +4103,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "js-sys", "wasm-bindgen", @@ -4332,12 +4139,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -4355,9 +4156,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -4368,9 +4169,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4378,9 +4179,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -4391,28 +4192,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] [[package]] name = "wax" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c7a3bac6110ac062b7b422a442b7ee23e07209e2784a036654cab1e71bbafc" +checksum = "1f8cbf8125142b9b30321ac8721f54c52fbcd6659f76cf863d5e2e38c07a3d7b" dependencies = [ - "bstr", "const_format", - "itertools", + "itertools 0.14.0", "nom", - "nom-supreme", "pori", "regex", - "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.18", "walkdir", ] @@ -4490,25 +4288,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -4526,31 +4306,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -4559,101 +4322,53 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" -version = "0.7.15" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] @@ -4666,9 +4381,9 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -4696,9 +4411,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4707,9 +4422,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4719,18 +4434,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4739,18 +4454,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4760,9 +4475,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4771,9 +4486,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4782,9 +4497,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/docs/static-files.md b/docs/static-files.md index bdafa32a..4b333920 100644 --- a/docs/static-files.md +++ b/docs/static-files.md @@ -15,7 +15,9 @@ The Cot CLI generates a `static` directory in your project root, which serves as To serve static files, you'll need to register them in your application's [`static_files()`](trait@cot::project::App#method.static_files) method within the `CotApp` implementation. Here's a basic example: ```rust +# struct MyApp; impl App for MyApp { +# fn name(&self) -> &str { "test" } fn static_files(&self) -> Vec { static_files!("css/main.css") } @@ -25,7 +27,9 @@ impl App for MyApp { To add more files, simply include them in the [`static_files!`](macro@cot::static_files) macro. For example, after adding a logo to your project: ```rust +# struct MyApp; impl App for MyApp { +# fn name(&self) -> &str { "test" } fn static_files(&self) -> Vec { static_files!( "css/main.css", @@ -83,6 +87,8 @@ This command aggregates all static files into the specified directory (in this c If you prefer not to serve static files through the Cot server, you can disable this functionality by removing the [`StaticFilesMiddleware`](struct@cot::static_files::StaticFilesMiddleware) from your project configuration: ```rust +# struct MyProject; +# impl Project for MyProject { fn middlewares( &self, handler: RootHandlerBuilder, @@ -93,6 +99,7 @@ fn middlewares( // ... .build() } +# } ``` Simply remove the `.middleware(StaticFilesMiddleware ...)` line to disable static file serving. diff --git a/docs/templates.md b/docs/templates.md index 50c2f9f5..b6aac968 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -228,10 +228,10 @@ The `{% match %}` tag matches a value against a set of Rust patterns. Use `{% wh #### Example ```html.j2 -{% match user.role %} +{% match user.role.as_deref() %} {% when Some with ("admin") %} Welcome, admin! - {% when Some %} + {% when Some with (_val) %} Welcome, user! {% when None %} {% endmatch %} @@ -329,8 +329,8 @@ When rendered, it will display the `title` from the `Item` struct. By default, Askama escapes all output to protect against XSS attacks. Special characters are replaced with their HTML entities. If you’re certain your data is safe and want to bypass escaping, you can implement the `HtmlSafe` marker trait. ```rust -use askama::filters::HtmlSafe; - +# struct Item; +# impl Display for Item { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } } impl HtmlSafe for Item {} ``` @@ -339,6 +339,7 @@ Be very cautious when marking output as safe; you are responsible for ensuring t To simplify generating safe HTML in Rust, Cot provides the [`HtmlTag`](struct@cot::html::HtmlTag) type. It automatically applies escaping where necessary. ```rust +# struct Item { title: String } impl Display for Item { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut tag = HtmlTag::input("text"); diff --git a/docs/testing.md b/docs/testing.md index 2bf0ceef..34af305e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -7,6 +7,7 @@ Cot includes various built-in utilities to help you test your application. This ## Why Test at All? Testing is a critical part of any application development process. By writing and running tests, you can: + 1. **Ensure Code Reliability** – Tests catch bugs and regressions before they reach production, increasing overall stability and confidence in your application. 2. **Document Your Code** – Tests serve as living documentation. They show how different parts of your application are supposed to work and can act as examples for future maintainers. 3. **Facilitate Refactoring** – With a robust test suite, you can safely modify or refactor your code. If something breaks, your tests will let you know right away. @@ -21,10 +22,12 @@ By employing Cot's testing utilities, you'll be able to verify that each piece o Cot provides several built-in utilities located in the [`cot::test` module](cot::test) to help you create and run tests for your application. Typical Rust projects keep their tests in: + - A dedicated `tests/` directory (for integration tests). - A `mod tests` section in your source files (for unit tests). You can run all your tests by executing: + ```bash cargo test ``` @@ -59,7 +62,7 @@ let request = TestRequestBuilder::post("/") // Add JSON data let request = TestRequestBuilder::post("/") - .json(&your_data) + .json(&[("key", "value")]) .build(); ``` @@ -79,18 +82,19 @@ Integration tests check how multiple parts of your application work together. Co The [`Client`](struct@cot::test::Client) struct lets you create a temporary instance of your Cot application and perform HTTP requests against it: ```rust -let project = CotProject::builder() - .register_app_with_views(MyApp, "/app") - .build().await?; +# struct MyProject; +# impl Project for MyProject { +# fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) { todo!() } +# } // Create a new test client -let mut client = Client::new(project); +let mut client = Client::new(MyProject).await; // Make GET requests let response = client.get("/").await?; // Make custom requests -let request = http::Request::get("/").body(Body::empty())?; +let request = http::Request::get("/").body(Body::empty()).unwrap(); let response = client.request(request).await?; ``` @@ -149,6 +153,7 @@ test_db.cleanup().await?; test_db.with_auth().run_migrations().await; let request = TestRequestBuilder::get("/") .with_db_auth(test_db.database()) + .await .build(); ``` This ensures that your tests have all necessary schema and authentication information set up. diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 153446bb..0e8de223 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -19,7 +19,7 @@ Sometimes, though, the changes need to be made in a backwards-incompatible manne ### Forms * **Attribute Rename**: The `opt` attribute parameter in `#[form(...)]` macro has been renamed to `opts`. - ```rust + ```rust,ignore // Before #[form(opt(max_length = 100))] @@ -44,8 +44,8 @@ Sometimes, though, the changes need to be made in a backwards-incompatible manne * "Not Found" handler support has been removed. Instead, there is a single project-global error handler that handles both "Not Found", "Internal Server Error", and other errors that may occur during request processing. * The error handler is now almost a regular request handler (meaning you don't have to implement the `ErrorHandler` trait manually) and can access most of the request data, such as request path, method, headers, but also static files, root router URLs, and more. - - The main difference between a regular request handler and an error handler is that the error handler may receive an additional argument of type `RequestError`, which contains information about the error that occurred during request processing. - - On the other hand, it can **not** receive the request body, as it might have been consumed already. + - The main difference between a regular request handler and an error handler is that the error handler may receive an additional argument of type `RequestError`, which contains information about the error that occurred during request processing. + - On the other hand, it can **not** receive the request body, as it might have been consumed already. * `Project::server_error_handler` method is now called `error_handler` and returns a `DynErrorPageHandler`. ### Dependencies diff --git a/justfile b/justfile index 6bc5eb7a..ab5239fa 100644 --- a/justfile +++ b/justfile @@ -73,3 +73,8 @@ test-ignored: docker compose up -d --wait cargo nextest run --all-features --run-ignored only docker compose down + +alias td := test-docs + +test-docs: + cargo nextest run -p cot-test