Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgehermo9 authored Jan 18, 2025
2 parents 86dbbb8 + be4e44f commit 2681485
Show file tree
Hide file tree
Showing 24 changed files with 460 additions and 9 deletions.
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: loco-rs
4 changes: 4 additions & 0 deletions .github/workflows/loco-gen-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ on:
push:
branches:
- master
paths:
- "loco-gen/**"
pull_request:
paths:
- "loco-gen/**"

env:
RUST_TOOLCHAIN: stable
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/loco-gen-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: "[loco-gen-deploy]"

on:
push:
branches:
- master
pull_request:

env:
RUST_TOOLCHAIN: stable
TOOLCHAIN_PROFILE: minimal

jobs:
g-deploy-docker:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout the code
uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2

- name: Install seaorm cli
run: cargo install sea-orm-cli

- name: install 'loco new'
run: |
cargo install --path ./loco-new
- name: create myapp
run: |
loco new -n myapp --db sqlite --bg async --assets serverside -a
- name:
run: cargo loco generate deployment --kind docker && docker build -t demo .
working-directory: ./myapp

4 changes: 4 additions & 0 deletions .github/workflows/loco-gen-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ on:
push:
branches:
- master
paths:
- "loco-gen/**"
pull_request:
paths:
- "loco-gen/**"

env:
RUST_TOOLCHAIN: stable
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/loco-rs-ci-sanity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# To optimize CI runtime:
# A simpler "sanity check" workflow is introduced.
# This workflow only runs if changes in the PR do NOT include
# the `loco-gen` or `loco-new` paths.
# (When changes are made to `loco-gen` or `loco-new`,
# we run comprehensive tests to validate every generator command
# and template option.)

# Purpose of the sanity check:
# It performs basic validation by comparing the local changes
# against the templates.
# If any breaking changes are detected in the templates,
# the sanity check will fail, signaling an issue.

name: "[loco_rs:sanity]"

on:
push:
branches:
- master
paths-ignore:
- "loco-gen/**"
- "loco-new/**"
pull_request:
paths-ignore:
- "loco-gen/**"
- "loco-new/**"

env:
RUST_TOOLCHAIN: stable
TOOLCHAIN_PROFILE: minimal

jobs:
sanity:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout the code
uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2

- name: Install seaorm cli
run: cargo install sea-orm-cli

- run: cargo install --path loco-new

- run: |
loco new -n myappdb --db sqlite --bg async --assets serverside -a
cd myappdb
cargo check
env:
LOCO_DEV_MODE_PATH: ${{ github.workspace }}
- run: |
loco new -n myappnodb --db none --bg none --assets none -a
cd myappdb
cargo check
env:
LOCO_DEV_MODE_PATH: ${{ github.workspace }}
22 changes: 21 additions & 1 deletion .github/workflows/loco-rs-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,30 @@ jobs:
command: clippy
args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms

test:
check:
needs: [style]
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout the code
uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-hack
- run: cargo hack check --each-feature

test:
needs: [check, style]
runs-on: ubuntu-latest

