Skip to content
Merged
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
49 changes: 49 additions & 0 deletions src/catcher.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
use crate::Status;
use pyo3::prelude::*;

/// A catcher for handling specific HTTP status codes.
///
/// Catchers allow you to provide custom responses for specific HTTP status codes.
/// They are typically created using the `catcher` decorator function.
///
/// Args:
/// status (Status): The HTTP status code this catcher will handle.
/// handler (callable): The handler function that will be called when this status occurs.
///
/// Example:
/// ```python
/// from oxapy import catcher, Status
///
/// @catcher(Status.NOT_FOUND)
/// def handle_not_found(request, response):
/// return Response("<h1>Custom 404 Page</h1>", content_type="text/html")
/// ```
#[pyclass]
pub struct Catcher {
pub status: Status,
pub handler: Py<PyAny>,
}

/// Internal builder class for creating catchers.
///
/// This class is returned by the `catcher` function and is used to create
/// a Catcher when called with a handler function.
#[pyclass]
pub struct CatcherBuilder {
status: Status,
}

#[pymethods]
impl CatcherBuilder {
/// Create a Catcher when called with a handler function.
///
/// Args:
/// handler (callable): The handler function to call when the status occurs.
///
/// Returns:
/// Catcher: A new catcher for the specified status.
fn __call__(&self, handler: Py<PyAny>) -> Catcher {
Catcher {
status: self.status,
Expand All @@ -22,6 +50,27 @@ impl CatcherBuilder {
}
}

/// Decorator for creating status code catchers.
///
/// A catcher allows you to provide custom responses for specific HTTP status codes.
///
/// Args:
/// status (Status): The HTTP status code to catch.
///
/// Returns:
/// CatcherBuilder: A builder that creates a Catcher when called with a handler function.
///
/// Example:
/// ```python
/// from oxapy import catcher, Status, Response
///
/// @catcher(Status.NOT_FOUND)
/// def handle_404(request, response):
/// return Response("<h1>Page Not Found</h1>", content_type="text/html")
///
/// # Add the catcher to your server
/// app.catchers([handle_404])
/// ```
#[pyfunction]
pub fn catcher(status: Status) -> CatcherBuilder {
CatcherBuilder { status }
Expand Down
65 changes: 65 additions & 0 deletions src/cors.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
use crate::{response::Response, status::Status};
use pyo3::prelude::*;

/// Cross-Origin Resource Sharing (CORS) configuration.
///
/// This class allows you to configure CORS headers for your server to control
/// which domains can access your API and what methods they can use.
///
/// Args:
/// None
///
/// Returns:
/// Cors: A new CORS configuration with default settings.
///
/// Example:
/// ```python
/// from oxapy import HttpServer, Cors
///
/// app = HttpServer(("127.0.0.1", 8000))
///
/// # Set up CORS with custom configuration
/// cors = Cors()
/// cors.origins = ["https://example.com", "https://app.example.com"]
/// cors.methods = ["GET", "POST", "OPTIONS"]
/// cors.headers = ["Content-Type", "Authorization"]
///
/// app.cors(cors)
/// ```
#[derive(Clone, Debug)]
#[pyclass]
pub struct Cors {
/// List of allowed origins, default is ["*"] (all origins)
#[pyo3(get, set)]
pub origins: Vec<String>,
/// List of allowed HTTP methods, default includes common methods
#[pyo3(get, set)]
pub methods: Vec<String>,
/// List of allowed HTTP headers, default includes common headers
#[pyo3(get, set)]
pub headers: Vec<String>,
/// Whether to allow credentials (cookies, authorization headers), default is true
#[pyo3(get, set)]
pub allow_credentials: bool,
/// Maximum age of preflight requests in seconds, default is 86400 (1 day)
#[pyo3(get, set)]
pub max_age: u32,
}
Expand All @@ -30,11 +60,29 @@ impl Default for Cors {

#[pymethods]
impl Cors {
/// Create a new CORS configuration with default settings.
///
/// Returns:
/// Cors: A new CORS configuration with default values.
///
/// Example:
/// ```python
/// # Create CORS with default configuration (allows all origins)
/// cors = Cors()
///
/// # Customize CORS settings
/// cors.origins = ["https://example.com"]
/// cors.allow_credentials = False
/// ```
#[new]
fn new() -> Self {
Self::default()
}

/// Return a string representation of the CORS configuration.
///
/// Returns:
/// str: A debug string showing the CORS configuration.
fn __repr__(&self) -> String {
format!("{:#?}", self.clone())
}
Expand All @@ -49,6 +97,16 @@ impl From<Cors> for Response {
}

impl Cors {
/// Apply CORS headers to a response.
///
/// This is an internal method used to add all configured CORS headers
/// to an existing response.
///
/// Args:
/// response (Response): The response to modify.
///
/// Returns:
/// None
pub fn apply_headers(&self, response: &mut Response) {
response.insert_header("Access-Control-Allow-Origin", self.origins.join(", "));
response.insert_header("Access-Control-Allow-Methods", self.methods.join(", "));
Expand All @@ -59,6 +117,13 @@ impl Cors {
response.insert_header("Access-Control-Max-Age", self.max_age.to_string());
}

/// Apply CORS headers to a response and return the modified response.
///
/// Args:
/// response (Response): The response to modify with CORS headers.
///
/// Returns:
/// Response: The modified response with CORS headers.
pub fn apply_to_response(&self, mut response: Response) -> PyResult<Response> {
self.apply_headers(&mut response);
Ok(response)
Expand Down
43 changes: 37 additions & 6 deletions src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,22 @@ pub struct Jwt {

#[pymethods]
impl Jwt {
/// Create a new JWT manager
/// Create a new JWT
///
/// Args:
/// secret: Secret key used for signing tokens
/// algorithm: JWT algorithm to use (default: "HS256")
/// secret (str): Secret key used for signing tokens
/// algorithm (str): JWT algorithm to use (default: "HS256")
///
/// Returns:
/// A new JwtManager instance
/// Jwt: A new Jwt instance
///
/// Raises:
/// ValueError: If the algorithm is not supported or secret is invalid

/// Exception: If the algorithm is not supported or secret is invalid
///
/// Example:
/// ```python
/// jwt = Jwt(secret="mysecret", algorithm="HS256")
/// ```
#[new]
#[pyo3(signature = (secret, algorithm="HS256"))]
pub fn new(secret: String, algorithm: &str) -> PyResult<Self> {
Expand All @@ -73,6 +77,18 @@ impl Jwt {
///
/// Raises:
/// Exception: If claims cannot be serialized or the token cannot be generated
///
/// Example:
/// ```python
/// claims = {
/// "exp": 3600, # seconds from now
/// "sub": "user123", # subject (optional)
/// "iss": "myapp", # issuer (optional)
/// "aud": "webapp", # audience (optional)
/// "nbf": 1234567890 # not before timestamp (optional)
/// }
/// token = jwt.generate_token(claims)
/// ```
pub fn generate_token(&self, claims: Bound<'_, PyDict>) -> PyResult<String> {
let expiration = claims
.get_item("exp")?
Expand Down Expand Up @@ -104,6 +120,21 @@ impl Jwt {
Ok(token)
}

/// Verify the integrity of the JWT token
///
/// Args:
/// token: A JWT token String
///
/// Returns:
/// Return Dictionary: the claims that you use to generate the token
///
/// Raises:
/// JwtError: if token was expired or not valid token
///
/// Example:
/// ```python
/// jwt.verify_token("mytoken")
/// ```
pub fn verify_token(&self, token: &str) -> PyResult<Py<PyDict>> {
let token_data = jsonwebtoken::decode::<Claims>(
token,
Expand Down
Loading