Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"cot-cli",
"cot-codegen",
"cot-macros",
"cot-core",
# Examples
"examples/admin",
"examples/custom-error-pages",
Expand Down Expand Up @@ -76,6 +77,7 @@ clap-verbosity-flag = { version = "3", default-features = false }
clap_complete = "4"
clap_mangen = "0.2.31"
cot = { version = "0.4.0", path = "cot" }
cot_core = { version = "0.4.0", path = "cot-core" }
cot_codegen = { version = "0.4.0", path = "cot-codegen" }
cot_macros = { version = "0.4.0", path = "cot-macros" }
criterion = "0.8"
Expand Down
49 changes: 49 additions & 0 deletions cot-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[package]
name = "cot_core"
version = "0.4.0"
description = "The Rust web framework for lazy developers - framework core."
categories = ["web-programming", "web-programming::http-server", "network-programming"]
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

[lints]
workspace = true

[dependencies]
http.workspace = true
derive_more = { workspace = true, features = ["debug", "deref", "display", "from"] }
thiserror.workspace = true
serde.workspace = true
serde_json.workspace = true
backtrace.workspace = true
bytes.workspace = true
futures-core.workspace = true
http-body.workspace = true
http-body-util.workspace = true
sync_wrapper.workspace = true
axum.workspace = true
cot_macros.workspace = true
askama = { workspace = true, features = ["alloc"] }
tower-sessions.workspace = true
serde_path_to_error.workspace = true
indexmap.workspace = true
serde_html_form.workspace = true
form_urlencoded.workspace = true
tower.workspace = true
futures-util.workspace = true

[dev-dependencies]
async-stream.workspace = true
cot = { workspace = true, features = ["test"] }
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The cot-core crate has a circular dev-dependency on the main cot crate. This creates a circular dependency structure where cot depends on cot_core, and cot_core has a dev-dependency on cot for tests. While this is technically allowed for dev-dependencies in Rust, it can complicate the build process and may cause issues with certain tools or workflows. Consider whether the tests in cot-core can be restructured to avoid depending on the main cot crate, or whether they can be moved to the main cot crate's test suite instead.

Suggested change
cot = { workspace = true, features = ["test"] }

Copilot uses AI. Check for mistakes.
futures.workspace = true
tokio.workspace = true

[features]
default = []
json = []
12 changes: 7 additions & 5 deletions cot/src/body.rs → cot-core/src/body.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! HTTP body type.
//!
//! This module provides the [`Body`] type for representing HTTP bodies,
//! supporting both fixed in-memory buffers and streaming data sources.

use std::error::Error as StdError;
use std::fmt::{Debug, Formatter};
use std::pin::Pin;
Expand All @@ -9,7 +14,7 @@ use http_body::{Frame, SizeHint};
use http_body_util::combinators::BoxBody;
use sync_wrapper::SyncWrapper;

use crate::error::error_impl::impl_into_cot_error;
use crate::error::impl_into_cot_error;
use crate::{Error, Result};

/// A type that represents an HTTP request or response body.
Expand Down Expand Up @@ -166,7 +171,7 @@ impl Body {
}

