Add documentation, refactor public-facing API
This commit is contained in:
Generated
+1
-212
@@ -2,21 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "addr2line"
|
|
||||||
version = "0.25.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
|
||||||
dependencies = [
|
|
||||||
"gimli",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "adler2"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -49,21 +34,6 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "backtrace"
|
|
||||||
version = "0.3.76"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
|
||||||
dependencies = [
|
|
||||||
"addr2line",
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"miniz_oxide",
|
|
||||||
"object",
|
|
||||||
"rustc-demangle",
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -155,33 +125,6 @@ dependencies = [
|
|||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "color-eyre"
|
|
||||||
version = "0.6.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d"
|
|
||||||
dependencies = [
|
|
||||||
"backtrace",
|
|
||||||
"color-spantrace",
|
|
||||||
"eyre",
|
|
||||||
"indenter",
|
|
||||||
"once_cell",
|
|
||||||
"owo-colors",
|
|
||||||
"tracing-error",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "color-spantrace"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"owo-colors",
|
|
||||||
"tracing-core",
|
|
||||||
"tracing-error",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -248,26 +191,6 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "errno"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "eyre"
|
|
||||||
version = "0.6.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
|
||||||
dependencies = [
|
|
||||||
"indenter",
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -393,12 +316,6 @@ dependencies = [
|
|||||||
"wasip2",
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gimli"
|
|
||||||
version = "0.32.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -602,12 +519,6 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indenter"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
@@ -663,20 +574,9 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ldctl"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"color-eyre",
|
|
||||||
"futures",
|
|
||||||
"ldp",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ldp"
|
name = "ldp"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -749,15 +649,6 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "miniz_oxide"
|
|
||||||
version = "0.8.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
|
||||||
dependencies = [
|
|
||||||
"adler2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -779,27 +670,12 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "object"
|
|
||||||
version = "0.37.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "owo-colors"
|
|
||||||
version = "4.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxigraph"
|
name = "oxigraph"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@@ -924,16 +800,6 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.12.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.9.12"
|
version = "0.9.12"
|
||||||
@@ -1171,12 +1037,6 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-demangle"
|
|
||||||
version = "0.1.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.2"
|
version = "2.1.2"
|
||||||
@@ -1252,31 +1112,12 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sharded-slab"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "signal-hook-registry"
|
|
||||||
version = "1.4.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
|
||||||
dependencies = [
|
|
||||||
"errno",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -1423,15 +1264,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thread_local"
|
|
||||||
version = "1.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@@ -1448,28 +1280,13 @@ version = "1.52.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-macros"
|
|
||||||
version = "2.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@@ -1557,28 +1374,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"valuable",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-error"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
|
|
||||||
dependencies = [
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-subscriber"
|
|
||||||
version = "0.3.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
|
||||||
dependencies = [
|
|
||||||
"sharded-slab",
|
|
||||||
"thread_local",
|
|
||||||
"tracing-core",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1617,12 +1412,6 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "valuable"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"ldp",
|
"ldp",
|
||||||
"ldctl",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "ldctl"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
println!("Hello, world!");
|
|
||||||
}
|
|
||||||
+6
-1
@@ -1,7 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ldp"
|
name = "ldp"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
description = "A library to assist with the creation and maintenance of remote RDF data via LDP"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "GPL-3.0-only"
|
||||||
|
keywords = ["ldp", "rdf", "sparql"]
|
||||||
|
categories = ["database", "web-programming::http-client"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# LDP
|
||||||
|
|
||||||
|
This library is intended to implement the [Linked Data Platform](https://www.w3.org/TR/ldp/) standard, to facilitate the
|
||||||
|
creation and maintenance of RDF data on remote servers.
|
||||||
+4
-6
@@ -1,7 +1,9 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// The result type for the library.
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// The error type for the library.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@@ -13,15 +15,11 @@ pub enum Error {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ReqwestMiddleware(#[from] reqwest_middleware::Error),
|
ReqwestMiddleware(#[from] reqwest_middleware::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
/// The server did not advertise LDP support.
|
||||||
InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
|
|
||||||
|
|
||||||
#[error("Server did not advertise LDP support")]
|
#[error("Server did not advertise LDP support")]
|
||||||
LDPUnsupported,
|
LDPUnsupported,
|
||||||
|
|
||||||
|
/// The response from the server was not in a format we understand.
|
||||||
#[error("Response was not in a supported RDF format")]
|
#[error("Response was not in a supported RDF format")]
|
||||||
UnsupportedFormat,
|
UnsupportedFormat,
|
||||||
|
|
||||||
#[error("Document has been modified since last fetch, and overwrite was not enabled")]
|
|
||||||
DocumentModified,
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-7
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(doc, doc = include_str!("../README.md"))]
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
@@ -5,12 +7,11 @@ mod rdf_source;
|
|||||||
mod resource;
|
mod resource;
|
||||||
pub mod vocab;
|
pub mod vocab;
|
||||||
|
|
||||||
pub use http::{HeaderName, HeaderValue};
|
pub use http;
|
||||||
pub use oxigraph;
|
pub use oxigraph;
|
||||||
pub use reqwest_middleware::ClientBuilder;
|
pub use reqwest_middleware;
|
||||||
pub use reqwest_middleware::reqwest::Client;
|
pub use reqwest_middleware::reqwest;
|
||||||
pub use reqwest_middleware::reqwest::Url;
|
|
||||||
|
|
||||||
pub use error::Result;
|
pub use error::{Error, Result};
|
||||||
pub use rdf_source::RdfSource;
|
pub use rdf_source::{RdfSource, RdfSourceUpdateRequest, RdfSourceUpdateResponse};
|
||||||
pub use resource::{Resource, ResourceRequestBuilder};
|
pub use resource::{Resource, ResourceRequest, ResourceRequestBuilder, ResponseFormat};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use reqwest_middleware::reqwest::header::HeaderValue;
|
|||||||
use reqwest_middleware::reqwest::{Request, Response, header};
|
use reqwest_middleware::reqwest::{Request, Response, header};
|
||||||
use reqwest_middleware::{Middleware, Next};
|
use reqwest_middleware::{Middleware, Next};
|
||||||
|
|
||||||
|
/// HTTP Basic Authentication
|
||||||
pub struct BasicAuthMiddleware {
|
pub struct BasicAuthMiddleware {
|
||||||
username: String,
|
username: String,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
|
|||||||
+83
-28
@@ -1,11 +1,12 @@
|
|||||||
use crate::{Url, error};
|
|
||||||
use bytes::BufMut;
|
use bytes::BufMut;
|
||||||
use http::{HeaderValue, StatusCode, header};
|
use http::{StatusCode, header};
|
||||||
use oxigraph::io::{RdfFormat, RdfSerializer};
|
use oxigraph::io::{RdfFormat, RdfSerializer};
|
||||||
use oxigraph::model::Dataset;
|
use oxigraph::model::Dataset;
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use reqwest_middleware::reqwest::Request;
|
use reqwest_middleware::reqwest::Url;
|
||||||
|
|
||||||
|
/// A LDP [RDF Source](https://www.w3.org/TR/ldp/#ldprs).
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct RdfSource {
|
pub struct RdfSource {
|
||||||
pub(crate) origin: Url,
|
pub(crate) origin: Url,
|
||||||
pub(crate) described_by: Option<Url>,
|
pub(crate) described_by: Option<Url>,
|
||||||
@@ -14,22 +15,31 @@ pub struct RdfSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RdfSource {
|
impl RdfSource {
|
||||||
|
/// The original URL used to procure this RDF Source.
|
||||||
pub fn origin(&self) -> &Url {
|
pub fn origin(&self) -> &Url {
|
||||||
&self.origin
|
&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> {
|
pub fn described_by(&self) -> Option<&Url> {
|
||||||
self.described_by.as_ref()
|
self.described_by.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The state token of the resource.
|
||||||
|
///
|
||||||
|
/// This is used for optimistic locking.
|
||||||
pub fn state_token(&self) -> Option<&str> {
|
pub fn state_token(&self) -> Option<&str> {
|
||||||
self.state_token.as_deref()
|
self.state_token.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The underlying Dataset.
|
||||||
pub fn dataset(&self) -> &Dataset {
|
pub fn dataset(&self) -> &Dataset {
|
||||||
&self.dataset
|
&self.dataset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serializes the Dataset in to the provided format.
|
||||||
pub fn serialize(&self, format: RdfFormat) -> crate::Result<bytes::Bytes> {
|
pub fn serialize(&self, format: RdfFormat) -> crate::Result<bytes::Bytes> {
|
||||||
let writer = bytes::BytesMut::new().writer();
|
let writer = bytes::BytesMut::new().writer();
|
||||||
let mut serializer = RdfSerializer::from_format(format).for_writer(writer);
|
let mut serializer = RdfSerializer::from_format(format).for_writer(writer);
|
||||||
@@ -48,40 +58,85 @@ impl RdfSource {
|
|||||||
Ok(finished_writer.into_inner().freeze())
|
Ok(finished_writer.into_inner().freeze())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_request(
|
/// Prepare an update request.
|
||||||
&self,
|
pub fn to_update(&self, format: RdfFormat) -> crate::Result<RdfSourceUpdateRequest> {
|
||||||
client: ClientWithMiddleware,
|
|
||||||
format: RdfFormat,
|
|
||||||
) -> crate::Result<Request> {
|
|
||||||
let url = self.described_by.clone().unwrap_or(self.origin.clone());
|
let url = self.described_by.clone().unwrap_or(self.origin.clone());
|
||||||
|
let media_type = format.media_type().to_string();
|
||||||
let body = self.serialize(format)?;
|
let body = self.serialize(format)?;
|
||||||
Ok(client
|
|
||||||
.put(url)
|
Ok(RdfSourceUpdateRequest {
|
||||||
.header(header::CONTENT_TYPE, format.media_type())
|
url,
|
||||||
.body(body)
|
state_token: self.state_token.clone(),
|
||||||
.build()?)
|
media_type,
|
||||||
|
body,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An update request.
|
||||||
|
pub struct RdfSourceUpdateRequest {
|
||||||
|
url: Url,
|
||||||
|
state_token: Option<String>,
|
||||||
|
media_type: String,
|
||||||
|
body: bytes::Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An update response.
|
||||||
|
///
|
||||||
|
/// If the document was modified since it was last fetched, and if the user set `overwrite` to
|
||||||
|
/// `false`, then the request will be returned back to the user so it can be re-submitted.
|
||||||
|
pub enum RdfSourceUpdateResponse {
|
||||||
|
/// The update succeeded.
|
||||||
|
Success,
|
||||||
|
/// The update failed specifically because of optimistic locking, and `overwrite` was disabled.
|
||||||
|
/// The original request is preserved here to allow the user to cheaply re-submit the request
|
||||||
|
/// with `overwrite` set to `true`.
|
||||||
|
DocumentModified(RdfSourceUpdateRequest),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RdfSourceUpdateRequest {
|
||||||
|
/// Send the update request.
|
||||||
|
///
|
||||||
|
/// If `overwrite` is `true`, then optimistic locking is disabled. In the event of a failed
|
||||||
|
/// update, the original request is preserved to permit the user to cheaply re-submit it. This
|
||||||
|
/// avoids unnecessary cloning/serialization.
|
||||||
|
///
|
||||||
|
/// Optimistic locking is implemented via the `X-State-Token` and `X-If-State-Token` HTTP
|
||||||
|
/// headers. The [412 Precondition Failed](https://http.dev/412) status code is used to
|
||||||
|
/// determine whether the update failed specifically because of optimistic locking.
|
||||||
pub async fn send(
|
pub async fn send(
|
||||||
&self,
|
self,
|
||||||
client: ClientWithMiddleware,
|
client: ClientWithMiddleware,
|
||||||
mut request: Request,
|
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<RdfSourceUpdateResponse> {
|
||||||
if !overwrite && let Some(state_token) = &self.state_token {
|
if overwrite {
|
||||||
let value = HeaderValue::from_str(state_token.as_str())?;
|
client
|
||||||
request
|
.put(self.url)
|
||||||
.headers_mut()
|
.header(header::CONTENT_TYPE, self.media_type)
|
||||||
.insert(crate::header::X_IF_STATE_TOKEN, value);
|
.body(self.body)
|
||||||
}
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
Ok(RdfSourceUpdateResponse::Success)
|
||||||
|
} else {
|
||||||
|
let mut builder = client
|
||||||
|
.put(self.url.clone())
|
||||||
|
.header(header::CONTENT_TYPE, self.media_type.clone());
|
||||||
|
|
||||||
let response = client.execute(request).await?;
|
if let Some(state_token) = &self.state_token {
|
||||||
|
builder = builder.header(crate::header::X_IF_STATE_TOKEN, state_token.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
match response.status() {
|
let response = builder.body(self.body.clone()).send().await?;
|
||||||
StatusCode::PRECONDITION_FAILED => Err(error::Error::DocumentModified),
|
|
||||||
_ => {
|
match response.status() {
|
||||||
response.error_for_status()?;
|
StatusCode::PRECONDITION_FAILED => {
|
||||||
Ok(())
|
Ok(RdfSourceUpdateResponse::DocumentModified(self))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
response.error_for_status()?;
|
||||||
|
Ok(RdfSourceUpdateResponse::Success)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-36
@@ -1,5 +1,5 @@
|
|||||||
use crate::rdf_source::RdfSource;
|
use crate::rdf_source::RdfSource;
|
||||||
use crate::{error, vocab};
|
use crate::vocab;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use oxigraph::io::{RdfFormat, RdfParser};
|
use oxigraph::io::{RdfFormat, RdfParser};
|
||||||
@@ -8,6 +8,23 @@ use reqwest_middleware::reqwest::{Client, Response, StatusCode, Url, header};
|
|||||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
/// Builds an HTTP request for a [Resource](https://www.w3.org/TR/ldp/#ldpr).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// use ldp::reqwest::{Client, Url};
|
||||||
|
/// use ldp::reqwest_middleware::ClientBuilder;
|
||||||
|
/// use ldp::ResourceRequestBuilder;
|
||||||
|
///
|
||||||
|
/// let client = ClientBuilder::new(Client::new()).build();
|
||||||
|
/// let url = Url::parse("http://server/resource")?;
|
||||||
|
/// let resource = ResourceRequestBuilder::with_client_and_url(client.clone(), url)
|
||||||
|
/// .follow_described_by(true)
|
||||||
|
/// .accept_all_rdf_formats()
|
||||||
|
/// .send();
|
||||||
|
/// .await?;
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct ResourceRequestBuilder {
|
pub struct ResourceRequestBuilder {
|
||||||
client: ClientWithMiddleware,
|
client: ClientWithMiddleware,
|
||||||
url: Url,
|
url: Url,
|
||||||
@@ -16,10 +33,14 @@ pub struct ResourceRequestBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceRequestBuilder {
|
impl ResourceRequestBuilder {
|
||||||
|
/// Creates a new request.
|
||||||
|
///
|
||||||
|
/// A reqwest client will be created and managed by this library.
|
||||||
pub fn new(url: Url) -> Self {
|
pub fn new(url: Url) -> Self {
|
||||||
Self::with_client_and_url(ClientBuilder::new(Client::new()).build(), url)
|
Self::with_client_and_url(ClientBuilder::new(Client::new()).build(), url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new request with the given HTTP client.
|
||||||
pub fn with_client_and_url(client: ClientWithMiddleware, url: Url) -> Self {
|
pub fn with_client_and_url(client: ClientWithMiddleware, url: Url) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
@@ -29,17 +50,23 @@ impl ResourceRequestBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If the HTTP response includes a [describedby](https://www.w3.org/TR/ldp/#link-relation-describedby) link rel, then it will be followed.
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allow_format(mut self, format: RdfFormat) -> Self {
|
/// Restrict the request to the given RDF format.
|
||||||
|
///
|
||||||
|
/// Repeated calls are additive. By default, all formats — even non-RDF formats — are accepted.
|
||||||
|
/// If your intention is to process non-RDF data, then this should not be called.
|
||||||
|
pub fn accept_rdf_format(mut self, format: RdfFormat) -> Self {
|
||||||
self.formats.push(format);
|
self.formats.push(format);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allow_only_supported_formats(mut self) -> Self {
|
/// Restrict the request to all the formats supported by the underlying parser.
|
||||||
|
pub fn accept_all_rdf_formats(mut self) -> Self {
|
||||||
self.formats = vec.
|
||||||
origin: Url,
|
#[derive(Clone, Debug)]
|
||||||
described_by: Option<Url>,
|
pub struct ResourceRequest {
|
||||||
state_token: Option<String>,
|
builder: ResourceRequestBuilder,
|
||||||
format: Option<RdfFormat>,
|
|
||||||
response: Response,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource {
|
impl ResourceRequest {
|
||||||
/// Described by example:
|
/// Described by example:
|
||||||
/// Link: <http://fedora.quill.lan/rest/E2/fcr:metadata>; rel="describedby"
|
/// Link: <http://fedora.quill.lan/rest/E2/fcr:metadata>; rel="describedby"
|
||||||
fn extract_described_by(response: &Response) -> Option<Url> {
|
fn extract_described_by(response: &Response) -> Option<Url> {
|
||||||
@@ -104,41 +130,46 @@ impl Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error::Error::LDPUnsupported)
|
Err(crate::Error::LDPUnsupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_media_types(
|
fn add_media_types(&self, mut request_builder: RequestBuilder) -> RequestBuilder {
|
||||||
formats: Vec<RdfFormat>,
|
let media_types = self.builder.formats.iter().map(|f| f.media_type());
|
||||||
mut request_builder: RequestBuilder,
|
|
||||||
) -> RequestBuilder {
|
|
||||||
let media_types = formats.iter().map(|f| f.media_type());
|
|
||||||
for media_type in media_types {
|
for media_type in media_types {
|
||||||
request_builder = request_builder.header(header::ACCEPT, media_type);
|
request_builder = request_builder.header(header::ACCEPT, media_type);
|
||||||
}
|
}
|
||||||
request_builder
|
request_builder
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_builder(builder: ResourceRequestBuilder) -> crate::Result<Self> {
|
/// Send the request.
|
||||||
let request_builder = builder.client.head(builder.url.clone());
|
///
|
||||||
|
/// 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.
|
||||||
|
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()?;
|
let mut response = request_builder.send().await?.error_for_status()?;
|
||||||
Resource::ensure_ldp_support(&response)?;
|
Self::ensure_ldp_support(&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
|
||||||
&& builder.follow_described_by
|
&& self.builder.follow_described_by
|
||||||
{
|
{
|
||||||
url_to_get = new_url.clone();
|
url_to_get = new_url.clone();
|
||||||
} else {
|
} else {
|
||||||
url_to_get = builder.url.clone();
|
url_to_get = self.builder.url.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut request_builder = builder.client.get(url_to_get);
|
let mut request_builder = self.builder.client.get(url_to_get);
|
||||||
if !builder.formats.is_empty() {
|
request_builder = self.add_media_types(request_builder);
|
||||||
request_builder = Self::add_media_types(builder.formats, request_builder);
|
|
||||||
}
|
|
||||||
response = request_builder.send().await?.error_for_status()?;
|
response = request_builder.send().await?.error_for_status()?;
|
||||||
Resource::ensure_ldp_support(&response)?;
|
Self::ensure_ldp_support(&response)?;
|
||||||
|
|
||||||
let state_token = response
|
let state_token = response
|
||||||
.headers()
|
.headers()
|
||||||
@@ -148,28 +179,82 @@ impl Resource {
|
|||||||
let format = response
|
let format = response
|
||||||
.headers()
|
.headers()
|
||||||
.get(header::CONTENT_TYPE)
|
.get(header::CONTENT_TYPE)
|
||||||
.map(|hv| hv.to_str().unwrap_or_default())
|
.and_then(|hv| hv.to_str().ok())
|
||||||
.and_then(RdfFormat::from_media_type);
|
.map(|value| {
|
||||||
|
if let Some(format) = RdfFormat::from_media_type(value) {
|
||||||
|
ResponseFormat::RdfFormat(format)
|
||||||
|
} else {
|
||||||
|
ResponseFormat::Other(value.to_string())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(ResponseFormat::Unspecified);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Resource {
|
||||||
origin: builder.url,
|
origin: self.builder.url.clone(),
|
||||||
described_by,
|
described_by,
|
||||||
state_token,
|
state_token,
|
||||||
format,
|
format,
|
||||||
response,
|
response,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format(&self) -> Option<RdfFormat> {
|
/// The format of the response, as determined by the `Content-Type` HTTP header.
|
||||||
self.format
|
pub enum ResponseFormat {
|
||||||
|
RdfFormat(RdfFormat),
|
||||||
|
Other(String),
|
||||||
|
Unspecified,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A LDP [Resource](https://www.w3.org/TR/ldp/#ldpr).
|
||||||
|
///
|
||||||
|
/// A Resource on its own is not very useful, as it is a very abstract concept. If the intention is
|
||||||
|
/// to treat the response as opaque data (such as a media file), call [`Resource::into_stream`]. In
|
||||||
|
/// the spec, this is equivalent to a [NonRDFSource](https://www.w3.org/TR/ldp/#ldpnr).
|
||||||
|
///
|
||||||
|
/// If the intention is to treat the response as RDF data to be parsed, call
|
||||||
|
/// [`Resource::into_rdf_source`].
|
||||||
|
pub struct Resource {
|
||||||
|
origin: Url,
|
||||||
|
described_by: Option<Url>,
|
||||||
|
state_token: Option<String>,
|
||||||
|
format: ResponseFormat,
|
||||||
|
response: Response,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resource {
|
||||||
|
/// The original URL used to make the request.
|
||||||
|
pub fn origin(&self) -> &Url {
|
||||||
|
&self.origin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The URL used to describe the Resource, which may be different from the Resource itself.
|
||||||
|
///
|
||||||
|
/// This is useful because it allows RDF data to be attached to non-RDF data, such as a video.
|
||||||
|
pub fn described_by(&self) -> Option<&Url> {
|
||||||
|
self.described_by.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The format of the response.
|
||||||
|
pub fn format(&self) -> &ResponseFormat {
|
||||||
|
&self.format
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state token, as extracted from the `X-State-Token` header.
|
||||||
|
///
|
||||||
|
/// Note that this is a feature [specific to Fedora](https://fedora.info/2021/05/01/spec/#state-tokens).
|
||||||
|
pub fn state_token(&self) -> Option<&String> {
|
||||||
|
self.state_token.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the response as a stream of unparsed bytes.
|
||||||
pub fn into_stream(self) -> impl Stream<Item = reqwest_middleware::reqwest::Result<Bytes>> {
|
pub fn into_stream(self) -> impl Stream<Item = reqwest_middleware::reqwest::Result<Bytes>> {
|
||||||
self.response.bytes_stream()
|
self.response.bytes_stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the response.
|
||||||
pub async fn into_rdf_source(self) -> crate::Result<RdfSource> {
|
pub async fn into_rdf_source(self) -> crate::Result<RdfSource> {
|
||||||
if let Some(format) = self.format {
|
if let ResponseFormat::RdfFormat(format) = self.format {
|
||||||
let graph_url = self.described_by.as_ref().unwrap_or(&self.origin);
|
let graph_url = self.described_by.as_ref().unwrap_or(&self.origin);
|
||||||
let graph = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked(graph_url.as_str()));
|
let graph = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked(graph_url.as_str()));
|
||||||
let parser = RdfParser::from_format(format).with_default_graph(graph);
|
let parser = RdfParser::from_format(format).with_default_graph(graph);
|
||||||
@@ -183,7 +268,7 @@ impl Resource {
|
|||||||
dataset,
|
dataset,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(error::Error::UnsupportedFormat)
|
Err(crate::Error::UnsupportedFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user