jsonld-expandable-core 0.23.2

Runtime traits, attribute parser, and codegen for the JSON-LD `Expandable` derive
Documentation
//! Runtime support for the JSON-LD `Expandable` derive macro.
//!
//! This crate hosts:
//!
//! - The [`Expandable`] trait that the derive macro implements.
//! - The [`JsonValue`] backend trait abstracting any JSON value type the
//!   derive can emit (`serde_json::Value`, `sonic_rs::Value`,
//!   `jstrict::Value`, or a user-supplied type).
//! - The [`ToJsonValue`] trait used by generated code to convert leaf field
//!   values into a chosen [`JsonValue`] backend.
//! - Behind the `codegen` feature: the attribute parser, intermediate
//!   representation, and codegen used by the `jsonld-expandable` proc-macro
//!   crate (kept here so they can be unit-tested without `trybuild`). Only
//!   the derive crate enables the feature; runtime consumers never pull in
//!   syn/quote/proc-macro2, and those modules are internal API for the
//!   derive, not covered by semver.
//!
//! Application code rarely depends on this crate directly; instead enable an
//! `expandable*` feature on the umbrella `jsonld` crate, which re-exports both
//! the derive and this crate (as `jsonld::expandable_core`). The derive reads
//! the consuming crate's manifest and points the code it generates at whichever
//! of the two that crate depends on, following a renamed dependency to the name
//! it was given.
//!
//! # Limitations
//!
//! The derive does not add trait bounds for generic parameters: a generic
//! struct would need `T: ToJsonValue<V>`-style bounds on the generated
//! `impl`, which the derive cannot express yet. Deriving on a generic struct
//! fails with unresolved trait-bound errors; use concrete field types.

// On docs.rs, label every feature-gated item with the feature that unlocks it.
// `doc(auto_cfg)` is still nightly-gated, and `docsrs` is set by docs.rs itself
// (see `rustdoc-args` in Cargo.toml), so stable builds are unaffected.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]
#[cfg(feature = "codegen")]
#[doc(hidden)]
pub mod attrs;
#[cfg(feature = "codegen")]
#[doc(hidden)]
pub mod codegen;
#[cfg(feature = "codegen")]
#[doc(hidden)]
pub mod ir;
#[cfg(feature = "codegen")]
#[doc(hidden)]
pub mod iri;

mod value;

#[cfg(any(feature = "serde-json", feature = "sonic-rs", feature = "jstrict"))]
mod backends;

#[cfg(feature = "chrono")]
mod interop_chrono;

pub use value::{JsonValue, ToJsonValue};

/// Convert `&self` into the expanded JSON-LD form for the chosen backend `V`.
///
/// Generated by the `Expandable` derive in `jsonld-expandable`.
pub trait Expandable {
    /// Render `self` as expanded JSON-LD using backend `V`.
    fn expand<V: JsonValue>(&self) -> V;
}

/// Field marker for dynamic `@type` arrays.
///
/// Implement this for fields tagged with `#[jsonld(type_value)]` to provide a
/// `@type` array whose contents are computed at runtime.
pub trait ExpandableTypeValue {
    /// Renders the node's `@type` entry as a JSON array of type IRIs in the
    /// chosen backend `V`.
    fn to_type_array<V: JsonValue>(&self) -> V;
}

/// Renders a field tagged `#[jsonld(container = "language")]`.
///
/// Implemented here for `HashMap` and `BTreeMap` whose keys and values are both
/// `AsRef<str>`, which covers the ordinary language map of tag to text. A
/// language that carries several strings, or values that are not strings, needs
/// its own implementation.
pub trait ExpandableLanguageMap {
    /// Renders the map in expanded language-map form: a JSON array of
    /// `{"@value": ..., "@language": ...}` objects in the chosen backend `V`.
    fn to_expanded_language_map<V: JsonValue>(&self) -> V;
}

fn language_entries<'a, V: JsonValue, I>(entries: I) -> V
where
    I: IntoIterator<Item = (&'a str, &'a str)>,
{
    V::array(
        entries
            .into_iter()
            .map(|(lang, text)| V::object([("@value".to_string(), V::string(text)), ("@language".to_string(), V::string(lang))])),
    )
}

impl<K: AsRef<str>, T: AsRef<str>, S> ExpandableLanguageMap for std::collections::HashMap<K, T, S> {
    fn to_expanded_language_map<V: JsonValue>(&self) -> V {
        // `HashMap` iteration order varies between processes. Sorting by
        // language tag keeps the emitted array stable, which matters as soon as
        // the output is compared, hashed or golden-tested.
        let mut entries: Vec<(&str, &str)> = self.iter().map(|(k, v)| (k.as_ref(), v.as_ref())).collect();
        entries.sort_unstable_by_key(|(lang, _)| *lang);
        language_entries(entries)
    }
}

impl<K: AsRef<str>, T: AsRef<str>> ExpandableLanguageMap for std::collections::BTreeMap<K, T> {
    fn to_expanded_language_map<V: JsonValue>(&self) -> V {
        language_entries(self.iter().map(|(k, v)| (k.as_ref(), v.as_ref())))
    }
}

/// Driver entry used by the proc-macro shell crate.
///
/// Lives here so the parser, IR, and codegen can be exercised by ordinary
/// `#[test]` functions; proc-macro crates cannot host normal tests.
///
/// `runtime` is the path the generated code uses to reach this crate. Only the
/// derive can determine it, since it depends on how the *consuming* crate
/// depends on this one.
#[cfg(feature = "codegen")]
#[doc(hidden)]
#[must_use]
pub fn derive_expandable(input: &syn::DeriveInput, runtime: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    match codegen::generate(input, runtime) {
        Ok(ts) => ts,
        Err(err) => err.to_compile_error(),
    }
}