From 821ca3de45a32ae02a25f1dd26b2c262906a7c9c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 13 Mar 2024 10:44:37 +0100 Subject: [PATCH 1/2] server: Simplify middleware setup --- server/svix-server/src/lib.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/server/svix-server/src/lib.rs b/server/svix-server/src/lib.rs index d509ef944..04e7f366c 100644 --- a/server/svix-server/src/lib.rs +++ b/server/svix-server/src/lib.rs @@ -19,7 +19,7 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, time::Duration, }; -use tower::ServiceBuilder; +use tower::layer::layer_fn; use tower_http::cors::{AllowHeaders, Any, CorsLayer}; use tracing_subscriber::layer::SubscriberExt as _; @@ -149,21 +149,17 @@ pub async fn run_with_prefix( let openapi = openapi::postprocess_spec(openapi); let docs_router = docs::router(openapi); - let app = app - .merge(docs_router) - .layer( - ServiceBuilder::new().layer_fn(move |service| IdempotencyService { - cache: svc_cache.clone(), - service, - }), - ) - .layer( - CorsLayer::new() - .allow_origin(Any) - .allow_methods(Any) - .allow_headers(AllowHeaders::mirror_request()) - .max_age(Duration::from_secs(600)), - ); + let app = app.merge(docs_router).layer(( + layer_fn(move |service| IdempotencyService { + cache: svc_cache.clone(), + service, + }), + CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(AllowHeaders::mirror_request()) + .max_age(Duration::from_secs(600)), + )); let with_api = cfg.api_enabled; let with_worker = cfg.worker_enabled; From 410a9274c33b54f8e54ecc49de6bed4835469320 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 13 Mar 2024 10:57:55 +0100 Subject: [PATCH 2/2] server: Don't require trailing slash at the end of request paths --- server/openapi.json | 60 +++++++++---------- server/svix-server/Cargo.toml | 2 +- server/svix-server/src/lib.rs | 15 +++-- .../src/v1/endpoints/application.rs | 4 +- .../svix-server/src/v1/endpoints/attempt.rs | 18 +++--- server/svix-server/src/v1/endpoints/auth.rs | 6 +- .../src/v1/endpoints/endpoint/mod.rs | 16 ++--- .../src/v1/endpoints/event_type.rs | 6 +- server/svix-server/src/v1/endpoints/health.rs | 4 +- .../svix-server/src/v1/endpoints/message.rs | 6 +- server/svix-server/tests/it/e2e_health.rs | 26 ++++++++ server/svix-server/tests/it/main.rs | 1 + 12 files changed, 99 insertions(+), 65 deletions(-) create mode 100644 server/svix-server/tests/it/e2e_health.rs diff --git a/server/openapi.json b/server/openapi.json index a55d11839..80e014af0 100644 --- a/server/openapi.json +++ b/server/openapi.json @@ -2040,7 +2040,7 @@ }, "openapi": "3.0.2", "paths": { - "/api/v1/app/": { + "/api/v1/app": { "get": { "description": "List of all the organization's applications.", "operationId": "v1.application.list", @@ -2281,7 +2281,7 @@ ] } }, - "/api/v1/app/{app_id}/": { + "/api/v1/app/{app_id}": { "delete": { "description": "Delete an application.", "operationId": "v1.application.delete", @@ -2641,7 +2641,7 @@ ] } }, - "/api/v1/app/{app_id}/attempt/endpoint/{endpoint_id}/": { + "/api/v1/app/{app_id}/attempt/endpoint/{endpoint_id}": { "get": { "description": "List attempts by endpoint id", "operationId": "v1.message-attempt.list-by-endpoint", @@ -2864,7 +2864,7 @@ ] } }, - "/api/v1/app/{app_id}/attempt/msg/{msg_id}/": { + "/api/v1/app/{app_id}/attempt/msg/{msg_id}": { "get": { "description": "List attempts by message id", "operationId": "v1.message-attempt.list-by-msg", @@ -3102,7 +3102,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/": { + "/api/v1/app/{app_id}/endpoint": { "get": { "description": "List the application's endpoints.", "operationId": "v1.endpoint.list", @@ -3348,7 +3348,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}": { "delete": { "description": "Delete an endpoint.", "operationId": "v1.endpoint.delete", @@ -3805,7 +3805,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/headers/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}/headers": { "get": { "description": "Get the additional headers to be sent with the webhook", "operationId": "v1.endpoint.get-headers", @@ -4137,7 +4137,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/msg/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}/msg": { "get": { "description": "List messages for a particular endpoint. Additionally includes metadata about the latest message attempt.\n\nThe `before` parameter lets you filter all items created before a certain date and is ignored if an iterator is passed.", "operationId": "v1.message-attempt.list-attempted-messages", @@ -4349,7 +4349,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/recover/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}/recover": { "post": { "description": "Resend all failed messages since a given time.", "operationId": "v1.endpoint.recover", @@ -4471,7 +4471,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/secret/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}/secret": { "get": { "description": "Get the endpoint's signing secret.\n\nThis is used to verify the authenticity of the webhook.\nFor more information please refer to [the consuming webhooks docs](https://docs.svix.com/consuming-webhooks/).", "operationId": "v1.endpoint.get-secret", @@ -4581,7 +4581,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/secret/rotate/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}/secret/rotate": { "post": { "description": "Rotates the endpoint's signing secret. The previous secret will be valid for the next 24 hours.", "operationId": "v1.endpoint.rotate-secret", @@ -4703,7 +4703,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/send-example/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}/send-example": { "post": { "description": "Send an example message for an event", "operationId": "v1.endpoint.send-example", @@ -4832,7 +4832,7 @@ ] } }, - "/api/v1/app/{app_id}/endpoint/{endpoint_id}/stats/": { + "/api/v1/app/{app_id}/endpoint/{endpoint_id}/stats": { "get": { "description": "Get basic statistics for the endpoint.", "operationId": "v1.endpoint.get-stats", @@ -4962,7 +4962,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/": { + "/api/v1/app/{app_id}/msg": { "get": { "description": "List all of the application's messages.\n\nThe `before` parameter lets you filter all items created before a certain date and is ignored if an iterator is passed.\nThe `after` parameter lets you filter all items created after a certain date and is ignored if an iterator is passed.\n`before` and `after` cannot be used simultaneously.", "operationId": "v1.message.list", @@ -5249,7 +5249,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/": { + "/api/v1/app/{app_id}/msg/{msg_id}": { "get": { "description": "Get a message by its ID or eventID.", "operationId": "v1.message.get", @@ -5370,7 +5370,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/attempt/": { + "/api/v1/app/{app_id}/msg/{msg_id}/attempt": { "get": { "description": "Deprecated: Please use \"List Attempts by Endpoint\" and \"List Attempts by Msg\" instead.\n\n`msg_id`: Use a message id or a message `eventId`", "operationId": "v1.message-attempt.list-by-msg-deprecated", @@ -5586,7 +5586,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/attempt/{attempt_id}/": { + "/api/v1/app/{app_id}/msg/{msg_id}/attempt/{attempt_id}": { "get": { "description": "`msg_id`: Use a message id or a message `eventId`", "operationId": "v1.message-attempt.get", @@ -5706,7 +5706,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/attempt/{attempt_id}/content/": { + "/api/v1/app/{app_id}/msg/{msg_id}/attempt/{attempt_id}/content": { "delete": { "description": "Deletes the given attempt's response body. Useful when an endpoint accidentally returned sensitive content.", "operationId": "v1.message-attempt.expunge-content", @@ -5816,7 +5816,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/content/": { + "/api/v1/app/{app_id}/msg/{msg_id}/content": { "delete": { "description": "Delete the given message's payload. Useful in cases when a message was accidentally sent with sensitive content.\n\nThe message can't be replayed or resent once its payload has been deleted or expired.", "operationId": "v1.message.expunge-content", @@ -5916,7 +5916,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/endpoint/": { + "/api/v1/app/{app_id}/msg/{msg_id}/endpoint": { "get": { "description": "`msg_id`: Use a message id or a message `eventId`", "operationId": "v1.message-attempt.list-attempted-destinations", @@ -6050,7 +6050,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/endpoint/{endpoint_id}/attempt/": { + "/api/v1/app/{app_id}/msg/{msg_id}/endpoint/{endpoint_id}/attempt": { "get": { "description": "DEPRECATED: please use list_attempts with endpoint_id as a query parameter instead.\n\nList the message attempts for a particular endpoint.\n\nReturning the endpoint.\n\nThe `before` parameter lets you filter all items created before a certain date and is ignored if an iterator is passed.", "operationId": "v1.message-attempt.list-by-endpoint-deprecated", @@ -6264,7 +6264,7 @@ ] } }, - "/api/v1/app/{app_id}/msg/{msg_id}/endpoint/{endpoint_id}/resend/": { + "/api/v1/app/{app_id}/msg/{msg_id}/endpoint/{endpoint_id}/resend": { "post": { "description": "Resend a message to the specified endpoint.", "operationId": "v1.message-attempt.resend", @@ -6389,7 +6389,7 @@ ] } }, - "/api/v1/auth/app-portal-access/{app_id}/": { + "/api/v1/auth/app-portal-access/{app_id}": { "post": { "description": "Use this function to get magic links (and authentication codes) for connecting your users to the Consumer Application Portal.", "operationId": "v1.authentication.app-portal-access", @@ -6505,7 +6505,7 @@ ] } }, - "/api/v1/auth/dashboard-access/{app_id}/": { + "/api/v1/auth/dashboard-access/{app_id}": { "post": { "description": "DEPRECATED: Please use `app-portal-access` instead.\n\nUse this function to get magic links (and authentication codes) for connecting your users to the Consumer Application Portal.", "operationId": "v1.authentication.dashboard-access", @@ -6611,7 +6611,7 @@ ] } }, - "/api/v1/auth/logout/": { + "/api/v1/auth/logout": { "post": { "description": "\nLogout an app token.\n\nTrying to log out other tokens will fail.\n", "operationId": "logout_api_v1_auth_logout__post", @@ -6637,7 +6637,7 @@ ] } }, - "/api/v1/event-type/": { + "/api/v1/event-type": { "get": { "description": "Return the list of event types.", "operationId": "v1.event-type.list", @@ -6881,7 +6881,7 @@ ] } }, - "/api/v1/event-type/schema/generate-example/": { + "/api/v1/event-type/schema/generate-example": { "post": { "description": "Generates a fake example from the given JSONSchema", "parameters": [ @@ -6905,7 +6905,7 @@ ] } }, - "/api/v1/event-type/{event_type_name}/": { + "/api/v1/event-type/{event_type_name}": { "delete": { "description": "Archive an event type.\n\nEndpoints already configured to filter on an event type will continue to do so after archival.\nHowever, new messages can not be sent with it and endpoints can not filter on it.\nAn event type can be unarchived with the\n[create operation](#operation/create_event_type_api_v1_event_type__post).", "operationId": "v1.event-type.delete", @@ -7306,7 +7306,7 @@ ] } }, - "/api/v1/health/": { + "/api/v1/health": { "get": { "description": "Verify the API server is up and running.", "operationId": "v1.health.get", @@ -7451,7 +7451,7 @@ ] } }, - "/api/v1/health/ping/": { + "/api/v1/health/ping": { "get": { "responses": { "204": { diff --git a/server/svix-server/Cargo.toml b/server/svix-server/Cargo.toml index fa8df3a0c..14ae0e6c2 100644 --- a/server/svix-server/Cargo.toml +++ b/server/svix-server/Cargo.toml @@ -26,7 +26,7 @@ hyper-openssl = "0.9.2" openssl = "0.10.60" tokio = { version = "1.24.2", features = ["full"] } tower = "0.4.11" -tower-http = { version = "0.4.0", features = ["trace", "cors", "request-id"] } +tower-http = { version = "0.4.4", features = ["trace", "cors", "normalize-path", "request-id"] } serde = { version = "1.0.184", features = ["derive"] } serde_json = { version = "1.0.74", features = ["arbitrary_precision", "raw_value"] } serde_path_to_error = "0.1.7" diff --git a/server/svix-server/src/lib.rs b/server/svix-server/src/lib.rs index 04e7f366c..ac9fadc16 100644 --- a/server/svix-server/src/lib.rs +++ b/server/svix-server/src/lib.rs @@ -20,7 +20,10 @@ use std::{ time::Duration, }; use tower::layer::layer_fn; -use tower_http::cors::{AllowHeaders, Any, CorsLayer}; +use tower_http::{ + cors::{AllowHeaders, Any, CorsLayer}, + normalize_path::NormalizePath, +}; use tracing_subscriber::layer::SubscriberExt as _; use crate::{ @@ -160,10 +163,14 @@ pub async fn run_with_prefix( .allow_headers(AllowHeaders::mirror_request()) .max_age(Duration::from_secs(600)), )); + let svc = tower::make::Shared::new( + // It is important that this service wraps the router instead of being + // applied via `Router::layer`, as it would run after routing then. + NormalizePath::trim_trailing_slash(app), + ); let with_api = cfg.api_enabled; let with_worker = cfg.worker_enabled; - let listen_address = cfg.listen_address; let (server, worker_loop, expired_message_cleaner_loop) = tokio::join!( @@ -173,13 +180,13 @@ pub async fn run_with_prefix( tracing::debug!("API: Listening on {}", l.local_addr().unwrap()); axum::Server::from_tcp(l) .expect("Error starting http server") - .serve(app.into_make_service()) + .serve(svc) .with_graceful_shutdown(graceful_shutdown_handler()) .await } else { tracing::debug!("API: Listening on {}", listen_address); axum::Server::bind(&listen_address) - .serve(app.into_make_service()) + .serve(svc) .with_graceful_shutdown(graceful_shutdown_handler()) .await } diff --git a/server/svix-server/src/v1/endpoints/application.rs b/server/svix-server/src/v1/endpoints/application.rs index 80dbcc1fe..738587568 100644 --- a/server/svix-server/src/v1/endpoints/application.rs +++ b/server/svix-server/src/v1/endpoints/application.rs @@ -389,13 +389,13 @@ pub fn router() -> ApiRouter { let tag = openapi_tag("Application"); ApiRouter::new() .api_route_with( - "/app/", + "/app", post_with(create_application, create_application_operation) .get_with(list_applications, list_applications_operation), &tag, ) .api_route_with( - "/app/:app_id/", + "/app/:app_id", get_with(get_application, get_application_operation) .put_with(update_application, update_application_operation) .patch_with(patch_application, patch_application_operation) diff --git a/server/svix-server/src/v1/endpoints/attempt.rs b/server/svix-server/src/v1/endpoints/attempt.rs index b8afad5a3..bdcefb695 100644 --- a/server/svix-server/src/v1/endpoints/attempt.rs +++ b/server/svix-server/src/v1/endpoints/attempt.rs @@ -875,22 +875,22 @@ pub fn router() -> ApiRouter { ApiRouter::new() // NOTE: [`list_messageattempts`] is deprecated .api_route_with( - "/app/:app_id/msg/:msg_id/attempt/", + "/app/:app_id/msg/:msg_id/attempt", get_with(list_messageattempts, list_messageattempts_operation), &tag, ) .api_route_with( - "/app/:app_id/msg/:msg_id/attempt/:attempt_id/", + "/app/:app_id/msg/:msg_id/attempt/:attempt_id", get_with(get_messageattempt, get_messageattempt_operation), &tag, ) .api_route_with( - "/app/:app_id/msg/:msg_id/attempt/:attempt_id/content/", + "/app/:app_id/msg/:msg_id/attempt/:attempt_id/content", delete_with(expunge_attempt_content, expunge_attempt_content_operation), &tag, ) .api_route_with( - "/app/:app_id/msg/:msg_id/endpoint/", + "/app/:app_id/msg/:msg_id/endpoint", get_with( list_attempted_destinations, list_attempted_destinations_operation, @@ -898,13 +898,13 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/app/:app_id/msg/:msg_id/endpoint/:endpoint_id/resend/", + "/app/:app_id/msg/:msg_id/endpoint/:endpoint_id/resend", post_with(resend_webhook, resend_webhook_operation), &tag, ) // NOTE: [`list_attempts_for_endpoint`] is deprecated .api_route_with( - "/app/:app_id/msg/:msg_id/endpoint/:endpoint_id/attempt/", + "/app/:app_id/msg/:msg_id/endpoint/:endpoint_id/attempt", get_with( list_attempts_for_endpoint, list_attempts_for_endpoint_operation, @@ -912,12 +912,12 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/msg/", + "/app/:app_id/endpoint/:endpoint_id/msg", get_with(list_attempted_messages, list_attempted_messages_operation), &tag, ) .api_route_with( - "/app/:app_id/attempt/endpoint/:endpoint_id/", + "/app/:app_id/attempt/endpoint/:endpoint_id", get_with( list_attempts_by_endpoint, list_attempts_by_endpoint_operation, @@ -925,7 +925,7 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/app/:app_id/attempt/msg/:msg_id/", + "/app/:app_id/attempt/msg/:msg_id", get_with(list_attempts_by_msg, list_attempts_by_msg_operation), tag, ) diff --git a/server/svix-server/src/v1/endpoints/auth.rs b/server/svix-server/src/v1/endpoints/auth.rs index 4e44cb8dc..0afe882d8 100644 --- a/server/svix-server/src/v1/endpoints/auth.rs +++ b/server/svix-server/src/v1/endpoints/auth.rs @@ -129,17 +129,17 @@ pub fn router() -> ApiRouter { let tag = openapi_tag("Authentication"); ApiRouter::new() .api_route_with( - "/auth/dashboard-access/:app_id/", + "/auth/dashboard-access/:app_id", post_with(dashboard_access, dashboard_access_operation), &tag, ) .api_route_with( - "/auth/logout/", + "/auth/logout", post_with(api_not_implemented, logout_operation), &tag, ) .api_route_with( - "/auth/app-portal-access/:app_id/", + "/auth/app-portal-access/:app_id", post_with(app_portal_access, app_portal_access_operation), tag, ) diff --git a/server/svix-server/src/v1/endpoints/endpoint/mod.rs b/server/svix-server/src/v1/endpoints/endpoint/mod.rs index 567a306e0..5ddad0c46 100644 --- a/server/svix-server/src/v1/endpoints/endpoint/mod.rs +++ b/server/svix-server/src/v1/endpoints/endpoint/mod.rs @@ -818,13 +818,13 @@ pub fn router() -> ApiRouter { let tag = openapi_tag("Endpoint"); ApiRouter::new() .api_route_with( - "/app/:app_id/endpoint/", + "/app/:app_id/endpoint", post_with(crud::create_endpoint, crud::create_endpoint_operation) .get_with(crud::list_endpoints, crud::list_endpoints_operation), &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/", + "/app/:app_id/endpoint/:endpoint_id", get_with(crud::get_endpoint, crud::get_endpoint_operation) .put_with(crud::update_endpoint, crud::update_endpoint_operation) .patch_with(crud::patch_endpoint, crud::patch_endpoint_operation) @@ -832,7 +832,7 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/secret/", + "/app/:app_id/endpoint/:endpoint_id/secret", get_with( secrets::get_endpoint_secret, secrets::get_endpoint_secret_operation, @@ -840,7 +840,7 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/secret/rotate/", + "/app/:app_id/endpoint/:endpoint_id/secret/rotate", post_with( secrets::rotate_endpoint_secret, secrets::rotate_endpoint_secret_operation, @@ -848,17 +848,17 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/stats/", + "/app/:app_id/endpoint/:endpoint_id/stats", get_with(endpoint_stats, endpoint_stats_operation), &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/send-example/", + "/app/:app_id/endpoint/:endpoint_id/send-example", post_with(send_example, send_example_operation), &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/recover/", + "/app/:app_id/endpoint/:endpoint_id/recover", post_with( recovery::recover_failed_webhooks, recovery::recover_failed_webhooks_operation, @@ -866,7 +866,7 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/app/:app_id/endpoint/:endpoint_id/headers/", + "/app/:app_id/endpoint/:endpoint_id/headers", get_with( headers::get_endpoint_headers, headers::get_endpoint_headers_operation, diff --git a/server/svix-server/src/v1/endpoints/event_type.rs b/server/svix-server/src/v1/endpoints/event_type.rs index af585fe2b..d227c4837 100644 --- a/server/svix-server/src/v1/endpoints/event_type.rs +++ b/server/svix-server/src/v1/endpoints/event_type.rs @@ -414,13 +414,13 @@ pub fn router() -> ApiRouter { let tag = openapi_tag("Event Type"); ApiRouter::new() .api_route_with( - "/event-type/", + "/event-type", post_with(create_event_type, create_event_type_operation) .get_with(list_event_types, list_event_types_operation), &tag, ) .api_route_with( - "/event-type/:event_type_name/", + "/event-type/:event_type_name", get_with(get_event_type, get_event_type_operation) .put_with(update_event_type, update_event_type_operation) .patch_with(patch_event_type, patch_event_type_operation) @@ -428,7 +428,7 @@ pub fn router() -> ApiRouter { &tag, ) .api_route_with( - "/event-type/schema/generate-example/", + "/event-type/schema/generate-example", post_with( api_not_implemented, openapi_desc(GENERATE_SCHEMA_EXAMPLE_DESCRIPTION), diff --git a/server/svix-server/src/v1/endpoints/health.rs b/server/svix-server/src/v1/endpoints/health.rs index 6584e4e41..da8e59d57 100644 --- a/server/svix-server/src/v1/endpoints/health.rs +++ b/server/svix-server/src/v1/endpoints/health.rs @@ -133,9 +133,9 @@ pub fn router() -> ApiRouter { let tag = openapi_tag("Health"); ApiRouter::new() - .api_route("/health/ping/", get(ping).head(ping)) + .api_route("/health/ping", get(ping).head(ping)) .api_route_with( - "/health/", + "/health", get_with(health, |op| op.response::<204, ()>().with(health_operation)) .head_with(health, health_operation), tag, diff --git a/server/svix-server/src/v1/endpoints/message.rs b/server/svix-server/src/v1/endpoints/message.rs index 314c0bc18..37978f2c8 100644 --- a/server/svix-server/src/v1/endpoints/message.rs +++ b/server/svix-server/src/v1/endpoints/message.rs @@ -454,18 +454,18 @@ pub fn router() -> ApiRouter { let tag = openapi_tag("Message"); ApiRouter::new() .api_route_with( - "/app/:app_id/msg/", + "/app/:app_id/msg", post_with(create_message, create_message_operation) .get_with(list_messages, list_messages_operation), &tag, ) .api_route_with( - "/app/:app_id/msg/:msg_id/", + "/app/:app_id/msg/:msg_id", get_with(get_message, get_message_operation), &tag, ) .api_route_with( - "/app/:app_id/msg/:msg_id/content/", + "/app/:app_id/msg/:msg_id/content", delete_with(expunge_message_content, expunge_message_content_operation), tag, ) diff --git a/server/svix-server/tests/it/e2e_health.rs b/server/svix-server/tests/it/e2e_health.rs new file mode 100644 index 000000000..7f7c59bca --- /dev/null +++ b/server/svix-server/tests/it/e2e_health.rs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: © 2024 Svix Authors +// SPDX-License-Identifier: MIT + +use reqwest::StatusCode; + +use crate::utils::{start_svix_server, IgnoredResponse}; + +#[tokio::test] +async fn ping_with_trailing_slash() { + let (client, _jh) = start_svix_server().await; + + let _: IgnoredResponse = client + .get("api/v1/health/ping/", StatusCode::NO_CONTENT) + .await + .unwrap(); +} + +#[tokio::test] +async fn ping_without_trailing_slash() { + let (client, _jh) = start_svix_server().await; + + let _: IgnoredResponse = client + .get("api/v1/health/ping", StatusCode::NO_CONTENT) + .await + .unwrap(); +} diff --git a/server/svix-server/tests/it/main.rs b/server/svix-server/tests/it/main.rs index f8ce1ce31..0b4b4f83c 100644 --- a/server/svix-server/tests/it/main.rs +++ b/server/svix-server/tests/it/main.rs @@ -4,6 +4,7 @@ mod e2e_attempt; mod e2e_auth; mod e2e_endpoint; mod e2e_event_type; +mod e2e_health; mod e2e_message; mod e2e_operational_webhooks; mod integ_webhook_http_client;