Skip to content

Commit

Permalink
chore: Rework Registry
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <[email protected]>
  • Loading branch information
Stranger6667 committed Feb 3, 2025
1 parent e1fb6fe commit baf1796
Show file tree
Hide file tree
Showing 16 changed files with 546 additions and 192 deletions.
1 change: 1 addition & 0 deletions crates/jsonschema-referencing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ license.workspace = true
ahash.workspace = true
fluent-uri = { version = "0.3.2", features = ["serde"] }
once_cell = "1.20.1"
parking_lot = "0.12.3"
percent-encoding = "2.3.1"
serde_json.workspace = true

Expand Down
11 changes: 7 additions & 4 deletions crates/jsonschema-referencing/src/anchors/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@
use std::{
borrow::Borrow,
hash::{Hash, Hasher},
sync::Arc,
};

use fluent_uri::Uri;

use super::AnchorName;

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct AnchorKey {
uri: Uri<String>,
name: String,
uri: Arc<Uri<String>>,
name: AnchorName,
}

impl AnchorKey {
pub(crate) fn new(uri: Uri<String>, name: String) -> Self {
pub(crate) fn new(uri: Arc<Uri<String>>, name: AnchorName) -> Self {
Self { uri, name }
}
}
Expand All @@ -50,7 +53,7 @@ pub(crate) trait BorrowDyn {

impl BorrowDyn for AnchorKey {
fn borrowed_key(&self) -> AnchorKeyRef {
AnchorKeyRef::new(&self.uri, &self.name)
AnchorKeyRef::new(&self.uri, self.name.as_str())
}
}

Expand Down
109 changes: 77 additions & 32 deletions crates/jsonschema-referencing/src/anchors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,80 @@
use std::sync::Arc;
use std::{
hash::Hash,
sync::atomic::{AtomicPtr, Ordering},
};

use serde_json::Value;

mod keys;

use crate::{Draft, Error, Resolved, Resolver, Resource};
use crate::{resource::InnerResourcePtr, Draft, Error, Resolved, Resolver};
pub(crate) use keys::{AnchorKey, AnchorKeyRef};

#[derive(Debug)]
pub(crate) struct AnchorName {
ptr: AtomicPtr<u8>,
len: usize,
}

impl AnchorName {
fn new(s: &str) -> Self {
Self {
ptr: AtomicPtr::new(s.as_ptr().cast_mut()),
len: s.len(),
}
}

fn as_str(&self) -> &str {
unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
self.ptr.load(Ordering::Relaxed),
self.len,
))
}
}
}

impl Clone for AnchorName {
fn clone(&self) -> Self {
Self {
ptr: AtomicPtr::new(self.ptr.load(Ordering::Relaxed)),
len: self.len,
}
}
}

impl Hash for AnchorName {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}

impl PartialEq for AnchorName {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}

impl Eq for AnchorName {}

/// An anchor within a resource.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Anchor {
Default {
draft: Draft,
name: String,
resource: Arc<Resource>,
name: AnchorName,
resource: InnerResourcePtr,
},
/// Dynamic anchors from Draft 2020-12.
Dynamic {
draft: Draft,
name: String,
resource: Arc<Resource>,
name: AnchorName,
resource: InnerResourcePtr,
},
}

