Conversation
Read admin username and password from the environment variable
…ng bracket) (#16) Co-authored-by: Kecci Kun <abyan.kecci@stockbit.com>
* feat: setup guide version handling * feat: make version switcher work * feat: automatically grab all versions * feat: handle too high versions * feat: show old version alert * chore: remove repeated disclaimer * feat: make content dependent on version * fix: review comments
Using `use cot::request::extractors::Urls;` yields an error 'struct Urls is private'
* feat: add v0.3 docs * one more fix
* feat: cot v0.4 initial docs, "master" version * more fixes * more fixes * fixes * fix * add v0.4
Co-authored-by: Mateusz Maćkowski <m4tx@m4tx.pl>
|
| Branch | docs2 |
| Testbed | github-ubuntu-latest |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result microseconds (µs) (Result Δ%) | Upper Boundary microseconds (µs) (Limit %) |
|---|---|---|---|
| empty_router/empty_router | 📈 view plot 🚷 view threshold | 5,762.30 µs(-2.52%)Baseline: 5,911.03 µs | 7,035.76 µs (81.90%) |
| json_api/json_api | 📈 view plot 🚷 view threshold | 1,045.50 µs(+2.69%)Baseline: 1,018.13 µs | 1,160.03 µs (90.13%) |
| nested_routers/nested_routers | 📈 view plot 🚷 view threshold | 959.95 µs(+2.31%)Baseline: 938.32 µs | 1,063.71 µs (90.25%) |
| single_root_route/single_root_route | 📈 view plot 🚷 view threshold | 925.81 µs(+2.96%)Baseline: 899.18 µs | 1,021.53 µs (90.63%) |
| single_root_route_burst/single_root_route_burst | 📈 view plot 🚷 view threshold | 19,134.00 µs(+8.91%)Baseline: 17,567.98 µs | 20,789.21 µs (92.04%) |
There was a problem hiding this comment.
Pull request overview
This PR migrates the Cot user guide content from cot-site into the cot repository, adding a set of documentation chapters covering core framework topics and an upgrade guide.
Changes:
- Added new documentation chapters for core Cot concepts (intro, templates, forms, models, admin, static files, caching, email, OpenAPI, testing).
- Added an upgrade guide covering breaking changes between releases.
- Added a framework comparison page.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/introduction.md | New introductory walkthrough for getting started with Cot and basic routing/extractors. |
| docs/templates.md | New chapter describing Askama templating and Cot URL reversing in templates. |
| docs/forms.md | New chapter explaining form derivation, validation, and template rendering patterns. |
| docs/db-models.md | New chapter documenting ORM model definitions and common DB operations. |
| docs/admin-panel.md | New chapter describing enabling and using the admin panel. |
| docs/static-files.md | New chapter explaining static file registration, URLs, and caching/versioning. |
| docs/caching.md | New chapter documenting cache configuration and usage examples. |
| docs/sending-emails.md | New chapter documenting email feature configuration and sending emails. |
| docs/openapi.md | New chapter documenting OpenAPI generation and Swagger UI integration. |
| docs/testing.md | New chapter documenting test utilities (request builder, client, test DB/cache, e2e). |
| docs/error-pages.md | New chapter explaining debug/default error pages and custom error handlers. |
| docs/framework-comparison.md | New page comparing Cot with other Rust web frameworks. |
| docs/upgrade-guide.md | New upgrade guide covering notable changes between versions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn middlewares( | ||
| &self, | ||
| handler: RootHandlerBuilder, | ||
| context: &MiddlewareContext, | ||
| ) -> BoxedHandler { | ||
| // StaticFilesMiddleware is required for SwaggerUI to serve its assets | ||
| handler | ||
| .middleware(StaticFilesMiddleware::from_context(context)) | ||
| .build() | ||
| } |
There was a problem hiding this comment.
In Project::middlewares, the return type should be RootHandler (built from RootHandlerBuilder::build()), not BoxedHandler. The current signature won’t compile against the Project trait.
| ```toml | ||
| [email] | ||
| from = "no-reply@example.com" # Default sender address (optional) | ||
|
|
||
| [email.transport] | ||
| type = "smtp" # Options: "smtp", "console" | ||
| url = "smtp://user:password@localhost:587" # For SMTP | ||
| mechanism = "plain" # or "login", "xoauth2" | ||
| ``` |
There was a problem hiding this comment.
The config example includes [email] from = ..., but EmailConfig currently only supports [email.transport] (no from field). Either remove the [email].from setting or update it to match the actual config schema.
| [cache] | ||
| prefix = "myapp" # Optional: prefix for all cache keys | ||
| max_retries = 3 # Optional: max retries for cache operations (default: 3) | ||
| timeout = "5s" # Optional: timeout for cache operations (default: 5s) | ||
|
|
||
| [cache.store] | ||
| type = "memory" # Options: "memory", "redis", "file" (if enabled) | ||
| ``` |
There was a problem hiding this comment.
The cache config docs state the default timeout is 5s, but the default Timeout is 300s (5 minutes) in cot::config (Timeout::default()). Please correct the documented default to avoid confusing users.
| use cot::html::Html; | ||
| use cot::request::{Request, RequestExt}; | ||
| use cot::response::{Response, ResponseExt}; | ||
|
|
||
| async fn contact(mut request: Request) -> cot::Result<Response> { | ||
| // Handle POST request (form submission) | ||
| if request.method() == Method::POST { | ||
| match ContactForm::from_request(&mut request).await? { | ||
| FormResult::Ok(form) => { | ||
| // Form is valid! Process the data |
There was a problem hiding this comment.
This handler compares request.method() to Method::POST, but Method isn’t imported in the snippet. Add use cot::Method; (or qualify cot::Method::POST) so the example compiles.
| {{ field|safe }} | ||
|
|
||
| <ul class="errors"> | ||
| {% for error in form_context.errors_for(FormErrorTarget::Field(field.dyn_id())) %} |
There was a problem hiding this comment.
In the template example, errors are rendered from form_context, but the loop iterates over form and there’s no form_context variable defined. This is likely meant to be form (the context returned by FormResult::ValidationError). Rename consistently so the example is usable.
| {% for error in form_context.errors_for(FormErrorTarget::Field(field.dyn_id())) %} | |
| {% for error in form.errors_for(FormErrorTarget::Field(field.dyn_id())) %} |
| let project = CotProject::builder() | ||
| .register_app_with_views(MyApp, "/app") | ||
| .build().await?; | ||
|
|
||
| // Create a new test client | ||
| let mut client = Client::new(project); | ||
|
|
||
| // Make GET requests | ||
| let response = client.get("/").await?; | ||
|
|
||
| // Make custom requests | ||
| let request = http::Request::get("/").body(Body::empty())?; | ||
| let response = client.request(request).await?; |
There was a problem hiding this comment.
The integration test example references CotProject::builder() and constructs Client::new(project) without .await, but CotProject doesn’t exist and cot::test::Client::new is async (it must be awaited). Update the snippet to construct a minimal impl Project and call Client::new(MyProject).await (see cot/src/test.rs docs).
| let project = CotProject::builder() | |
| .register_app_with_views(MyApp, "/app") | |
| .build().await?; | |
| // Create a new test client | |
| let mut client = Client::new(project); | |
| // Make GET requests | |
| let response = client.get("/").await?; | |
| // Make custom requests | |
| let request = http::Request::get("/").body(Body::empty())?; | |
| let response = client.request(request).await?; | |
| // A minimal project type that implements `cot::Project`. | |
| // In your real tests, use your actual project type here. | |
| struct MyProject; | |
| #[tokio::test] | |
| async fn integration_test_example() -> cot::Result<()> { | |
| // Create a new test client | |
| 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 response = client.request(request).await?; | |
| Ok(()) | |
| } |
| title: Sending Emails | ||
| --- | ||
|
|
||
| Cot provides a unified interface for sending emails, allowing you to switch between different email backends (like SMTP, Memory, or Console) easily. This is powered by the popular [`lettre`](https://crates.io/crates/lettre) crate. |
There was a problem hiding this comment.
The intro claims email backends include “Memory”, but Cot’s email transports are console and smtp (see EmailTransportTypeConfig). Either remove “Memory” or document it only if/when a memory transport exists.
| Cot provides a unified interface for sending emails, allowing you to switch between different email backends (like SMTP, Memory, or Console) easily. This is powered by the popular [`lettre`](https://crates.io/crates/lettre) crate. | |
| Cot provides a unified interface for sending emails, allowing you to switch between different email backends (like SMTP or Console) easily. This is powered by the popular [`lettre`](https://crates.io/crates/lettre) crate. |
| } | ||
| ``` | ||
|
|
||
| Now, when you visit [`localhost:8000/hello/John/Smith/`](http://localhost:8000/hello/John), you should see `Hello, John Smith!` displayed on the page! |
There was a problem hiding this comment.
The rendered link text suggests visiting /hello/John/Smith/, but the actual hyperlink points to /hello/John (missing /Smith/). Update the URL so the link matches the example route.
| Now, when you visit [`localhost:8000/hello/John/Smith/`](http://localhost:8000/hello/John), you should see `Hello, John Smith!` displayed on the page! | |
| Now, when you visit [`localhost:8000/hello/John/Smith/`](http://localhost:8000/hello/John/Smith/), you should see `Hello, John Smith!` displayed on the page! |
|
|
||
| async fn hello_name(Path(name): Path<String>) -> cot::Result<Html> { | ||
| Ok(Html::new(format!("Hello, {}!", name))) |
There was a problem hiding this comment.
The hello_name handler builds an HTML response by interpolating the untrusted name path parameter directly into Html::new(format!("Hello, {}!", name)) without any HTML escaping, which enables reflected XSS. An attacker could craft a URL like /hello/<script>alert(1)</script> that causes arbitrary script execution in the victim’s browser. Instead of formatting raw strings into Html, ensure user-controlled values are HTML-escaped (for example, render via a template engine that escapes by default or use an API that safely escapes values before constructing Html).
| async fn hello_name(Path(name): Path<String>) -> cot::Result<Html> { | |
| Ok(Html::new(format!("Hello, {}!", name))) | |
| use html_escape::encode_text; | |
| async fn hello_name(Path(name): Path<String>) -> cot::Result<Html> { | |
| let escaped_name = encode_text(&name); | |
| Ok(Html::new(format!("Hello, {}!", escaped_name))) |
| async fn hello_name(Path((first_name, last_name)): Path<(String, String)>) -> cot::Result<Html> { | ||
| Ok(Html::new(format!("Hello, {first_name} {last_name}!"))) |
There was a problem hiding this comment.
The second hello_name example also constructs an Html response using format!("Hello, {first_name} {last_name}!") with unescaped path parameters, which similarly allows reflected XSS via crafted URLs containing HTML/JavaScript payloads. Because Html::new treats the string as already-safe HTML, any tags or scripts in first_name/last_name will be rendered directly in the browser. Use an approach that HTML-escapes these values (for example, rendering through Askama templates or another escaping-aware helper) instead of interpolating raw path parameters into HTML.
Codecov Report✅ All modified and coverable lines are covered by tests.
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
|
Did you intend to include only the single version docs and exclude the general pages and older versions? |
|
Yes, as per #471 the idea is to provide older versions by using git submodules. There's no point in storing this on the repo - we've got a VCS for that. |
This migrates the guide from the
cot-sitetocotrepository, as discussed in #471.This PR should be merged or rebased into
master, not squashed (!) to keep the attribution of the specific authors.