Gracefully handle HTTP 405 errors

This commit is contained in:
2026-07-24 17:27:59 -04:00
parent 3be41f1733
commit ddf8014561
2 changed files with 55 additions and 5 deletions
+3
View File
@@ -26,4 +26,7 @@ pub enum Error {
/// The RDF data failed to parse.
#[error(transparent)]
InvalidRdfSyntax(#[from] oxigraph::io::RdfSyntaxError),
#[error(transparent)]
ToStr(#[from] http::header::ToStrError),
}
+52 -5
View File
@@ -2,6 +2,7 @@ use crate::rdf_source::RdfSource;
use crate::vocab;
use bytes::Bytes;
use futures::Stream;
use http::Method;
use oxigraph::io::{RdfFormat, RdfParser};
use oxigraph::model::{GraphNameRef, NamedNodeRef, Quad};
use reqwest_middleware::reqwest::{Client, Response, StatusCode, Url, header};
@@ -34,6 +35,7 @@ pub struct ResourceRequestBuilder {
client: ClientWithMiddleware,
url: Url,
follow_described_by: bool,
validate_support: bool,
formats: Vec<RdfFormat>,
}
@@ -52,17 +54,30 @@ impl ResourceRequestBuilder {
client,
url,
follow_described_by: true,
validate_support: true,
formats: Vec::new(),
}
}
/// If the HTTP response includes a [describedby](https://www.w3.org/TR/ldp/#link-relation-describedby) link rel, then it will be followed.
///
/// Default value: `true`.
#[must_use]
pub fn follow_described_by(mut self, value: bool) -> Self {
self.follow_described_by = value;
self
}
/// Require the presence of a `Link: <http://www.w3.org/ns/ldp#Resource>; rel="type"` response header.
/// This is described in [section 4.2.1.4](https://www.w3.org/TR/ldp/#ldpr-resource) of the spec.
///
/// Default value: `true`.
#[must_use]
pub fn validate_support(mut self, value: bool) -> Self {
self.validate_support = value;
self
}
/// Restrict the request to the given RDF format.
///
/// Repeated calls are additive. By default, all formats — even non-RDF formats — are accepted.
@@ -150,19 +165,48 @@ impl ResourceRequest {
request_builder
}
fn is_method_allowed(response: &Response, method: Method) -> crate::Result<bool> {
for header in response.headers().get_all(header::ALLOW) {
for value in header.to_str()?.replace(" ", "").split(',') {
if value == method {
return Ok(true);
}
}
}
Ok(false)
}
/// Send the request.
///
/// There are two stages to this process. First, a HEAD request is made to determine whether
/// There are two stages to this process. First, a `HEAD` request is made to determine whether
/// the requested resource is described by an RDF graph at another location. If it is, and if
/// the user permits it, then the URL in the
/// [describedby](https://www.w3.org/TR/ldp/#link-relation-describedby) header is used.
/// Otherwise, the original URL is used.
///
/// During the second stage, a GET request is made. No parsing occurs at this time.
/// During the second stage, a `GET` request is made. No parsing occurs at this time.
///
/// If the `HEAD` method is not allowed on this URI (a violation of the LDP spec), the `Allow`
/// header is inspected for `GET`. If absent, this method will return an error.
pub async fn send(&self) -> crate::Result<Resource> {
let request_builder = self.builder.client.head(self.builder.url.clone());
let mut response = request_builder.send().await?.error_for_status()?;
Self::ensure_ldp_support(&response)?;
let mut response = request_builder.send().await?;
match response.error_for_status_ref() {
Ok(response) => {
if self.builder.validate_support {
Self::ensure_ldp_support(&response)?;
}
}
Err(err) if err.status() == Some(StatusCode::METHOD_NOT_ALLOWED) => {
if !Self::is_method_allowed(&response, Method::GET)? {
return Err(err.into());
}
}
err => {
err?;
}
}
let url_to_get;
let described_by = Self::extract_described_by(&response);
@@ -178,7 +222,10 @@ impl ResourceRequest {
request_builder = self.add_media_types(request_builder);
response = request_builder.send().await?.error_for_status()?;
Self::ensure_ldp_support(&response)?;
if self.builder.validate_support {
Self::ensure_ldp_support(&response)?;
}
let state_token = response
.headers()