permissions:
contents: read

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
* fix: bump shuttle to 0.51.0. [https://github.com/loco-rs/loco/pull/1169](https://github.com/loco-rs/loco/pull/1169)
* Return 422 status code for JSON rejection errors. [https://github.com/loco-rs/loco/pull/1173](https://github.com/loco-rs/loco/pull/1173)
* Address clippy warnings for Rust stable 1.84. [https://github.com/loco-rs/loco/pull/1168](https://github.com/loco-rs/loco/pull/1168)
* Bump shuttle to 0.51.0. [https://github.com/loco-rs/loco/pull/1169](https://github.com/loco-rs/loco/pull/1169)
* return 422 status code for JSON rejection errors. [https://github.com/loco-rs/loco/pull/1173](https://github.com/loco-rs/loco/pull/1173)
* return json validation details response. [https://github.com/loco-rs/loco/pull/1174](https://github.com/loco-rs/loco/pull/1174)
* fix example command after generating schedule. [https://github.com/loco-rs/loco/pull/1176](https://github.com/loco-rs/loco/pull/1176)
* fixed independent features. [https://github.com/loco-rs/loco/pull/1177](https://github.com/loco-rs/loco/pull/1177)


## v0.14
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ lettre = { version = "0.11.4", default-features = false, features = [
include_dir = "0.7.3"
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
tracing-appender = "0.2.3"

duct = { version = "0.13.6" }
Expand Down Expand Up @@ -146,6 +146,7 @@ english-to-cron = { version = "0.1.2" }
# bg_sqlt: sqlite workers
# bg_pg: postgres workers
sqlx = { version = "0.8.2", default-features = false, features = [
"json",
"postgres",
"chrono",
"sqlite",
Expand Down
2 changes: 1 addition & 1 deletion docs-site/content/docs/getting-started/axum-users.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,4 @@ pub fn routes() -> Routes {
### Verdict

* **A drop-in compatibility** - Loco uses Axum and keeps all of its building blocks intact so that you can just use your own existing Axum code with no efforts.
* **Route metadata for free** - one gap that Axum routers has is the ability to describe the currently configured routes, which can be used for listing or automatic OpenAPI schema generation. Loco has a small metadata layer to suppor this. If you use `Routes` you get it for free, while all of the different signatures remain compatible with Axum router.
* **Route metadata for free** - one gap that Axum routers has is the ability to describe the currently configured routes, which can be used for listing or automatic OpenAPI schema generation. Loco has a small metadata layer to support this. If you use `Routes` you get it for free, while all of the different signatures remain compatible with Axum router.
64 changes: 64 additions & 0 deletions docs-site/content/docs/the-app/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,70 @@ impl Hooks for App {
}
```

# Request Validation
`JsonValidate` extractor simplifies input [validation](https://github.com/Keats/validator) by integrating with the validator crate. Here's an example of how to validate incoming request data:

### Define Your Validation Rules
```rust
use axum::debug_handler;
use loco_rs::prelude::*;
use serde::Deserialize;
use validator::Validate;

#[derive(Debug, Deserialize, Validate)]
pub struct DataParams {
#[validate(length(min = 5, message = "custom message"))]
pub name: String,
#[validate(email)]
pub email: String,
}
```
### Create a Handler with Validation
```rust
use axum::debug_handler;
use loco_rs::prelude::*;

#[debug_handler]
pub async fn index(
State(_ctx): State<AppContext>,
JsonValidate(params): JsonValidate<DataParams>,
) -> Result<Response> {
format::empty()
}
```
Using the `JsonValidate` extractor, Loco automatically performs validation on the DataParams struct:
* If validation passes, the handler continues execution with params.
* If validation fails, a 400 Bad Request response is returned.

### Returning Validation Errors as JSON
If you'd like to return validation errors in a structured JSON format, use `JsonValidateWithMessage` instead of `JsonValidate`. The response format will look like this:

```json
{
"errors": {
"email": [
{
"code": "email",
"message": null,
"params": {
"value": "ad"
}
}
],
"name": [
{
"code": "length",
"message": "custom message",
"params": {
"min": 5,
"value": "d"
}
}
]
}
}
```

# Pagination

In many scenarios, when querying data and returning responses to users, pagination is crucial. In `Loco`, we provide a straightforward method to paginate your data and maintain a consistent pagination response schema for your API responses.
Expand Down
2 changes: 1 addition & 1 deletion loco-gen/src/templates/deployment/docker/docker.t
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ COPY --from=builder /usr/src/{{fallback_file}} /usr/app/{{fallback_file}}
COPY --from=builder /usr/src/config /usr/app/config
COPY --from=builder /usr/src/target/release/{{pkg_name}}-cli /usr/app/{{pkg_name}}-cli

ENTRYPOINT ["/usr/app/{{pkg_name}}-cli"]
ENTRYPOINT ["/usr/app/{{pkg_name}}-cli"]
2 changes: 1 addition & 1 deletion loco-gen/src/templates/scheduler/scheduler.t
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
to: "config/scheduler.yaml"
skip_exists: true
message: "A Scheduler job configuration was added successfully. Run with `cargo loco run scheduler --list`."
message: "A Scheduler job configuration was added successfully. Run with `cargo loco scheduler --list`."

---
output: stdout
Expand Down
2 changes: 1 addition & 1 deletion loco-gen/tests/templates/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn can_generate() {

assert_eq!(
collect_messages(&gen_result),
r"* A Scheduler job configuration was added successfully. Run with `cargo loco run scheduler --list`.
r"* A Scheduler job configuration was added successfully. Run with `cargo loco scheduler --list`.
"
);

Expand Down
3 changes: 3 additions & 0 deletions loco-new/tests/wizard/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ fn test_combination(
// Generate deployment nginx
tester.run_generate(&vec!["deployment", "--kind", "docker"]);

// Generate deployment shuttle
tester.run_generate(&vec!["deployment", "--kind", "shuttle"]);

if db.enable() {
// Generate Model
if !settings.auth {
Expand Down
1 change: 1 addition & 0 deletions src/controller/extractor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod validate;
78 changes: 78 additions & 0 deletions src/controller/extractor/validate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::Error;
use axum::extract::{Form, FromRequest, Json, Request};
use serde::de::DeserializeOwned;
use validator::Validate;

#[derive(Debug, Clone, Copy, Default)]
pub struct JsonValidateWithMessage<T>(pub T);

impl<T, S> FromRequest<S> for JsonValidateWithMessage<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = Error;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(value) = Json::<T>::from_request(req, state).await?;
value.validate()?;
Ok(Self(value))
}
}

#[derive(Debug, Clone, Copy, Default)]
pub struct FormValidateWithMessage<T>(pub T);

impl<T, S> FromRequest<S> for FormValidateWithMessage<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = Error;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Form(value) = Form::<T>::from_request(req, state).await?;
value.validate()?;
Ok(Self(value))
}
}

#[derive(Debug, Clone, Copy, Default)]
pub struct JsonValidate<T>(pub T);

impl<T, S> FromRequest<S> for JsonValidate<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = Error;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(value) = Json::<T>::from_request(req, state).await?;
value.validate().map_err(|err| {
tracing::debug!(err = ?err, "request validation error occurred");
Error::BadRequest(String::new())
})?;
Ok(Self(value))
}
}

#[derive(Debug, Clone, Copy, Default)]
pub struct FormValidate<T>(pub T);

impl<T, S> FromRequest<S> for FormValidate<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = Error;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Form(value) = Form::<T>::from_request(req, state).await?;
value.validate().map_err(|err| {
tracing::debug!(err = ?err, "request validation error occurred");
Error::BadRequest(String::new())
})?;
Ok(Self(value))
}
}
Loading

0 comments on commit 2681485

Please sign in to comment.