Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OpenAPI integration #984

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8ed9348
inti
NexVeridian Nov 12, 2024
6deca2b
hook initial_openapi_spec
NexVeridian Nov 13, 2024
def3dd5
config.yaml and merge hosted doc with router
NexVeridian Nov 13, 2024
8db4443
update existing tests
NexVeridian Nov 13, 2024
34d10e6
test: OpenAPI config from file
NexVeridian Nov 14, 2024
c887042
snapshot tests
NexVeridian Nov 14, 2024
4ed2668
fix snapshot path
NexVeridian Nov 14, 2024
2aa3d66
match title, upload snapshots
NexVeridian Nov 14, 2024
0a304c1
OpenAPI json snapshot test
NexVeridian Nov 14, 2024
8c0fd96
Another snapshot
NexVeridian Nov 16, 2024
5f63b08
LocoMethodRouter
NexVeridian Nov 16, 2024
1c67cc6
fix AppContext
NexVeridian Nov 19, 2024
c1e953e
missing cfg for tests
NexVeridian Nov 19, 2024
74f78cc
clippy
NexVeridian Nov 19, 2024
ac0f1ce
openapi.josn and openapi.yaml endpoints for all types
NexVeridian Nov 20, 2024
2082e12
SecurityAddon
NexVeridian Nov 21, 2024
ea48919
fix cfg flag for import
NexVeridian Nov 23, 2024
c849c73
Merge branch 'upstream' into OpenAPI-integration
NexVeridian Nov 27, 2024
022909d
fix: snapshots and imports
NexVeridian Nov 27, 2024
165d162
Merge branch 'master' into OpenAPI-integration
kaplanelad Nov 27, 2024
00abce3
split feature openapi into feature swagger-ui redoc scalar, extract s…
NexVeridian Nov 28, 2024
2e7e750
move OpenAPIType.url
NexVeridian Dec 2, 2024
e27be7f
drop test for JWT_LOCATION.get_or_init, not possible with cargo test
NexVeridian Dec 2, 2024
b848348
rstest feature flagged cases
NexVeridian Dec 2, 2024
7a76f3a
some docs
NexVeridian Dec 3, 2024
3781c13
Merge branch 'master' into OpenAPI-integration
NexVeridian Dec 3, 2024
162ae44
clippy
NexVeridian Dec 3, 2024
bef0eb2
Merge branch 'master' into OpenAPI-integration
NexVeridian Dec 13, 2024
e1a64a6
docs: docs-site OpenAPI
NexVeridian Dec 13, 2024
8872a10
docs: SecurityAddon
NexVeridian Dec 14, 2024
e352e88
feat: format::yaml
NexVeridian Dec 16, 2024
dc97291
tests: add headers content-type to snapshots
NexVeridian Dec 16, 2024
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
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ auth_jwt = ["dep:jsonwebtoken"]
cli = ["dep:clap"]
testing = ["dep:axum-test", "dep:scraper"]
with-db = ["dep:sea-orm", "dep:sea-orm-migration", "loco-gen/with-db"]
# OpenAPI features
all_openapi = ["openapi_swagger", "openapi_redoc", "openapi_scalar"]
openapi_swagger = ["dep:utoipa", "dep:utoipa-axum", "dep:utoipa-swagger-ui"]
openapi_redoc = ["dep:utoipa", "dep:utoipa-axum", "dep:utoipa-redoc"]
openapi_scalar = ["dep:utoipa", "dep:utoipa-axum", "dep:utoipa-scalar"]
# Storage features
all_storage = ["storage_aws_s3", "storage_azure", "storage_gcp"]
storage_aws_s3 = ["object_store/aws"]
Expand Down Expand Up @@ -129,6 +134,16 @@ cfg-if = "1"

uuid = { version = "1.10.0", features = ["v4", "fast-rng"] }

# OpenAPI
utoipa = { version = "5.0.0", features = ["yaml"], optional = true }
utoipa-axum = { version = "0.1.0", optional = true }
utoipa-swagger-ui = { version = "8.0", features = [
"axum",
"vendored",
], optional = true }
utoipa-redoc = { version = "5.0.0", features = ["axum"], optional = true }
utoipa-scalar = { version = "0.2.0", features = ["axum"], optional = true }

# File Upload
object_store = { version = "0.11.0", default-features = false }

