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"))]
|
#![cfg_attr(doc, doc = include_str!("../README.md"))]
|
||||||
|
|
||||||
|
mod container;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
@@ -14,8 +15,11 @@ pub use oxigraph;
|
|||||||
pub use reqwest_middleware;
|
pub use reqwest_middleware;
|
||||||
pub use reqwest_middleware::reqwest;
|
pub use reqwest_middleware::reqwest;
|
||||||
|
|
||||||
|
pub use container::ContainerType;
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use rdf_source::{
|
pub use rdf_source::{
|
||||||
RdfSource, RdfSourceUpdateRequest, RdfSourceUpdateResponse, SerializationOptions,
|
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 bytes::BufMut;
|
||||||
use http::{StatusCode, header};
|
use http::{StatusCode, header};
|
||||||
use oxigraph::io::{RdfFormat, RdfSerializer};
|
use oxigraph::io::{RdfFormat, RdfSerializer};
|
||||||
@@ -11,8 +12,11 @@ use reqwest_middleware::reqwest::Url;
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RdfSource<D> {
|
pub struct RdfSource<D> {
|
||||||
pub(crate) origin: Url,
|
pub(crate) origin: Url,
|
||||||
pub(crate) described_by: Option<Url>,
|
pub(crate) origin_type: Option<ResourceType>,
|
||||||
pub(crate) state_token: Option<String>,
|
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,
|
pub(crate) dataset: D,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +27,11 @@ impl<D: Default> RdfSource<D> {
|
|||||||
pub fn new(origin: Url) -> Self {
|
pub fn new(origin: Url) -> Self {
|
||||||
Self {
|
Self {
|
||||||
origin,
|
origin,
|
||||||
described_by: None,
|
origin_type: None,
|
||||||
state_token: None,
|
state_token: None,
|
||||||
|
described_by: None,
|
||||||
|
file_name: None,
|
||||||
|
size: None,
|
||||||
dataset: Default::default(),
|
dataset: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,11 +43,9 @@ impl<D> RdfSource<D> {
|
|||||||
&self.origin
|
&self.origin
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The URL at which the RDF description of the resource is located.
|
/// The type of Resource that was originally fetched.
|
||||||
///
|
pub fn origin_type(&self) -> Option<&ResourceType> {
|
||||||
/// This is the URL at which updates are submitted.
|
self.origin_type.as_ref()
|
||||||
pub fn described_by(&self) -> Option<&Url> {
|
|
||||||
self.described_by.as_ref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The state token of the resource.
|
/// The state token of the resource.
|
||||||
@@ -50,6 +55,23 @@ impl<D> RdfSource<D> {
|
|||||||
self.state_token.as_deref()
|
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.
|
/// The underlying dataset.
|
||||||
pub fn dataset(&self) -> &D {
|
pub fn dataset(&self) -> &D {
|
||||||
&self.dataset
|
&self.dataset
|
||||||
|
|||||||
+80
-3
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::container::ContainerType;
|
||||||
use crate::rdf_source::RdfSource;
|
use crate::rdf_source::RdfSource;
|
||||||
use crate::vocab;
|
use crate::vocab;
|
||||||
use bytes::Bytes;
|
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::reqwest::{Client, Response, StatusCode, Url, header};
|
||||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
||||||
use sfv::{Item, TokenRef};
|
use sfv::{Item, TokenRef};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
@@ -164,6 +166,34 @@ impl ResourceRequest {
|
|||||||
Err(crate::Error::LDPUnsupported)
|
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 {
|
fn add_media_types(&self, mut request_builder: RequestBuilder) -> RequestBuilder {
|
||||||
let media_types = self.builder.formats.iter().map(|f| f.media_type());
|
let media_types = self.builder.formats.iter().map(|f| f.media_type());
|
||||||
for media_type in media_types {
|
for media_type in media_types {
|
||||||
@@ -245,6 +275,8 @@ impl ResourceRequest {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let resource_type = Self::resource_type(&response);
|
||||||
|
|
||||||
let url_to_get;
|
let url_to_get;
|
||||||
let described_by = Self::extract_described_by(&response);
|
let described_by = Self::extract_described_by(&response);
|
||||||
if let Some(new_url) = &described_by
|
if let Some(new_url) = &described_by
|
||||||
@@ -286,14 +318,47 @@ impl ResourceRequest {
|
|||||||
origin: self.builder.url.clone(),
|
origin: self.builder.url.clone(),
|
||||||
described_by,
|
described_by,
|
||||||
state_token,
|
state_token,
|
||||||
|
resource_type,
|
||||||
format,
|
format,
|
||||||
size,
|
|
||||||
file_name,
|
file_name,
|
||||||
|
size,
|
||||||
response,
|
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.
|
/// The format of the response, as determined by the `Content-Type` HTTP header.
|
||||||
pub enum ResponseFormat {
|
pub enum ResponseFormat {
|
||||||
RdfFormat(RdfFormat),
|
RdfFormat(RdfFormat),
|
||||||
@@ -313,9 +378,10 @@ pub struct Resource {
|
|||||||
origin: Url,
|
origin: Url,
|
||||||
described_by: Option<Url>,
|
described_by: Option<Url>,
|
||||||
state_token: Option<String>,
|
state_token: Option<String>,
|
||||||
|
resource_type: Option<ResourceType>,
|
||||||
format: ResponseFormat,
|
format: ResponseFormat,
|
||||||
size: Option<usize>,
|
|
||||||
file_name: Option<String>,
|
file_name: Option<String>,
|
||||||
|
size: Option<usize>,
|
||||||
response: Response,
|
response: Response,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,6 +423,14 @@ impl Resource {
|
|||||||
self.file_name.as_deref()
|
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.
|
/// The state token, as extracted from the `X-State-Token` header.
|
||||||
///
|
///
|
||||||
/// <div class="warning">
|
/// <div class="warning">
|
||||||
@@ -390,8 +464,11 @@ impl Resource {
|
|||||||
let dataset = quads.collect::<Result<_, _>>()?;
|
let dataset = quads.collect::<Result<_, _>>()?;
|
||||||
Ok(RdfSource {
|
Ok(RdfSource {
|
||||||
origin: self.origin,
|
origin: self.origin,
|
||||||
described_by: self.described_by,
|
origin_type: self.resource_type,
|
||||||
state_token: self.state_token,
|
state_token: self.state_token,
|
||||||
|
described_by: self.described_by,
|
||||||
|
file_name: self.file_name,
|
||||||
|
size: self.size,
|
||||||
dataset,
|
dataset,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,4 +5,14 @@ pub mod ldp {
|
|||||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#contains");
|
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#contains");
|
||||||
pub const RESOURCE: NamedNodeRef<'_> =
|
pub const RESOURCE: NamedNodeRef<'_> =
|
||||||
NamedNodeRef::new_unchecked("http://www.w3.org/ns/ldp#Resource");
|
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