Gracefully handle HTTP 405 errors
This commit is contained in:
@@ -26,4 +26,7 @@ pub enum Error {
|
|||||||
/// The RDF data failed to parse.
|
/// The RDF data failed to parse.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidRdfSyntax(#[from] oxigraph::io::RdfSyntaxError),
|
InvalidRdfSyntax(#[from] oxigraph::io::RdfSyntaxError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
ToStr(#[from] http::header::ToStrError),
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-5
@@ -2,6 +2,7 @@ use crate::rdf_source::RdfSource;
|
|||||||
use crate::vocab;
|
use crate::vocab;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
use http::Method;
|
||||||
use oxigraph::io::{RdfFormat, RdfParser};
|
use oxigraph::io::{RdfFormat, RdfParser};
|
||||||
use oxigraph::model::{GraphNameRef, NamedNodeRef, Quad};
|
use oxigraph::model::{GraphNameRef, NamedNodeRef, Quad};
|
||||||
use reqwest_middleware::reqwest::{Client, Response, StatusCode, Url, header};
|
use reqwest_middleware::reqwest::{Client, Response, StatusCode, Url, header};
|
||||||
@@ -34,6 +35,7 @@ pub struct ResourceRequestBuilder {
|
|||||||
client: ClientWithMiddleware,
|
client: ClientWithMiddleware,
|
||||||
url: Url,
|
url: Url,
|
||||||
follow_described_by: bool,
|
follow_described_by: bool,
|
||||||
|
validate_support: bool,
|
||||||
formats: Vec<RdfFormat>,
|
formats: Vec<RdfFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,17 +54,30 @@ impl ResourceRequestBuilder {
|
|||||||
client,
|
client,
|
||||||
url,
|
url,
|
||||||
follow_described_by: true,
|
follow_described_by: true,
|
||||||
|
validate_support: true,
|
||||||
formats: Vec::new(),
|
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.
|
/// 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]
|
#[must_use]
|
||||||
pub fn follow_described_by(mut self, value: bool) -> Self {
|
pub fn follow_described_by(mut self, value: bool) -> Self {
|
||||||
self.follow_described_by = value;
|
self.follow_described_by = value;
|
||||||
self
|
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.
|
/// Restrict the request to the given RDF format.
|
||||||
///
|
///
|
||||||
/// Repeated calls are additive. By default, all formats — even non-RDF formats — are accepted.
|
/// Repeated calls are additive. By default, all formats — even non-RDF formats — are accepted.
|
||||||
@@ -150,19 +165,48 @@ impl ResourceRequest {
|
|||||||
request_builder
|
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.
|
/// 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 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
|
/// the user permits it, then the URL in the
|
||||||
/// [describedby](https://www.w3.org/TR/ldp/#link-relation-describedby) header is used.
|
/// [describedby](https://www.w3.org/TR/ldp/#link-relation-describedby) header is used.
|
||||||
/// Otherwise, the original URL 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> {
|
pub async fn send(&self) -> crate::Result<Resource> {
|
||||||
let request_builder = self.builder.client.head(self.builder.url.clone());
|
let request_builder = self.builder.client.head(self.builder.url.clone());
|
||||||
let mut response = request_builder.send().await?.error_for_status()?;
|
let mut response = request_builder.send().await?;
|
||||||
Self::ensure_ldp_support(&response)?;
|
|
||||||
|
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 url_to_get;
|
||||||
let described_by = Self::extract_described_by(&response);
|
let described_by = Self::extract_described_by(&response);
|
||||||
@@ -178,7 +222,10 @@ impl ResourceRequest {
|
|||||||
request_builder = self.add_media_types(request_builder);
|
request_builder = self.add_media_types(request_builder);
|
||||||
|
|
||||||
response = request_builder.send().await?.error_for_status()?;
|
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
|
let state_token = response
|
||||||
.headers()
|
.headers()
|
||||||
|
|||||||
Reference in New Issue
Block a user