Expand Down
130 changes: 130 additions & 0 deletions docs-site/content/docs/the-app/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,136 @@ impl Hooks for App {
}
```

# OpenAPI Integration Setup
The Loco OpenAPI integration is generated using [`Utopia`](https://github.com/juhaku/utoipa)

## `Cargo.toml` features flages
Edit your `Cargo.toml` file and add one or multiple of the following features flages:
- `swagger-ui`
- `redoc`
- `scalar`
- `all_openapi`

```toml
loco-rs = { version = "0.13", features = ["scalar"] }
```

## Configuration
Add the corresponding OpenAPI visualizer to the config file
```yaml
#...
server:
...
openapi:
redoc:
!Redoc
url: /redoc
# spec_json_url: /redoc/openapi.json
# spec_yaml_url: /redoc/openapi.yaml
scalar:
!Scalar
url: /scalar
# spec_json_url: /scalar/openapi.json
# spec_yaml_url: /scalar/openapi.yaml
swagger:
!Swagger
url: /swagger-ui
spec_json_url: /api-docs/openapi.json # spec_json_url is required for swagger-ui
# spec_yaml_url: /api-docs/openapi.yaml
```
## Inital OpenAPI Spec
Modifies the OpenAPI spec before the routes are added, allowing you to edit [`openapi::info`](https://docs.rs/utoipa/latest/utoipa/openapi/info/struct.Info.html)

```rust
// src/app.rs
use utoipa::OpenApi;
use loco_rs::auth::openapi::{set_jwt_location_ctx, SecurityAddon};

impl Hooks for App {
#...
fn inital_openapi_spec(ctx: &AppContext) -> utoipa::openapi::OpenApi {
set_jwt_location_ctx(ctx);

#[derive(OpenApi)]
#[openapi(
modifiers(&SecurityAddon),
info(
title = "Loco Demo",
description = "This app is a kitchensink for various capabilities and examples of the [Loco](https://loco.rs) project."
)
)]
struct ApiDoc;
ApiDoc::openapi()
}
```

## Generating the OpenAPI spec for a route
Only routes that are annotated with [`utoipa::path`](https://docs.rs/utoipa/latest/utoipa/attr.path.html) will be included in the OpenAPI spec.

```rust
#[utoipa::path(
get,
path = "/album",
responses(
(status = 200, description = "Album found", body = Album),
),
)]
async fn get_action_openapi() -> Result<Response> {
format::json(Album {
title: "VH II".to_string(),
rating: 10,
})
}
```

Make sure to add `#[derive(ToSchema)]` on any struct that included in [`utoipa::path`](https://docs.rs/utoipa/latest/utoipa/attr.path.html).
```rust
use utoipa::ToSchema;

#[derive(Serialize, Debug, ToSchema)]
pub struct Album {
title: String,
rating: u32,
}
```

If `modifiers(&SecurityAddon)` is set in `inital_openapi_spec`, you can document the per route security in `utoipa::path`:
- `security(("jwt_token" = []))`
- `security(("api_key" = []))`
- or leave blank to remove security from the route `security(())`

Example:
```rust
#[utoipa::path(
get,
path = "/album",
security(("jwt_token" = [])),
responses(
(status = 200, description = "Album found", body = Album),
),
)]
```

## Adding routes to the OpenAPI spec visualizer
Swap the `axum::routing::MethodFilter` to `routes!`
### Before
```rust
Routes::new()
.add("/album", get(get_action_openapi)),
```
### After
```rust
use utoipa_axum::routes;

Routes::new()
.add("/album", routes!(get_action_openapi)),
```
### Note: do not add multiple routes inside the `routes!` macro
```rust
Routes::new()
.add("/album", routes!(get_action_1_do_not_do_this, get_action_2_do_not_do_this)),
```

# 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
170 changes: 170 additions & 0 deletions examples/demo/config/OpenAPI.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Loco configuration file documentation

# Application logging configuration
logger:
# Enable or disable logging.
enable: false
# Log level, options: trace, debug, info, warn or error.
level: error
# Define the logging format. options: compact, pretty or json
format: compact
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
# override_filter: trace

# Web server configuration
server:
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
port: 5150
# The UI hostname or IP address that mailers will point to.
host: http://localhost
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
middlewares:
# Allows to limit the payload size request. payload that bigger than this file will blocked the request.
limit_payload:
# Enable/Disable the middleware.
enable: true
# the limit size. can be b,kb,kib,mb,mib,gb,gib
body_limit: 5mb
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
logger:
# Enable/Disable the middleware.
enable: true
# when your code is panicked, the request still returns 500 status code.
catch_panic:
# Enable/Disable the middleware.
enable: true
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
timeout_request:
# Enable/Disable the middleware.
enable: true
# Duration time in milliseconds.
timeout: 5000
static_assets:
enable: true
must_exist: true
precompressed: true
folder:
path: assets
fallback: index.html
compression:
enable: true
cors:
enable: true
# Set the value of the [`Access-Control-Allow-Origin`][mdn] header
# allow_origins:
# - https://loco.rs
# Set the value of the [`Access-Control-Allow-Headers`][mdn] header
# allow_headers:
# - Content-Type
# Set the value of the [`Access-Control-Allow-Methods`][mdn] header
# allow_methods:
# - POST
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
# max_age: 3600
openapi:
redoc:
!Redoc
url: /redoc
spec_json_url: /redoc/openapi.json
spec_yaml_url: /redoc/openapi.yaml
scalar:
!Scalar
url: /scalar
spec_json_url: /scalar/openapi.json
spec_yaml_url: /scalar/openapi.yaml
swagger:
!Swagger
url: /swagger-ui
spec_json_url: /api-docs/openapi.json
spec_yaml_url: /api-docs/openapi.yaml

