From bec51a6e4f9f05fb6b5d2654c16bbdcbacf87450 Mon Sep 17 00:00:00 2001 From: Katsuyuki Omuro Date: Mon, 7 Sep 2026 02:13:08 +0900 Subject: [PATCH] Add string-based accessors for Request method --- include/fastly/http/request.h | 21 +++++++++++++++ src/cpp/http/request.cpp | 34 ++++++++++++++++++++++++ src/error.rs | 3 +++ src/http/request.rs | 22 +++++++++++++++ src/lib.rs | 9 +++++++ test/request.cpp | 50 +++++++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+) create mode 100644 test/request.cpp diff --git a/include/fastly/http/request.h b/include/fastly/http/request.h index 8a21583..82abb64 100644 --- a/include/fastly/http/request.h +++ b/include/fastly/http/request.h @@ -156,6 +156,12 @@ class Request { /// empty body. Request(Method method, std::string_view url); + /// Create a new request with an arbitrary method string (e.g. `PURGE`) + /// rather than one of the standard methods in `Method`, no headers, and an + /// empty body. Fails if `method` is not a valid HTTP method token. + static fastly::expected create(std::string_view method, + std::string_view url); + /// Create a new `GET` `Request` with the given URL, no headers, and an /// empty body. static Request get(std::string_view url); @@ -505,12 +511,27 @@ class Request { /// Builder-style equivalent of `Request::set_method()`. Request with_method(Method method) &&; + /// Builder-style equivalent of `Request::set_method()`, accepting an + /// arbitrary method string (e.g. `PURGE`) rather than one of the standard + /// methods in `Method`. Fails if `method` is not a valid HTTP method token. + fastly::expected with_method(std::string_view method) &&; + /// Get the request method. Method get_method(); + /// Get the request method as a string. Unlike `Request::get_method()`, + /// this works for nonstandard methods (e.g. `PURGE`) that aren't + /// represented in `Method`. + std::string get_method_str(); + /// Set the request method. void set_method(Method method); + /// Set the request method to an arbitrary method string (e.g. `PURGE`) + /// rather than one of the standard methods in `Method`. Fails if `method` + /// is not a valid HTTP method token. + fastly::expected set_method(std::string_view method); + /// Builder-style equivalent of `Request::set_url()`. fastly::expected with_url(std::string_view url) &&; diff --git a/src/cpp/http/request.cpp b/src/cpp/http/request.cpp index 9454165..36b780a 100644 --- a/src/cpp/http/request.cpp +++ b/src/cpp/http/request.cpp @@ -74,6 +74,20 @@ Request::Request(Method method, std::string_view url) : req(fastly::sys::http::m_static_http_request_new( method, static_cast(url))) {} +fastly::expected Request::create(std::string_view method, + std::string_view url) { + fastly::sys::http::Request *out; + fastly::sys::error::FastlyError *err; + fastly::sys::http::m_static_http_request_new_str( + static_cast(method), static_cast(url), out, + err); + if (err != nullptr) { + return fastly::unexpected(err); + } else { + return FSLY_BOX(http, Request, out); + } +} + Request Request::from_client() { Request req{fastly::sys::http::m_static_http_request_from_client()}; return req; @@ -433,10 +447,30 @@ Request Request::with_method(Method method) && { return std::move(*this); } +fastly::expected Request::with_method(std::string_view method) && { + return this->set_method(method).map([this]() { return std::move(*this); }); +} + Method Request::get_method() { return this->req->get_method(); } +std::string Request::get_method_str() { + std::string out; + this->req->get_method_str(out); + return out; +} + void Request::set_method(Method method) { this->req->set_method(method); } +fastly::expected Request::set_method(std::string_view method) { + fastly::sys::error::FastlyError *err; + this->req->set_method_str(static_cast(method), err); + if (err != nullptr) { + return fastly::unexpected(err); + } else { + return fastly::expected(); + } +} + fastly::expected Request::with_url(std::string_view url) && { return this->set_url(url).map([this]() { return std::move(*this); }); } diff --git a/src/error.rs b/src/error.rs index c6fc257..95747bf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,6 +35,8 @@ pub enum FastlyError { #[error(transparent)] InvalidStatusCode(#[from] http::status::InvalidStatusCode), #[error(transparent)] + InvalidMethod(#[from] http::method::InvalidMethod), + #[error(transparent)] IoError(#[from] std::io::Error), #[error(transparent)] #[allow(clippy::enum_variant_names)] @@ -101,6 +103,7 @@ impl FastlyError { FastlyError::InvalidHeaderName(_) => FastlyErrorCode::InvalidHeaderName, FastlyError::InvalidHeaderValue(_) => FastlyErrorCode::InvalidHeaderValue, FastlyError::InvalidStatusCode(_) => FastlyErrorCode::InvalidStatusCode, + FastlyError::InvalidMethod(_) => FastlyErrorCode::InvalidMethod, FastlyError::IoError(_) => FastlyErrorCode::IoError, FastlyError::FastlyError(_) => FastlyErrorCode::FastlyError, FastlyError::FastlySendError(_) => FastlyErrorCode::FastlySendError, diff --git a/src/http/request.rs b/src/http/request.rs index 032f56f..a99b1a3 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -146,6 +146,19 @@ pub fn m_static_http_request_new(method: Method, url: &CxxString) -> Box, + mut err: ErrPtr, +) { + let method = try_fe!(err, fastly::http::Method::try_from(method.as_bytes())); + out.set(Box::into_raw(Box::new(Request(fastly::Request::new( + method, + url.to_str().expect("Invalid UTF-8 in URL"), + ))))); +} + pub fn m_static_http_request_get(url: &CxxString) -> Box { Box::new(Request(fastly::Request::get( url.to_str().expect("Invalid UTF-8 in URL"), @@ -429,6 +442,15 @@ impl Request { self.0.set_method(method); } + pub fn get_method_str(&self, out: Pin<&mut CxxString>) { + out.push_str(self.0.get_method_str()); + } + + pub fn set_method_str(&mut self, method: &CxxString, mut err: ErrPtr) { + let method = try_fe!(err, fastly::http::Method::try_from(method.as_bytes())); + self.0.set_method(method); + } + pub fn get_url(&self, out: Pin<&mut CxxString>) { out.push_str(self.0.get_url().as_str()); } diff --git a/src/lib.rs b/src/lib.rs index 2ec01d3..6ed3891 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ mod ffi { InvalidHeaderName, InvalidHeaderValue, InvalidStatusCode, + InvalidMethod, IoError, FastlyError, FastlySendError, @@ -434,6 +435,12 @@ mod ffi { fn m_static_http_request_trace(url: &CxxString) -> Box; fn m_static_http_request_patch(url: &CxxString) -> Box; fn m_static_http_request_new(method: Method, url: &CxxString) -> Box; + fn m_static_http_request_new_str( + method: &CxxString, + url: &CxxString, + mut out: Pin<&mut *mut Request>, + mut err: Pin<&mut *mut FastlyError>, + ); fn m_static_http_request_from_client() -> Box; // Regular methods @@ -506,6 +513,8 @@ mod ffi { ) -> bool; fn get_method(&self) -> Method; fn set_method(&mut self, method: Method); + fn get_method_str(&self, mut out: Pin<&mut CxxString>); + fn set_method_str(&mut self, method: &CxxString, mut err: Pin<&mut *mut FastlyError>); fn get_url(&self, mut out: Pin<&mut CxxString>); fn set_url(&mut self, url: &CxxString, mut err: Pin<&mut *mut FastlyError>); fn get_path(&self, mut out: Pin<&mut CxxString>); diff --git a/test/request.cpp b/test/request.cpp new file mode 100644 index 0000000..9c63d32 --- /dev/null +++ b/test/request.cpp @@ -0,0 +1,50 @@ +#include +#include + +using namespace fastly::http; + +TEST_CASE("Request::get_method_str reflects a standard method", "[request]") { + auto req = Request::get("https://example.com"); + REQUIRE(req.get_method_str() == "GET"); +} + +TEST_CASE("Request::create accepts a nonstandard method", "[request]") { + auto req = Request::create("PURGE", "https://example.com"); + REQUIRE(req.has_value()); + REQUIRE(req->get_method_str() == "PURGE"); +} + +TEST_CASE("Request::create rejects an invalid method token", "[request]") { + auto req = Request::create("BAD METHOD", "https://example.com"); + REQUIRE(!req.has_value()); + REQUIRE(req.error().error_code() == fastly::FastlyErrorCode::InvalidMethod); +} + +TEST_CASE("Request::set_method(string) accepts a nonstandard method", + "[request]") { + auto req = Request::get("https://example.com"); + auto res = req.set_method("PURGE"); + REQUIRE(res.has_value()); + REQUIRE(req.get_method_str() == "PURGE"); +} + +TEST_CASE("Request::set_method(string) rejects an invalid method token", + "[request]") { + auto req = Request::get("https://example.com"); + auto res = req.set_method("BAD METHOD"); + REQUIRE(!res.has_value()); + REQUIRE(res.error().error_code() == fastly::FastlyErrorCode::InvalidMethod); + // The request's method is unchanged after a failed set_method. + REQUIRE(req.get_method_str() == "GET"); +} + +TEST_CASE("Request::with_method(string) accepts a nonstandard method", + "[request]") { + auto req = Request::get("https://example.com").with_method("PURGE"); + REQUIRE(req.has_value()); + REQUIRE(req->get_method_str() == "PURGE"); +} + +// Required due to https://github.com/WebAssembly/wasi-libc/issues/485 +#include +int main(int argc, char *argv[]) { return Catch::Session().run(argc, argv); }