Skip to content

docs: migrate docs from cot-site#489

Open
m4tx wants to merge 21 commits intomasterfrom
docs2
Open

docs: migrate docs from cot-site#489
m4tx wants to merge 21 commits intomasterfrom
docs2

Conversation

@m4tx
Copy link
Member

@m4tx m4tx commented Feb 23, 2026

This migrates the guide from the cot-site to cot repository, as discussed in #471.

This PR should be merged or rebased into master, not squashed (!) to keep the attribution of the specific authors.

m4tx and others added 21 commits February 17, 2025 23:36
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>
git-subtree-dir: docs
git-subtree-mainline: 65ca557
git-subtree-split: e79c39c
Copilot AI review requested due to automatic review settings February 23, 2026 19:47
@github-actions
Copy link

🐰 Bencher Report

Branchdocs2
Testbedgithub-ubuntu-latest
Click to view all benchmark results
BenchmarkLatencyBenchmark 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%)
🐰 View full continuous benchmarking report in Bencher

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +101 to +110
fn middlewares(
&self,
handler: RootHandlerBuilder,
context: &MiddlewareContext,
) -> BoxedHandler {
// StaticFilesMiddleware is required for SwaggerUI to serve its assets
handler
.middleware(StaticFilesMiddleware::from_context(context))
.build()
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +32
```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"
```
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +23
[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)
```
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +36
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
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
{{ field|safe }}

<ul class="errors">
{% for error in form_context.errors_for(FormErrorTarget::Field(field.dyn_id())) %}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
{% for error in form_context.errors_for(FormErrorTarget::Field(field.dyn_id())) %}
{% for error in form.errors_for(FormErrorTarget::Field(field.dyn_id())) %}

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +94
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?;
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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(())
}

Copilot uses AI. Check for mistakes.
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.
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.
}
```

Now, when you visit [`localhost:8000/hello/John/Smith/`](http://localhost:8000/hello/John), you should see `Hello, John Smith!` displayed on the page!
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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!

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +141

async fn hello_name(Path(name): Path<String>) -> cot::Result<Html> {
Ok(Html::new(format!("Hello, {}!", name)))
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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)))

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +159
async fn hello_name(Path((first_name, last_name)): Path<(String, String)>) -> cot::Result<Html> {
Ok(Html::new(format!("Hello, {first_name} {last_name}!")))
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

Flag Coverage Δ
rust 89.65% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@seqre
Copy link
Member

seqre commented Feb 24, 2026

Did you intend to include only the single version docs and exclude the general pages and older versions?

@m4tx
Copy link
Member Author

m4tx commented Feb 24, 2026

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants