From 69a701f71b050740045eea758f21dbb10d2ef21d Mon Sep 17 00:00:00 2001 From: Luke Frisken Date: Wed, 15 May 2024 17:08:21 +1000 Subject: [PATCH] Use typed path for forecasts --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 2 +- src/forecasts/mod.rs | 11 +++++++++-- src/index.rs | 9 +++++++-- src/main.rs | 3 ++- 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e035c9..ddbf64d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,15 +362,19 @@ checksum = "895ff42f72016617773af68fb90da2a9677d89c62338ec09162d4909d86fdd8f" dependencies = [ "axum", "axum-core", + "axum-macros", "bytes", "cookie", + "form_urlencoded", "futures-util", "http", "http-body", "http-body-util", "mime", + "percent-encoding", "pin-project-lite", "serde", + "serde_html_form", "tower", "tower-layer", "tower-service", @@ -4176,6 +4180,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_html_form" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5" +dependencies = [ + "form_urlencoded", + "indexmap 2.2.3", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_json" version = "1.0.113" diff --git a/Cargo.toml b/Cargo.toml index 0316af6..63cc0ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ utils = { path = "./utils" } average = "0.14.1" axum = { version = "0.7.4", features = ["macros", "multipart"] } headers = "0.4.0" -axum-extra = { version = "0.9.2", default-features = false, features = ["cookie"] } +axum-extra = { version = "0.9.2", default-features = false, features = ["cookie", "typed-routing"] } base64 = { workspace = true } bcrypt = "0.15.0" ansi-to-html = "0.2.1" diff --git a/src/forecasts/mod.rs b/src/forecasts/mod.rs index f509c56..4bdbd1d 100644 --- a/src/forecasts/mod.rs +++ b/src/forecasts/mod.rs @@ -8,6 +8,7 @@ use axum::{ response::{IntoResponse, Response}, Extension, Json, }; +use axum_extra::routing::TypedPath; use eyre::{Context, ContextCompat}; use forecast_spreadsheet::{ options::AreaDefinition, AreaId, Aspect, AspectElevation, Confidence, Distribution, @@ -19,7 +20,7 @@ use http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use indexmap::IndexMap; use once_cell::sync::Lazy; use secrecy::SecretString; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use time::{OffsetDateTime, PrimitiveDateTime}; use time_tz::{Offset, TimeZone}; use tracing::instrument; @@ -139,8 +140,14 @@ fn parse_forecast_name_impl( }) } +#[derive(Deserialize, TypedPath)] +#[typed_path("/forecasts/:file_name")] +pub struct ForecastsFilePath { + pub file_name: String, +} + pub async fn handler( - Path(file_name): Path, + ForecastsFilePath { file_name }: ForecastsFilePath, State(state): State, Extension(database): Extension, Extension(i18n): Extension, diff --git a/src/index.rs b/src/index.rs index 11651ce..13d335a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -5,6 +5,7 @@ use axum::{ response::{IntoResponse, Response}, Extension, Json, }; +use axum_extra::routing::TypedPath; use color_eyre::Help; use eyre::{bail, eyre, Context, ContextCompat}; use forecast_spreadsheet::{HazardRating, HazardRatingKind}; @@ -20,7 +21,7 @@ use crate::{ error::map_eyre_error, forecasts::{ get_forecast_data, parse_forecast_name, Forecast, ForecastContext, ForecastData, - ForecastDetails, ForecastFileDetails, RequestedForecastData, + ForecastDetails, ForecastFileDetails, ForecastsFilePath, RequestedForecastData, }, google_drive::{self, ListFileMetadata}, i18n::{self, I18nLoader}, @@ -53,7 +54,11 @@ pub struct ForecastFileContext { impl From for ForecastFileContext { fn from(value: ForecastFile) -> Self { - let path = format!("/forecasts/{}", urlencoding::encode(&value.file.name)); + let path = ForecastsFilePath { + file_name: value.file.name, + } + .to_uri() + .to_string(); Self { path } } } diff --git a/src/main.rs b/src/main.rs index f400e54..0ac4a3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use axum::{ routing::{get, post}, Extension, Router, }; +use axum_extra::routing::RouterExt; use bytes::Bytes; use error::map_std_error; use eyre::Context; @@ -173,7 +174,7 @@ async fn main() -> eyre::Result<()> { Router::new() .route("/", get(index::handler)) .route("/json", get(index::json_handler)) - .route("/forecasts/:file_name", get(forecasts::handler)) + .typed_get(forecasts::handler) .nest("/observations", observations::router()) .layer(middleware::from_fn(disclaimer::middleware)), )