Simplify issuer, fix serialization

Depends on change to rdf-types, PR incoming
This commit is contained in:
asonix 2022-12-11 16:29:48 -06:00
parent 980fd91acb
commit f646d759ca
4 changed files with 226 additions and 393 deletions

View file

@ -20,11 +20,16 @@ itertools = "0.10.5"
json-ld = "0.9.1"
locspan = "0.7.9"
rdf-types = "0.12.4"
sha2 = { version = "0.10.6", optional = true }
hex = { version = "0.4.3", optional = true }
sha2 = { version = "0.10.6", optional = true }
[dev-dependencies]
iref = "2.2.0"
reqwest = "0.11.13"
static-iref = "2.0.0"
tokio = { version = "1", features = ["full"] }
[patch.crates-io]
# json-ld = { path = "../json-ld/json-ld" }
rdf-types = { path = "../rdf-types" }

View file

@ -1,9 +1,9 @@
use contextual::WithContext;
use iref::{Iri, IriBuf};
use json_ld::{
syntax::{parse::MetaError, Parse, Value},
Flatten, JsonLdProcessor, Loader, RemoteDocument,
};
use json_ld_normalization::quad_to_string;
use locspan::{Location, Meta, Span};
use rdf_types::{generator::Blank, vocabulary::Index, IriVocabulary, IriVocabularyMut};
use reqwest::Client;
@ -32,8 +32,8 @@ struct Cache {
struct ReqwestLoader<I = Index, M = Location<I>, T = Value<M>, E = MetaError<M>> {
cache: Cache,
parser: Box<DynParser<I, M, T, E>>,
client: Client,
parser: Box<DynParser<I, M, T, E>>,
}
impl Cache {
@ -55,6 +55,7 @@ impl Cache {
impl<I, M, T, E> ReqwestLoader<I, M, T, E> {
fn new(
cache: Cache,
client: Client,
parser: impl 'static
+ Send
+ Sync
@ -62,18 +63,15 @@ impl<I, M, T, E> ReqwestLoader<I, M, T, E> {
) -> Self {
Self {
cache,
client,
parser: Box::new(parser),
client: Client::builder()
.user_agent("json-ld-playground")
.build()
.expect("Successful client"),
}
}
}
impl<I: Clone> ReqwestLoader<I, Location<I>, Value<Location<I>>, MetaError<Location<I>>> {
fn default_with_cache(cache: Cache) -> Self {
Self::new(cache, |_, file: &I, s| {
fn with_default_parser(cache: Cache, client: Client) -> Self {
Self::new(cache, client, |_, file: &I, s| {
Value::parse_str(s, |span| Location::new(file.clone(), span))
})
}
@ -83,7 +81,12 @@ impl<I: Clone> Default
for ReqwestLoader<I, Location<I>, Value<Location<I>>, MetaError<Location<I>>>
{
fn default() -> Self {
Self::default_with_cache(Cache::default())
let client = Client::builder()
.user_agent("json-ld-playground")
.build()
.expect("Successful client");
Self::with_default_parser(Cache::default(), client)
}
}
@ -96,17 +99,13 @@ impl<I, M, T, E> ReqwestLoader<I, M, T, E> {
let url = vocabulary.iri(url).unwrap().to_owned();
if !PERMITTED_CONTEXTS.contains(&url.as_str()) {
println!("Fetching {url} is not permitted");
return Err(Error::NotPermitted(url));
}
if let Some(cached) = self.cache.get(&url) {
println!("Got {url} from cache");
return Ok(cached);
}
println!("Fetching {url}");
let response = self
.client
.get(url.as_str())
@ -166,22 +165,27 @@ impl<I: Send + Sync, T: Send, M: Send, E> Loader<I, M> for ReqwestLoader<I, M, T
#[tokio::main]
async fn main() -> Result<(), AnyError> {
let cache = Cache::new();
let client = Client::builder()
.user_agent("json-ld-playground")
.build()
.expect("Successful client");
for (iri, document) in [
(
iri!("https://masto.asonix.dog/actor"),
MASTO_ASONIX_DOG_ACTOR,
),
(
iri!("https://relay.asonix.dog/actor"),
RELAY_ASONIX_DOG_ACTOR,
),
(
iri!("https://masto.asonix.dog/users/asonix"),
MASTO_ASONIX_DOG_ASONIX_ACTOR,
),
] {
do_the_thing(cache.clone(), iri, document).await?;
let iris = [
iri!("https://relay.asonix.dog/actor"),
iri!("https://masto.asonix.dog/actor"),
iri!("https://masto.asonix.dog/users/asonix"),
];
for iri in iris {
let document = client
.get(iri.as_str())
.header("accept", "application/activity+json")
.send()
.await?
.text()
.await?;
do_the_thing(cache.clone(), client.clone(), iri, &document).await?;
}
Ok(())
@ -189,8 +193,9 @@ async fn main() -> Result<(), AnyError> {
async fn do_the_thing(
cache: Cache,
client: Client,
iri: Iri<'static>,
document: &'static str,
document: &str,
) -> Result<(), AnyError> {
let mut vocabulary: rdf_types::IndexVocabulary = rdf_types::IndexVocabulary::new();
@ -199,17 +204,18 @@ async fn do_the_thing(
let input = RemoteDocument::new(
Some(iri_index.clone()),
Some("application/activity+json".parse()?),
Value::parse_str(document, |span| Location::new(iri_index, span)).expect("Failed to parse"),
Value::parse_str(document, |span| Location::new(iri_index.clone(), span))
.expect("Failed to parse"),
);
let mut loader = ReqwestLoader::default_with_cache(cache);
let mut loader = ReqwestLoader::with_default_parser(cache, client);
let expanded = input
.expand_with(&mut vocabulary, &mut loader)
.await
.expect("Failed to expand");
let mut pre_gen = Blank::new().with_metadata(Location::new(iri_index, Span::default()));
let mut pre_gen = Blank::new().with_metadata(Location::new(iri_index.clone(), Span::default()));
let flattened = expanded
.flatten_with(&mut vocabulary, &mut pre_gen, true)
@ -222,243 +228,15 @@ async fn do_the_thing(
true,
)?;
for quad in output_document.quads {
let (subject, predicate, object, graph) = quad.into_parts();
let mut strings = output_document
.quads
.iter()
.map(|quad| quad_to_string(quad, &vocabulary))
.collect::<Vec<_>>();
let subject = subject.with(&vocabulary);
let predicate = predicate.with(&vocabulary);
let object = object.with(&vocabulary);
strings.sort();
if let Some(graph) = graph {
let graph = graph.with(&vocabulary);
println!("{subject} {predicate} {object} {graph}");
} else {
println!("{subject} {predicate} {object}");
}
}
println!("{}", strings.join(""));
Ok(())
}
const MASTO_ASONIX_DOG_ACTOR: &'static str = r#"
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"discoverable": "toot:discoverable",
"Device": "toot:Device",
"Ed25519Signature": "toot:Ed25519Signature",
"Ed25519Key": "toot:Ed25519Key",
"Curve25519Key": "toot:Curve25519Key",
"EncryptedMessage": "toot:EncryptedMessage",
"publicKeyBase64": "toot:publicKeyBase64",
"deviceId": "toot:deviceId",
"claim": {
"@type": "@id",
"@id": "toot:claim"
},
"fingerprintKey": {
"@type": "@id",
"@id": "toot:fingerprintKey"
},
"identityKey": {
"@type": "@id",
"@id": "toot:identityKey"
},
"devices": {
"@type": "@id",
"@id": "toot:devices"
},
"messageFranking": "toot:messageFranking",
"messageType": "toot:messageType",
"cipherText": "toot:cipherText",
"suspended": "toot:suspended"
}
],
"id": "https://masto.asonix.dog/actor",
"type": "Application",
"inbox": "https://masto.asonix.dog/actor/inbox",
"outbox": "https://masto.asonix.dog/actor/outbox",
"preferredUsername": "masto.asonix.dog",
"url": "https://masto.asonix.dog/about/more?instance_actor=true",
"manuallyApprovesFollowers": true,
"publicKey": {
"id": "https://masto.asonix.dog/actor#main-key",
"owner": "https://masto.asonix.dog/actor",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx8zXS0QNg9YGUBsxAOBH\nJaxIn7i6t+Z4UOpSFDVa2kP0NvQgIJsq3wzRqvaiuncRWpkyFk1fTakiRGD32xnY\nt+juuAaIBlU8eswKyANFqhcLAvFHmT3rA1848M4/YM19djvlL/PR9T53tPNHU+el\nS9MlsG3o6Zsj8YaUJtCI8RgEuJoROLHUb/V9a3oMQ7CfuIoSvF3VEz3/dRT09RW6\n0wQX7yhka9WlKuayWLWmTcB9lAIX6neBk+qKc8VSEsO7mHkzB8mRgVcS2uYZl1eA\nD8/jTT+SlpeFNDZk0Oh35GNFoOxh9qjRw3NGxu7jJCVBehDODzasOv4xDxKAhONa\njQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"endpoints": {
"sharedInbox": "https://masto.asonix.dog/inbox"
}
}
"#;
const RELAY_ASONIX_DOG_ACTOR: &'static str = r#"
{
"publicKey": {
"id": "https://relay.asonix.dog/actor#main-key",
"owner": "https://relay.asonix.dog/actor",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw8UKc+men4z4Ex3Nx1be\ngti6DXAiRvCMp5pgOld7jjvSv2OqfdT71c1ZmAhNh6fMCSgusrGA+JGysExGODb4\nQw5NFa1jDxQkufMawEPQkvmB24vFi2C0PjVeSzu0oHZWfw4zlt+Lg1pOtlFOf41U\npFg282T+kDqIy968v8bH1wY3XzQ/jyb3jZD58jsXKtNUUzHs2K2IqJSuhuCJSkGd\nMkrCCXgLZ8Tw+dgYF+jLv1YNCPS2zBgM0v3IujNuszyzCobWUMfsGFfIvMORyvNP\nr5oxM1r3JV3CxMOOmskeYyh5DrtVSJ/ZVNabmj+Cy1WMadO8Rpd7e5j12pbY6ORu\nNu6mLOQ0qxX101bKYzVxVCP8KDN3j7RSkPB/K4gKPXZcHAcdRIjnlthDRicwZqW6\n1OsdP45tmfHxSIVNPmn1Wc4xWMsGU+EubmsxfXxkGNSaNpD1sjBf9vcf/c7q3mj8\nqcp74dOAmmO/O1AQnoQKCVqZaT26Db6YRn1P/lBHefdCetapmsyTkJ39Htxp3aAK\n+FHTxxX/hM6PlQwvteIvFHnaYBQgvp37L+zdXh87E2MQN4azj7LoUfmJG+7UVatn\nS61VCwLvKbDvlS8zg4RZo2hCBXG4rnyW8DpD2YdCMTKXvSySEzUF64Jomsxq7vqC\nNLWSay9Y9rhvQswxPe2XYEUCAwEAAQ==\n-----END PUBLIC KEY-----\n"
},
"inbox": "https://relay.asonix.dog/inbox",
"outbox": "https://relay.asonix.dog/outbox",
"following": "https://relay.asonix.dog/following",
"followers": "https://relay.asonix.dog/followers",
"preferredUsername": "relay",
"endpoints": {
"sharedInbox": "https://relay.asonix.dog/inbox"
},
"summary": "AodeRelay bot",
"url": "https://relay.asonix.dog/actor",
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id": "https://relay.asonix.dog/actor",
"type": "Application",
"name": "AodeRelay"
}
"#;
const MASTO_ASONIX_DOG_ASONIX_ACTOR: &'static str = r#"
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"discoverable": "toot:discoverable",
"Device": "toot:Device",
"Ed25519Signature": "toot:Ed25519Signature",
"Ed25519Key": "toot:Ed25519Key",
"Curve25519Key": "toot:Curve25519Key",
"EncryptedMessage": "toot:EncryptedMessage",
"publicKeyBase64": "toot:publicKeyBase64",
"deviceId": "toot:deviceId",
"claim": {
"@type": "@id",
"@id": "toot:claim"
},
"fingerprintKey": {
"@type": "@id",
"@id": "toot:fingerprintKey"
},
"identityKey": {
"@type": "@id",
"@id": "toot:identityKey"
},
"devices": {
"@type": "@id",
"@id": "toot:devices"
},
"messageFranking": "toot:messageFranking",
"messageType": "toot:messageType",
"cipherText": "toot:cipherText",
"suspended": "toot:suspended",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
}
}
],
"id": "https://masto.asonix.dog/users/asonix",
"type": "Person",
"following": "https://masto.asonix.dog/users/asonix/following",
"followers": "https://masto.asonix.dog/users/asonix/followers",
"inbox": "https://masto.asonix.dog/users/asonix/inbox",
"outbox": "https://masto.asonix.dog/users/asonix/outbox",
"featured": "https://masto.asonix.dog/users/asonix/collections/featured",
"featuredTags": "https://masto.asonix.dog/users/asonix/collections/tags",
"preferredUsername": "asonix",
"name": "Liom on Mane -> ANE",
"summary": "<p>26, local liom, friend, rust (lang) stan, bi </p><p>icon by <span class=\"h-card\"><a href=\"https://furaffinity.net/user/lalupine\" target=\"blank\" rel=\"noopener noreferrer\" class=\"u-url mention\">@<span>lalupine@furaffinity.net</span></a></span><br />header by <span class=\"h-card\"><a href=\"https://furaffinity.net/user/tronixx\" target=\"blank\" rel=\"noopener noreferrer\" class=\"u-url mention\">@<span>tronixx@furaffinity.net</span></a></span></p><p>Testimonials:</p><p>Stand: LIONS<br />Stand User: AODE<br />- Keris (not on here)</p>",
"url": "https://masto.asonix.dog/@asonix",
"manuallyApprovesFollowers": true,
"discoverable": true,
"published": "2021-02-09T00:00:00Z",
"devices": "https://masto.asonix.dog/users/asonix/collections/devices",
"publicKey": {
"id": "https://masto.asonix.dog/users/asonix#main-key",
"owner": "https://masto.asonix.dog/users/asonix",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm+YpyXb3bUp5EyryHqRA\npKSvl4RamJh6CLlngYYPFU8lcx92oQR8nFlqOwInAczGPoCoIfojQpZfqV4hFq1I\nlETy6jHHeoO/YkUsH2dYtz6gjEqiZFCFpoWuGxUQO3lwfmPYpxl2/GFEDR4MrUNp\n9fPn9jHUlKydiDkFQqluajqSJgv0BCwnUGBanTEfeQKahnc3OqPTi4xNbsd2cbAW\nZtJ6VYepphQCRHElvkzefe1ra5qm5i8YBdan3Z3oo5wN1vo3u41tqjVGhDptKZkv\nwBevdL0tedoLp5Lj1l/HLTSBP0D0ZT/HUFuo6Zq27PCq/4ZgJaZkMi7YCVVtpjim\nmQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"tag": [],
"attachment": [
{
"type": "PropertyValue",
"name": "pronouns",
"value": "he/they"
},
{
"type": "PropertyValue",
"name": "software",
"value": "bad"
},
{
"type": "PropertyValue",
"name": "gitea",
"value": "<a href=\"https://git.asonix.dog\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"><span class=\"invisible\">https://</span><span class=\"\">git.asonix.dog</span><span class=\"invisible\"></span></a>"
},
{
"type": "PropertyValue",
"name": "join my",
"value": "relay"
}
],
"endpoints": {
"sharedInbox": "https://masto.asonix.dog/inbox"
},
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://masto.asonix.dog/system/accounts/avatars/000/000/001/original/00852df0e6fee7e0.png"
},
"image": {
"type": "Image",
"mediaType": "image/png",
"url": "https://masto.asonix.dog/system/accounts/headers/000/000/001/original/8122ce3e5a745385.png"
}
}
"#;

View file

@ -3,7 +3,7 @@ use indexmap::IndexMap;
use itertools::Itertools;
use json_ld::{rdf::Value, RdfQuads, ValidId as Subject};
use locspan::{Location, Span};
use rdf_types::{generator::Blank, BlankIdVocabularyMut, Vocabulary, VocabularyMut};
use rdf_types::{generator::Blank, BlankIdVocabularyMut, RdfDisplay, Vocabulary, VocabularyMut};
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap, HashSet},
@ -11,26 +11,11 @@ use std::{
};
mod input_dataset;
#[cfg(feature = "rustcrypto")]
mod sha2_impls;
use input_dataset::{InputDataset, NormalizingQuad, Position, QuadSubject, QuadValue};
#[cfg(feature = "rustcrypto")]
mod sha2_impls {
use sha2::{Digest, Sha256};
impl super::Sha256 for Sha256 {
fn update(&mut self, bytes: &[u8]) {
Digest::update(self, bytes)
}
fn finalize_hex_and_reset(&mut self) -> crate::HexHash {
let output = self.finalize_reset();
crate::HexHash(hex::encode(&output))
}
}
}
#[derive(Clone, Debug)]
pub struct Security;
@ -50,18 +35,22 @@ where
pub quads: Vec<NormalizingQuad<N>>,
}
pub struct Issuer<B> {
// Identifier Prefix and Identifier Counter
blank_node_generator: Blank,
// Issued Identifier List
issued_identifier_list: IndexMap<B, B>,
}
pub struct CanonicalizationState<'a, N, S>
where
N: Vocabulary,
{
sha256: S,
// Identifier Prefix and Identifier Counter
blank_node_generator: Blank,
vocabulary: &'a mut N,
// Issued Identifier List
issued_identifier_list: IndexMap<N::BlankId, N::BlankId>,
canonical_issuer: Issuer<N::BlankId>,
blank_node_to_quads: HashMap<N::BlankId, HashSet<Position>>,
hash_to_blank_nodes: BTreeMap<HexHash, HashSet<N::BlankId>>,
@ -88,6 +77,66 @@ where
)
}
impl<B> Issuer<B> {
fn new() -> Self {
Self {
blank_node_generator: canonicalization_node_generator(),
issued_identifier_list: Default::default(),
}
}
fn new_with_prefix(prefix: &str) -> Self {
Self {
blank_node_generator: make_issuer(prefix),
issued_identifier_list: Default::default(),
}
}
fn iter(&self) -> impl Iterator<Item = (&B, &B)> {
self.issued_identifier_list.iter()
}
fn get(&self, identifier: &B) -> Option<&B>
where
B: Eq + Hash,
{
self.issued_identifier_list.get(identifier)
}
fn contains(&self, identifier: &B) -> bool
where
B: Eq + Hash,
{
self.issued_identifier_list.contains_key(identifier)
}
fn issue_identifier<N>(&mut self, identifier: B, vocabulary: &mut N) -> N::BlankId
where
N: Vocabulary<BlankId = B> + BlankIdVocabularyMut,
B: Eq + Hash + Clone,
{
use rdf_types::Generator;
// step 1
if let Some(blank) = self.get(&identifier) {
return blank.clone();
}
// step 2 and 4
let blank = match self.blank_node_generator.next(vocabulary) {
Subject::Blank(blank) => blank,
Subject::Iri(_) => unreachable!("Blank ID generators should only generate blank IDs"),
};
// step 3
self.issued_identifier_list
.insert(identifier, blank.clone());
// step 5
blank
}
}
impl<'a, N, S> CanonicalizationState<'a, N, S>
where
N: Vocabulary,
@ -99,9 +148,8 @@ where
{
Self {
sha256: S::default(),
blank_node_generator: canonicalization_node_generator(),
vocabulary,
issued_identifier_list: Default::default(),
canonical_issuer: Issuer::default(),
blank_node_to_quads: Default::default(),
hash_to_blank_nodes: Default::default(),
}
@ -228,12 +276,8 @@ where
non_normalized_identifiers.remove(&identifier);
// step 5.4.2
issue_identifier_algorithm(
identifier,
&mut self.blank_node_generator,
self.vocabulary,
&mut self.issued_identifier_list,
);
self.canonical_issuer
.issue_identifier(identifier, self.vocabulary);
}
// step 5.4.5
@ -266,7 +310,7 @@ where
// step 6.2
for identifier in identifier_list {
// step 6.2.1
if self.issued_identifier_list.contains_key(&identifier) {
if self.canonical_issuer.contains(&identifier) {
continue;
}
@ -275,18 +319,12 @@ where
}
// step 6.2.2
let mut temporary_issuer = make_issuer("b");
let mut temporary_issuer = Issuer::new_with_prefix("b");
// step 6.2.3
let mut issued_identifier_list = Default::default();
let mut temporary_vocabulary = N::default();
issue_identifier_algorithm(
identifier.clone(),
&mut temporary_issuer,
&mut temporary_vocabulary,
&mut issued_identifier_list,
);
temporary_issuer.issue_identifier(identifier.clone(), &mut temporary_vocabulary);
// step 6.2.4
let hash = hash_n_degree_quads(
@ -294,26 +332,21 @@ where
&mut temporary_vocabulary,
&self.vocabulary,
&mut temporary_issuer,
&mut issued_identifier_list,
&self.issued_identifier_list,
&self.canonical_issuer,
identifier,
input_dataset,
&mut self.sha256,
);
hash_path_list.push((hash, issued_identifier_list));
hash_path_list.push((hash, temporary_issuer));
}
// step 6.3
for (_, issued_identifier_list) in hash_path_list {
for (_, temporary_issuer) in hash_path_list {
// step 6.3.1
for (existing_identifier, _) in issued_identifier_list {
issue_identifier_algorithm(
existing_identifier,
&mut self.blank_node_generator,
self.vocabulary,
&mut self.issued_identifier_list,
);
for (existing_identifier, _) in temporary_issuer.iter() {
self.canonical_issuer
.issue_identifier(existing_identifier.clone(), self.vocabulary);
}
}
}
@ -368,9 +401,9 @@ where
{
match subject {
Subject::Iri(iri) => Some(Subject::Iri(iri.clone())),
Subject::Blank(blank) => Some(Subject::Blank(
self.issued_identifier_list.get(blank)?.clone(),
)),
Subject::Blank(blank) => {
Some(Subject::Blank(self.canonical_issuer.get(blank)?.clone()))
}
}
}
}
@ -379,9 +412,8 @@ fn hash_n_degree_quads<N, S>(
blank_node_to_quads: &HashMap<N::BlankId, HashSet<Position>>,
vocabulary: &mut N,
canon_vocabulary: &N,
issuer: &mut Blank,
issued_identifier_list: &mut IndexMap<N::BlankId, N::BlankId>,
canon_issued_identifier_list: &IndexMap<N::BlankId, N::BlankId>,
issuer: &mut Issuer<N::BlankId>,
canon_issuer: &Issuer<N::BlankId>,
identifier: N::BlankId,
input_dataset: &InputDataset<N>,
sha256: &mut S,
@ -416,9 +448,9 @@ where
// step 3.1.1
let hash = hash_related_blank_node(
blank_node_to_quads,
canon_issued_identifier_list,
canon_issuer,
canon_vocabulary,
issued_identifier_list,
issuer,
vocabulary,
related,
quad,
@ -451,12 +483,10 @@ where
// step 5.3
let mut chosen_issuer = Default::default();
let mut chosen_issued_identifier_list = Default::default();
'permute: for permutation in permute(blank_node_list) {
// step 5.4.1
let mut issuer_copy = Blank::new_full(issuer.prefix().to_string(), issuer.count());
let mut issued_identifier_list_copy = issued_identifier_list.clone();
let mut issuer_copy = issuer.clone();
// step 5.4.2
let mut path = String::new();
// step 5.4.3
@ -464,7 +494,7 @@ where
// step 5.4.4
for related in permutation {
if let Some(blank) = canon_issued_identifier_list.get(&related) {
if let Some(blank) = canon_issuer.get(&related) {
// step 5.4.4.1
if let Some(blank_id) = canon_vocabulary.blank_id(blank) {
path += &blank_id.to_string();
@ -476,12 +506,7 @@ where
// step 5.4.4.2.1
recursion_list.insert(related.clone());
// step 5.4.4.2.2
issue_identifier_algorithm(
related,
&mut issuer_copy,
vocabulary,
&mut issued_identifier_list_copy,
);
issuer_copy.issue_identifier(related, vocabulary);
}
// step 5.4.4.3
@ -499,19 +524,13 @@ where
vocabulary,
canon_vocabulary,
&mut issuer_copy,
&mut issued_identifier_list_copy,
canon_issued_identifier_list,
canon_issuer,
related.clone(),
input_dataset,
sha256,
);
// step 5.4.5.2
let new_blank = issue_identifier_algorithm(
related,
&mut issuer_copy,
vocabulary,
&mut issued_identifier_list_copy,
);
let new_blank = issuer_copy.issue_identifier(related, vocabulary);
if let Some(blank_id) = vocabulary.blank_id(&new_blank) {
path += &blank_id.to_string();
@ -536,7 +555,6 @@ where
if chosen_path.is_empty() || path < chosen_path {
chosen_path = path;
chosen_issuer = issuer_copy;
chosen_issued_identifier_list = issued_identifier_list_copy;
}
}
@ -545,7 +563,6 @@ where
// step 5.6
std::mem::swap(issuer, &mut chosen_issuer);
std::mem::swap(issued_identifier_list, &mut chosen_issued_identifier_list);
}
// step 6
@ -564,9 +581,9 @@ where
fn hash_related_blank_node<N, S>(
blank_node_to_quads: &HashMap<N::BlankId, HashSet<Position>>,
canon_issued_identifier_list: &IndexMap<N::BlankId, N::BlankId>,
canon_issuer: &Issuer<N::BlankId>,
canon_vocabulary: &N,
issued_identifier_list: &IndexMap<N::BlankId, N::BlankId>,
issuer: &Issuer<N::BlankId>,
vocabulary: &N,
related: &N::BlankId,
quad: &NormalizingQuad<N>,
@ -580,12 +597,12 @@ where
S: Sha256,
{
// step 1
let identifier = if let Some(blank_id) = canon_issued_identifier_list.get(related) {
let identifier = if let Some(blank_id) = canon_issuer.get(related) {
let blank = canon_vocabulary
.blank_id(blank_id)
.expect("No blank in vocabulary");
blank.to_string()
} else if let Some(blank_id) = issued_identifier_list.get(related) {
} else if let Some(blank_id) = issuer.get(related) {
let blank = vocabulary
.blank_id(blank_id)
.expect("No blank in vocabulary");
@ -666,24 +683,24 @@ where
N::BlankId: Clone + Eq,
{
let subject = serialize_subject(identifier, quad.subject(), vocabulary);
let predicate = quad.predicate().with(vocabulary);
let predicate = quad.predicate().with(vocabulary).rdf_display().to_string();
let object = serialize_object(identifier, quad.object(), vocabulary);
let graph = quad
.graph()
.map(|graph| serialize_subject(identifier, graph, vocabulary));
if let Some(graph) = graph {
format!("{subject} {predicate} {object} {graph}")
format!("{subject} {predicate} {object} {graph} .\n")
} else {
format!("{subject} {predicate} {object}")
format!("{subject} {predicate} {object} .\n")
}
}
fn serialize_subject<N>(
fn serialize_subject<'a, N>(
identifier: &N::BlankId,
subject: &QuadSubject<N>,
vocabulary: &N,
) -> Cow<'static, str>
subject: &'a QuadSubject<N>,
vocabulary: &'a N,
) -> Cow<'a, str>
where
N: Vocabulary,
N::BlankId: Eq,
@ -693,25 +710,53 @@ where
} else if subject.is_blank() {
Cow::Borrowed("_:z")
} else {
Cow::Owned(subject.with(vocabulary).to_string())
Cow::Owned(subject.with(vocabulary).rdf_display().to_string())
}
}
fn serialize_object<N>(
fn serialize_object<'a, N>(
identifier: &N::BlankId,
object: &QuadValue<N>,
vocabulary: &N,
) -> Cow<'static, str>
object: &'a QuadValue<N>,
vocabulary: &'a N,
) -> Cow<'a, str>
where
N: Vocabulary,
N::BlankId: Eq,
{
match object {
Value::Literal(lit) => Cow::Owned(lit.with(vocabulary).to_string()),
Value::Literal(lit) => Cow::Owned(lit.with(vocabulary).rdf_display().to_string()),
Value::Reference(subject) => serialize_subject(identifier, subject, vocabulary),
}
}
pub fn quad_to_string<N>(quad: &NormalizingQuad<N>, vocabulary: &N) -> String
where
N: Vocabulary,
{
let subject = quad.subject().with(vocabulary).rdf_display().to_string();
let predicate = quad.predicate().with(vocabulary).rdf_display().to_string();
let object = object_to_string(quad.object(), vocabulary);
let graph = quad
.graph()
.map(|graph| graph.with(vocabulary).rdf_display().to_string());
if let Some(graph) = graph {
format!("{subject} {predicate} {object} {graph} .\n")
} else {
format!("{subject} {predicate} {object} .\n")
}
}
fn object_to_string<'a, N>(object: &'a QuadValue<N>, vocabulary: &'a N) -> String
where
N: Vocabulary,
{
match object {
Value::Literal(lit) => lit.with(vocabulary).rdf_display().to_string(),
Value::Reference(subject) => subject.with(vocabulary).rdf_display().to_string(),
}
}
fn matches_identifier<N>(identifier: &N::BlankId, subject: &QuadSubject<N>) -> bool
where
N: Vocabulary,
@ -743,36 +788,6 @@ where
}
}
fn issue_identifier_algorithm<N>(
identifier: N::BlankId,
generator: &mut Blank,
vocabulary: &mut N,
issued_identifier_list: &mut IndexMap<N::BlankId, N::BlankId>,
) -> N::BlankId
where
N: VocabularyMut + BlankIdVocabularyMut,
N::BlankId: Eq + Hash + Clone,
{
use rdf_types::Generator;
// step 1
if let Some(blank) = issued_identifier_list.get(&identifier) {
return blank.clone();
}
// step 2 and 4
let blank = match generator.next(vocabulary) {
Subject::Blank(blank) => blank,
Subject::Iri(_) => unreachable!("Blank ID generators should only generate blank IDs"),
};
// step 3
issued_identifier_list.insert(identifier, blank.clone());
// step 5
blank
}
fn canonicalization_node_generator() -> Blank {
make_issuer("c14n")
}
@ -788,3 +803,24 @@ impl std::fmt::Display for Security {
}
impl std::error::Error for Security {}
impl<B> Clone for Issuer<B>
where
B: Clone,
{
fn clone(&self) -> Self {
Self {
blank_node_generator: Blank::new_full(
self.blank_node_generator.prefix().to_string(),
self.blank_node_generator.count(),
),
issued_identifier_list: self.issued_identifier_list.clone(),
}
}
}
impl<B> Default for Issuer<B> {
fn default() -> Self {
Self::new()
}
}

14
src/sha2_impls.rs Normal file
View file

@ -0,0 +1,14 @@
use sha2::{Digest, Sha256};
impl super::Sha256 for Sha256 {
fn update(&mut self, bytes: &[u8]) {
Digest::update(self, bytes)
}
fn finalize_hex_and_reset(&mut self) -> crate::HexHash {
let output = self.finalize_reset();
crate::HexHash(hex::encode(&output))
}
}