#[must_use]
pub(crate) fn axum(inner: axum::body::Body) -> Self {
pub fn axum(inner: axum::body::Body) -> Self {
Self::new(BodyInner::Axum(SyncWrapper::new(inner)))
}

Expand Down Expand Up @@ -261,9 +266,6 @@ impl_into_cot_error!(ReadRequestBody, BAD_REQUEST);

#[cfg(test)]
mod tests {
use std::pin::Pin;
use std::task::{Context, Poll};

use futures::stream;
use http_body::Body as HttpBody;

Expand Down
14 changes: 14 additions & 0 deletions cot-core/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Error handling types and utilities for Cot applications.
//!
//! This module provides error types, error handlers, and utilities for
//! handling various types of errors that can occur in Cot applications,
//! including 404 Not Found errors, uncaught panics, and custom error pages.

pub mod backtrace;
pub(crate) mod error_impl;
mod method_not_allowed;
mod uncaught_panic;

pub use error_impl::{Error, impl_into_cot_error};
pub use method_not_allowed::MethodNotAllowed;
pub use uncaught_panic::UncaughtPanic;
13 changes: 7 additions & 6 deletions cot/src/error/backtrace.rs → cot-core/src/error/backtrace.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// inline(never) is added to make sure there is a separate frame for this
// function so that it can be used to find the start of the backtrace.
#[inline(never)]
pub(crate) fn __cot_create_backtrace() -> Backtrace {
#[must_use]
pub fn __cot_create_backtrace() -> Backtrace {
let mut backtrace = Vec::new();
let mut start = false;
backtrace::trace(|frame| {
Expand All @@ -21,19 +22,19 @@ pub(crate) fn __cot_create_backtrace() -> Backtrace {
}

#[derive(Debug, Clone)]
pub(crate) struct Backtrace {
pub struct Backtrace {
frames: Vec<StackFrame>,
}

impl Backtrace {
#[must_use]
pub(crate) fn frames(&self) -> &[StackFrame] {
pub fn frames(&self) -> &[StackFrame] {
&self.frames
}
}

#[derive(Debug, Clone)]
pub(crate) struct StackFrame {
pub struct StackFrame {
symbol_name: Option<String>,
filename: Option<String>,
lineno: Option<u32>,
Expand All @@ -42,15 +43,15 @@ pub(crate) struct StackFrame {

impl StackFrame {
#[must_use]
pub(crate) fn symbol_name(&self) -> String {
pub fn symbol_name(&self) -> String {
self.symbol_name
.as_deref()
.unwrap_or("<unknown>")
.to_string()
}

#[must_use]
pub(crate) fn location(&self) -> String {
pub fn location(&self) -> String {
if let Some(filename) = self.filename.as_deref() {
let mut s = filename.to_owned();

Expand Down
46 changes: 3 additions & 43 deletions cot/src/error/error_impl.rs → cot-core/src/error/error_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use crate::StatusCode;
// Need to rename Backtrace to CotBacktrace, because otherwise it triggers special behavior
// in the thiserror library
use crate::error::backtrace::{__cot_create_backtrace, Backtrace as CotBacktrace};
use crate::error::not_found::NotFound;

/// An error that can occur while using Cot.
pub struct Error {
Expand Down Expand Up @@ -151,46 +150,6 @@ impl Error {
Self::internal(error)
}

/// Create a new "404 Not Found" error without a message.
///
/// # Examples
///
/// ```
/// use cot::Error;
///
/// let error = Error::not_found();
/// ```
#[must_use]
#[deprecated(
note = "Use `cot::Error::from(cot::error::NotFound::new())` instead",
since = "0.4.0"
)]
pub fn not_found() -> Self {
Self::from(NotFound::new())
}

/// Create a new "404 Not Found" error with a message.
///
/// Note that the message is only displayed when Cot's debug mode is
/// enabled. It will not be exposed to the user in production.
///
/// # Examples
///
/// ```
/// use cot::Error;
///
/// let id = 123;
/// let error = Error::not_found_message(format!("User with id={id} not found"));
/// ```
#[must_use]
#[deprecated(
note = "Use `cot::Error::from(cot::error::NotFound::with_message())` instead",
since = "0.4.0"
)]
pub fn not_found_message(message: String) -> Self {
Self::from(NotFound::with_message(message))
}

/// Returns the HTTP status code associated with this error.
///
/// This method returns the appropriate HTTP status code that should be
Expand All @@ -216,7 +175,7 @@ impl Error {
}

#[must_use]
pub(crate) fn backtrace(&self) -> &CotBacktrace {
pub fn backtrace(&self) -> &CotBacktrace {
Copy link
Member

Choose a reason for hiding this comment

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

This is an implementation detail and cannot be exposed to users. The CotBacktrace struct is (rightly) not even reexported in Cot.

Copy link
Member Author

@seqre seqre Jan 18, 2026

Choose a reason for hiding this comment

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

It's problematic, it has to be public so that the error page can use it to show backtrace properly. The easiest solution would be to pull that to cot, but then we would have to pull Error as well and it's impossible as it's used in cot_core. Alternatively, we could pull ErrorPage to cot_core, but it's not really core and it's specific to cot. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I think the error struct should be kept in the core crate. I can see several approaches here:

  1. #[doc(hidden)] - the easiest way. Perhaps it's good enough (I think some popular crates such as tokio and serde do use it), but it doesn't really stop the users from calling the method.
  2. Defining an extension trait (similar to ResponseExt, for example), and implementing it for the Error type. The extension trait is then public in cot-core, but not reexported in cot. This way the method will be invisible to the normal users, but still accessible if you use cot-core directly.
  3. Sort of similar to the one above: defining an access token. The code explains it all:
mod private {
    pub struct Token;
}

pub struct MyStruct;

impl MyStruct {
    pub fn restricted_method(&self, _: private::Token) {
        println!("Restricted!");
    }
}

#[doc(hidden)]
pub fn get_token() -> private::Token {
    private::Token
}

I'll let you pick the favorite one for the job.

Copy link
Member Author

Choose a reason for hiding this comment

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

I went with option 1. I want to keep this PR as change-light as possible and if it's used like that by other crates, I suppose it's good enough. If it's ever become a problem, we can change it then.

&self.repr.backtrace
}

Expand Down Expand Up @@ -319,6 +278,7 @@ impl From<Error> for askama::Error {
}
}

#[macro_export]
macro_rules! impl_into_cot_error {
($error_ty:ty) => {
impl From<$error_ty> for $crate::Error {
Expand All @@ -335,7 +295,7 @@ macro_rules! impl_into_cot_error {
}
};
}
pub(crate) use impl_into_cot_error;
pub use impl_into_cot_error;

#[derive(Debug, thiserror::Error)]
#[error("failed to render template: {0}")]
Expand Down
File renamed without changes.
Loading
Loading