Begin support for Resource names, sizes, and types
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
use crate::vocab;
|
||||
use oxigraph::model::NamedNodeRef;
|
||||
|
||||
/// The type of [Container](https://www.w3.org/TR/ldp/#ldpc).
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ContainerType {
|
||||
Basic,
|
||||
Direct,
|
||||
Indirect,
|
||||
}
|
||||
|
||||
impl ContainerType {
|
||||
pub fn as_named_node(&self) -> NamedNodeRef<'_> {
|
||||
match self {
|
||||
ContainerType::Basic => vocab::ldp::BASIC_CONTAINER,
|
||||
ContainerType::Direct => vocab::ldp::DIRECT_CONTAINER,
|
||||
ContainerType::Indirect => vocab::ldp::INDIRECT_CONTAINER,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_named_node(node: NamedNodeRef<'_>) -> Option<Self> {
|
||||
match node {
|
||||
vocab::ldp::BASIC_CONTAINER => Some(ContainerType::Basic),
|
||||
vocab::ldp::DIRECT_CONTAINER => Some(ContainerType::Direct),
|
||||
vocab::ldp::INDIRECT_CONTAINER => Some(ContainerType::Indirect),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,5 +1,6 @@
|
||||
#![cfg_attr(doc, doc = include_str!("../README.md"))]
|
||||
|
||||
mod container;
|
||||
mod error;
|
||||
pub mod header;
|
||||
pub mod middleware;
|
||||
@@ -14,8 +15,11 @@ pub use oxigraph;
|
||||
pub use reqwest_middleware;
|
||||
pub use reqwest_middleware::reqwest;
|
||||
|
||||
pub use container::ContainerType;
|
||||
pub use error::{Error, Result};
|
||||
pub use rdf_source::{
|
||||
RdfSource, RdfSourceUpdateRequest, RdfSourceUpdateResponse, SerializationOptions,
|
||||
};
|
||||
pub use resource::{Resource, ResourceRequest, ResourceRequestBuilder, ResponseFormat};
|
||||
pub use resource::{
|
||||
Resource, ResourceRequest, ResourceRequestBuilder, ResourceType, ResponseFormat,
|
||||
};
|
||||
|
||||
+29
-7
@@ -1,3 +1,4 @@
|
||||
use crate::resource::ResourceType;
|
||||
use bytes::BufMut;
|
||||
use http::{StatusCode, header};
|
||||
use oxigraph::io::{RdfFormat, RdfSerializer};
|
||||
@@ -11,8 +12,11 @@ use reqwest_middleware::reqwest::Url;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RdfSource<D> {
|
||||
pub(crate) origin: Url,
|
||||
pub(crate) described_by: Option<Url>,
|
||||
pub(crate) origin_type: Option<ResourceType>,
|
||||
pub(crate) state_token: Option<String>,
|
||||
pub(crate) described_by: Option<Url>,
|
||||
pub(crate) file_name: Option<String>,
|
||||
pub(crate) size: Option<usize>,
|
||||
pub(crate) dataset: D,
|
||||
}
|
||||
|
||||
@@ -23,8 +27,11 @@ impl<D: Default> RdfSource<D> {
|
||||
pub fn new(origin: Url) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
described_by: None,
|
||||
origin_type: None,
|
||||
state_token: None,
|
||||
described_by: None,
|
||||
file_name: None,
|
||||
size: None,
|
||||
dataset: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -36,11 +43,9 @@ impl<D> RdfSource<D> {
|
||||
&self.origin
|
||||
}
|
||||
|
||||
/// The URL at which the RDF description of the resource is located.
|
||||
///
|
||||
/// This is the URL at which updates are submitted.
|
||||
pub fn described_by(&self) -> Option<&Url> {
|
||||
self.described_by.as_ref()
|
||||
/// The type of Resource that was originally fetched.
|
||||
pub fn origin_type(&self) -> Option<&ResourceType> {
|
||||
self.origin_type.as_ref()
|
||||
}
|
||||
|
||||
/// The state token of the resource.
|
||||
@@ -50,6 +55,23 @@ impl<D> RdfSource<D> {
|
||||
self.state_token.as_deref()
|
||||
}
|
||||
|
||||
/// The URL at which the RDF description of the resource is located.
|
||||
///
|
||||
/// This is the URL at which updates are submitted.
|
||||
pub fn described_by(&self) -> Option<&Url> {
|
||||
self.described_by.as_ref()
|
||||
}
|
||||
|
||||
/// The name of the file that was originally fetched.
|
||||
pub fn origin_file_name(&self) -> Option<&str> {
|
||||
self.file_name.as_deref()
|
||||
}
|
||||
|
||||
/// The size of the file that was originally fetched.
|
||||
pub fn origin_size(&self) -> Option<usize> {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// The underlying dataset.
|
||||
pub fn dataset(&self) -> &D {
|
||||
&self.dataset
|
||||
|
||||
+80
-3
@@ -1,3 +1,4 @@
|
||||
use crate::container::ContainerType;
|
||||
use crate::rdf_source::RdfSource;
|
||||
use crate::vocab;
|
||||
use bytes::Bytes;
|
||||
@@ -8,6 +9,7 @@ use oxigraph::model::{GraphNameRef, NamedNodeRef, Quad};
|
||||
use reqwest_middleware::reqwest::{Client, Response, StatusCode, Url, header};
|
||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
||||
use sfv::{Item, TokenRef};
|
||||
use std::collections::BTreeSet;
|
||||
use std::str::FromStr;
|
||||
use tracing::error;
|
||||
|
||||
@@ -164,6 +166,34 @@ impl ResourceRequest {
|
||||
Err(crate::Error::LDPUnsupported)
|
||||
}
|
||||
|
||||
fn resource_type(response: &Response) -> Option<ResourceType> {
|
||||
let headers = response.headers();
|
||||
let links = headers
|
||||
.get_all(header::LINK)
|
||||
.iter()
|
||||
.map(|hv| hv.to_str().unwrap_or_default())
|
||||
.filter_map(|link| parse_link_header::parse_with_rel(link).ok())
|
||||
.filter_map(|link_map| link_map.get("type").map(|item| item.raw_uri.clone()))
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
// NB: The comparisons below must be done in this order, because multiple Link headers may
|
||||
// be present with differing degrees of specificity. E.g. BasicContainer is more specific
|
||||
// than RDFSource, but both are valid and may be present concomitantly.
|
||||
if links.contains(vocab::ldp::BASIC_CONTAINER.as_str()) {
|
||||
Some(ResourceType::RdfSource(Some(ContainerType::Basic)))
|
||||
} else if links.contains(vocab::ldp::DIRECT_CONTAINER.as_str()) {
|
||||
Some(ResourceType::RdfSource(Some(ContainerType::Direct)))
|
||||
} else if links.contains(vocab::ldp::INDIRECT_CONTAINER.as_str()) {
|
||||
Some(ResourceType::RdfSource(Some(ContainerType::Indirect)))
|
||||
} else if links.contains(vocab::ldp::RDF_SOURCE.as_str()) {
|
||||
Some(ResourceType::RdfSource(None))
|
||||
} else if links.contains(vocab::ldp::NON_RDF_SOURCE.as_str()) {
|
||||
Some(ResourceType::NonRdfSource)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn add_media_types(&self, mut request_builder: RequestBuilder) -> RequestBuilder {
|
||||
let media_types = self.builder.formats.iter().map(|f| f.media_type());
|
||||
for media_type in media_types {
|
||||
@@ -245,6 +275,8 @@ impl ResourceRequest {
|
||||
None
|
||||
};
|
||||
|
||||
let resource_type = Self::resource_type(&response);
|
||||
|
||||
let url_to_get;
|
||||
let described_by = Self::extract_described_by(&response);
|
||||
if let Some(new_url) = &described_by
|
||||
@@ -286,14 +318,47 @@ impl ResourceRequest {
|
||||
origin: self.builder.url.clone(),
|
||||
described_by,
|
||||
state_token,
|
||||
resource_type,
|
||||
format,
|
||||
size,
|
||||
file_name,
|
||||
size,
|
||||
response,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of [Resource](https://www.w3.org/TR/ldp/#ldpr-resource).
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ResourceType {
|
||||
NonRdfSource,
|
||||
RdfSource(Option<ContainerType>),
|
||||
}
|
||||
|
||||
impl ResourceType {
|
||||
pub fn as_named_node(&self) -> NamedNodeRef<'_> {
|
||||
match self {
|
||||
ResourceType::NonRdfSource => vocab::ldp::NON_RDF_SOURCE,
|
||||
ResourceType::RdfSource(None) => vocab::ldp::RDF_SOURCE,
|
||||
ResourceType::RdfSource(Some(container_type)) => container_type.as_named_node(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_named_node(node: NamedNodeRef<'_>) -> Option<Self> {
|
||||
match node {
|
||||
vocab::ldp::NON_RDF_SOURCE => Some(ResourceType::NonRdfSource),
|
||||
vocab::ldp::RDF_SOURCE => Some(ResourceType::RdfSource(None)),
|
||||
_ => {
|
||||
let container_type = ContainerType::from_named_node(node);
|
||||
if container_type.is_some() {
|
||||
Some(ResourceType::RdfSource(container_type))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The format of the response, as determined by the `Content-Type` HTTP header.
|
||||
pub enum ResponseFormat {
|
||||
RdfFormat(RdfFormat),
|
||||
@@ -313,9 +378,10 @@ pub struct Resource {
|
||||
origin: Url,
|
||||
described_by: Option<Url>,
|
||||
state_token: Option<String>,
|
||||
resource_type: Option<ResourceType>,
|
||||
format: ResponseFormat,
|
||||
size: Option<usize>,
|
||||
file_name: Option<String>,
|
||||
size: Option<usize>,
|
||||
response: Response,
|
||||
}
|
||||
|
||||
@@ -357,6 +423,14 @@ impl Resource {
|
||||
self.file_name.as_deref()
|
||||
}
|
||||
|
||||
/// The type of Resource, as reported by the `Link: <...> rel="type"` HTTP header.
|
||||
///
|
||||
/// Note: This value is extracted from the response headers of the HEAD request for the origin
|
||||
/// URL.
|
||||
pub fn resource_type(&self) -> Option<&ResourceType> {
|
||||
self.resource_type.as_ref()
|
||||
}
|
||||
|
||||
/// The state token, as extracted from the `X-State-Token` header.
|
||||
///
|
||||
/// <div class="warning">
|
||||
@@ -390,8 +464,11 @@ impl Resource {
|
||||
let dataset = quads.collect::<Result<_, _>>()?;
|
||||
Ok(RdfSource {
|
||||
origin: self.origin,
|
||||
described_by: self.described_by,
|
||||
origin_type: self.resource_type,
|
||||
state_token: self.state_token,
|
||||
described_by: self.described_by,
|
||||
file_name: self.file_name,
|
||||
size: self.size,
|
||||
dataset,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -5,4 +5,14 @@ pub mod ldp {
|
||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#contains");
|
||||
pub const RESOURCE: NamedNodeRef<'_> =
|
||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#Resource");
|
||||
pub const NON_RDF_SOURCE: NamedNodeRef<'_> =
|
||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#NonRDFSource");
|
||||
pub const RDF_SOURCE: NamedNodeRef<'_> =
|
||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#RDFSource");
|
||||
pub const BASIC_CONTAINER: NamedNodeRef<'_> =
|
||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#BasicContainer");
|
||||
pub const DIRECT_CONTAINER: NamedNodeRef<'_> =
|
||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#DirectContainer");
|
||||
pub const INDIRECT_CONTAINER: NamedNodeRef<'_> =
|
||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#IndirectContainer");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user