impl Anchor {
/// Anchor's name.
pub(crate) fn name(&self) -> &str {
pub(crate) fn name(&self) -> AnchorName {
match self {
Anchor::Default { name, .. } | Anchor::Dynamic { name, .. } => name,
Anchor::Default { name, .. } | Anchor::Dynamic { name, .. } => name.clone(),
}
}
/// Get the resource for this anchor.
Expand All @@ -38,10 +85,10 @@ impl Anchor {
resolver,
resource.draft(),
)),
Anchor::Dynamic { name, resource, .. } => {
Anchor::Dynamic { name, resource } => {
let mut last = resource;
for uri in &resolver.dynamic_scope() {
match resolver.registry.anchor(uri, name) {
match resolver.registry.anchor(uri, name.as_str()) {
Ok(anchor) => {
if let Anchor::Dynamic { resource, .. } = anchor {
last = resource;
Expand All @@ -53,7 +100,7 @@ impl Anchor {
}
Ok(Resolved::new(
last.contents(),
resolver.in_subresource((**last).as_ref())?,
resolver.in_subresource_inner(last)?,
last.draft(),
))
}
Expand All @@ -68,18 +115,16 @@ pub(crate) fn anchor(draft: Draft, contents: &Value) -> Box<dyn Iterator<Item =
.get("$anchor")
.and_then(Value::as_str)
.map(|name| Anchor::Default {
draft,
name: name.to_string(),
resource: Arc::new(draft.create_resource(contents.clone())),
name: AnchorName::new(name),
resource: InnerResourcePtr::new(contents, draft),
});

let dynamic_anchor = schema
.get("$dynamicAnchor")
.and_then(Value::as_str)
.map(|name| Anchor::Dynamic {
draft,
name: name.to_string(),
resource: Arc::new(draft.create_resource(contents.clone())),
name: AnchorName::new(name),
resource: InnerResourcePtr::new(contents, draft),
});

default_anchor.into_iter().chain(dynamic_anchor)
Expand All @@ -90,11 +135,11 @@ pub(crate) fn anchor_2019(draft: Draft, contents: &Value) -> Box<dyn Iterator<It
Box::new(
contents
.as_object()
.and_then(|schema| schema.get("$anchor").and_then(Value::as_str))
.and_then(|schema| schema.get("$anchor"))
.and_then(Value::as_str)
.map(move |name| Anchor::Default {
draft,
name: name.to_string(),
resource: Arc::new(draft.create_resource(contents.clone())),
name: AnchorName::new(name),
resource: InnerResourcePtr::new(contents, draft),
})
.into_iter(),
)
Expand All @@ -107,12 +152,12 @@ pub(crate) fn legacy_anchor_in_dollar_id(
Box::new(
contents
.as_object()
.and_then(|schema| schema.get("$id").and_then(Value::as_str))
.and_then(|schema| schema.get("$id"))
.and_then(Value::as_str)
.and_then(|id| id.strip_prefix('#'))
.map(move |id| Anchor::Default {
draft,
name: id.to_string(),
resource: Arc::new(draft.create_resource(contents.clone())),
name: AnchorName::new(id),
resource: InnerResourcePtr::new(contents, draft),
})
.into_iter(),
)
Expand All @@ -125,12 +170,12 @@ pub(crate) fn legacy_anchor_in_id<'a>(
Box::new(
contents
.as_object()
.and_then(|schema| schema.get("id").and_then(Value::as_str))
.and_then(|schema| schema.get("id"))
.and_then(Value::as_str)
.and_then(|id| id.strip_prefix('#'))
.map(move |id| Anchor::Default {
draft,
name: id.to_string(),
resource: Arc::new(draft.create_resource(contents.clone())),
name: AnchorName::new(id),
resource: InnerResourcePtr::new(contents, draft),
})
.into_iter(),
)
Expand Down
100 changes: 100 additions & 0 deletions crates/jsonschema-referencing/src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use core::hash::{BuildHasherDefault, Hash, Hasher};
use std::{
collections::{hash_map::Entry, HashMap},
sync::Arc,
};

use ahash::AHasher;
use fluent_uri::Uri;
use parking_lot::RwLock;

use crate::{hasher::BuildNoHashHasher, uri, Error};

#[derive(Debug, Clone)]
pub(crate) struct UriCache {
cache: HashMap<u64, Arc<Uri<String>>, BuildNoHashHasher>,
}

impl UriCache {
pub(crate) fn new() -> Self {
Self {
cache: HashMap::with_hasher(BuildHasherDefault::default()),
}
}

pub(crate) fn with_capacity(capacity: usize) -> Self {
Self {
cache: HashMap::with_capacity_and_hasher(capacity, BuildHasherDefault::default()),
}
}

pub(crate) fn resolve_against(
&mut self,
base: &Uri<&str>,
uri: impl AsRef<str>,
) -> Result<Arc<Uri<String>>, Error> {
let mut hasher = AHasher::default();
(base.as_str(), uri.as_ref()).hash(&mut hasher);
let hash = hasher.finish();

Ok(match self.cache.entry(hash) {
Entry::Occupied(entry) => Arc::clone(entry.get()),
Entry::Vacant(entry) => {
let new = Arc::new(uri::resolve_against(base, uri.as_ref())?);
Arc::clone(entry.insert(new))
}
})
}

pub(crate) fn into_shared(self) -> SharedUriCache {
SharedUriCache {
cache: RwLock::new(self.cache),
}
}
}

/// A dedicated type for URI resolution caching.
#[derive(Debug)]
pub(crate) struct SharedUriCache {
cache: RwLock<HashMap<u64, Arc<Uri<String>>, BuildNoHashHasher>>,
}

impl Clone for SharedUriCache {
fn clone(&self) -> Self {
Self {
cache: RwLock::new(
self.cache
.read()
.iter()
.map(|(k, v)| (*k, Arc::clone(v)))
.collect(),
),
}
}
}

impl SharedUriCache {
pub(crate) fn resolve_against(
&self,
base: &Uri<&str>,
uri: impl AsRef<str>,
) -> Result<Arc<Uri<String>>, Error> {
let mut hasher = AHasher::default();
(base.as_str(), uri.as_ref()).hash(&mut hasher);
let hash = hasher.finish();

if let Some(cached) = self.cache.read().get(&hash).cloned() {
return Ok(cached);
}

let new = Arc::new(uri::resolve_against(base, uri.as_ref())?);
self.cache.write().insert(hash, Arc::clone(&new));
Ok(new)
}

pub(crate) fn into_local(self) -> UriCache {
UriCache {
cache: self.cache.into_inner(),
}
}
}
45 changes: 45 additions & 0 deletions crates/jsonschema-referencing/src/hasher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use core::hash::{BuildHasherDefault, Hasher};

pub(crate) type BuildNoHashHasher = BuildHasherDefault<NoHashHasher>;

#[derive(Default)]
pub(crate) struct NoHashHasher(u64);

impl Hasher for NoHashHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, _: &[u8]) {
unreachable!("Should not be used")
}
fn write_u8(&mut self, _: u8) {
unreachable!("Should not be used")
}
fn write_u16(&mut self, _: u16) {
unreachable!("Should not be used")
}
fn write_u32(&mut self, _: u32) {
unreachable!("Should not be used")
}
fn write_u64(&mut self, n: u64) {
self.0 = n;
}
fn write_usize(&mut self, _: usize) {
unreachable!("Should not be used")
}
fn write_i8(&mut self, _: i8) {
unreachable!("Should not be used")
}
fn write_i16(&mut self, _: i16) {
unreachable!("Should not be used")
}
fn write_i32(&mut self, _: i32) {
unreachable!("Should not be used")
}
fn write_i64(&mut self, _: i64) {
unreachable!("Should not be used")
}
fn write_isize(&mut self, _: isize) {
unreachable!("Should not be used")
}
}
2 changes: 2 additions & 0 deletions crates/jsonschema-referencing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//!
//! An implementation-agnostic JSON reference resolution library for Rust.
mod anchors;
mod cache;
mod error;
mod hasher;
mod list;
pub mod meta;
mod registry;
Expand Down
Loading

0 comments on commit baf1796

Please sign in to comment.