# Worker Configuration
workers:
# specifies the worker mode. Options:
# - BackgroundQueue - Workers operate asynchronously in the background, processing queued.
# - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed.
# - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities.
mode: ForegroundBlocking

# Mailer Configuration.
mailer:
# SMTP mailer configuration.
smtp:
# Enable/Disable smtp mailer.
enable: true
# SMTP server host. e.x localhost, smtp.gmail.com
host: localhost
# SMTP server port
port: 1025
# Use secure connection (SSL/TLS).
secure: false
# auth:
# user:
# password:
stub: true

# Initializers Configuration
# initializers:
# oauth2:
# authorization_code: # Authorization code grant type
# - client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config.
# ... other fields

# Database Configuration
database:
# Database connection URI
uri: {{get_env(name="DATABASE_URL", default="postgres://loco:loco@localhost:5432/loco_app")}}
# When enabled, the sql query will be logged.
enable_logging: false
# Set the timeout duration when acquiring a connection.
connect_timeout: 500
# Set the idle duration before closing a connection.
idle_timeout: 500
# Minimum number of connections for a pool.
min_connections: 1
# Maximum number of connections for a pool.
max_connections: 1
# Run migration up when application loaded
auto_migrate: true
# Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
dangerously_truncate: true
# Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
dangerously_recreate: true

# Queue Configuration
queue:
kind: Redis
# Redis connection URI
uri: {{get_env(name="REDIS_URL", default="redis://127.0.0.1")}}
# Dangerously flush all data in Redis on startup. dangerous operation, make sure that you using this flag only on dev environments or test mode
dangerously_flush: false

# Authentication Configuration
auth:
# JWT authentication
jwt:
# Secret key for token generation and verification
secret: PqRwLF2rhHe8J22oBeHy
# Token expiration time in seconds
expiration: 604800 # 7 days

scheduler:
output: stdout
jobs:
write_content:
shell: true
run: "echo loco >> ./scheduler.txt"
schedule: run every 1 second
output: silent
tags: ['base', 'infra']

run_task:
run: "foo"
schedule: "at 10:00 am"

list_if_users:
run: "user_report"
shell: true
schedule: "* 2 * * * *"
tags: ['base', 'users']
39 changes: 39 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,45 @@ pub trait Hooks: Send {
/// This function allows users to perform any necessary cleanup or final
/// actions before the application stops completely.
async fn on_shutdown(_ctx: &AppContext) {}

/// Modifies the OpenAPI spec before the routes are added, allowing you to edit [openapi::info](https://docs.rs/utoipa/latest/utoipa/openapi/info/struct.Info.html)
/// # Examples
/// ```rust ignore
/// fn inital_openapi_spec(_ctx: &AppContext) -> utoipa::openapi::OpenApi {
/// #[derive(OpenApi)]
/// #[openapi(info(
/// title = "Loco Demo",
/// description = "This app is a kitchensink for various capabilities and examples of the [Loco](https://loco.rs) project."
/// ))]
/// struct ApiDoc;
/// ApiDoc::openapi()
/// }
/// ```
///
/// With SecurityAddon
/// ```rust ignore
/// fn inital_openapi_spec(ctx: &AppContext) -> utoipa::openapi::OpenApi {
/// set_jwt_location(ctx);
///
/// #[derive(OpenApi)]
/// #[openapi(
/// modifiers(&SecurityAddon),
/// info(
/// title = "Loco Demo",
/// description = "This app is a kitchensink for various capabilities and examples of the [Loco](https://loco.rs) project."
/// )
/// )]
/// struct ApiDoc;
/// ApiDoc::openapi()
/// }
/// ```
#[cfg(any(
feature = "openapi_swagger",
feature = "openapi_redoc",
feature = "openapi_scalar"
))]
#[must_use]
fn inital_openapi_spec(_ctx: &AppContext) -> utoipa::openapi::OpenApi;
}

/// An initializer.
Expand Down
6 changes: 6 additions & 0 deletions src/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
#[cfg(feature = "auth_jwt")]
pub mod jwt;
#[cfg(any(
feature = "openapi_swagger",
feature = "openapi_redoc",
feature = "openapi_scalar"
))]
pub mod openapi;
Loading